From 5ecea99b4f9dfa78071f9451c0880196af91d393 Mon Sep 17 00:00:00 2001 From: Thorsten Alteholz Date: Mon, 16 Jul 2018 20:51:19 +0200 Subject: Import osmo-bts_0.8.1-1.debian.tar.xz [dgit import tarball osmo-bts 0.8.1-1 osmo-bts_0.8.1-1.debian.tar.xz] --- changelog | 42 ++++++++++++++++++++++++++ compat | 1 + control | 37 +++++++++++++++++++++++ copyright | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++ gbp.conf | 2 ++ man/genmanpages.sh | 4 +++ man/osmobts-trx.txt | 45 +++++++++++++++++++++++++++ osmo-bts.manpages | 1 + patches/series | 1 + patches/spelling.patch | 54 +++++++++++++++++++++++++++++++++ rules | 31 +++++++++++++++++++ source/format | 1 + watch | 2 ++ 13 files changed, 303 insertions(+) create mode 100644 changelog create mode 100644 compat create mode 100644 control create mode 100644 copyright create mode 100644 gbp.conf create mode 100755 man/genmanpages.sh create mode 100644 man/osmobts-trx.txt create mode 100644 osmo-bts.manpages create mode 100644 patches/series create mode 100644 patches/spelling.patch create mode 100755 rules create mode 100644 source/format create mode 100644 watch diff --git a/changelog b/changelog new file mode 100644 index 0000000..93f2e47 --- /dev/null +++ b/changelog @@ -0,0 +1,42 @@ +osmo-bts (0.8.1-1) experimental; urgency=medium + + * New upstream release + + -- Thorsten Alteholz Mon, 16 Jul 2018 20:51:19 +0200 + +osmo-bts (0.7.0-1) unstable; urgency=medium + + * New upstream release (Closes: #895454) + * debian/control: add salsa URLs + * debian/control: use dh11 + * debian/control: bump standard to 4.1.4 (no changes) + * debian/patches: update/remove patches + + -- Thorsten Alteholz Sat, 21 Apr 2018 16:15:01 +0200 + +osmo-bts (0.4.0-3) unstable; urgency=medium + + * debian/control: add myself to uploaders + * debian/control: move package to debian-mobcom and change URLs + * debian/control: change maintainer to debian-mobcom-maintainers + * debian/control: use dh10 + * debian/control: bump standard to 4.1.0 (no changes) + * debian/control: remove redundant dependency of dh-autoreconf + + -- Thorsten Alteholz Mon, 11 Sep 2017 19:52:59 +0200 + +osmo-bts (0.4.0-2) unstable; urgency=medium + + * debian/man/osmobts-trx.txt: + - Fixed formatting issue + * debian/rules: + - Fixed reproducibility issue with date in man page + - Print test results in case of a failure + + -- Ruben Undheim Wed, 08 Jun 2016 23:31:28 +0200 + +osmo-bts (0.4.0-1) unstable; urgency=low + + * Initial release (Closes: #806584) + + -- Ruben Undheim Sat, 28 May 2016 10:01:24 +0200 diff --git a/compat b/compat new file mode 100644 index 0000000..b4de394 --- /dev/null +++ b/compat @@ -0,0 +1 @@ +11 diff --git a/control b/control new file mode 100644 index 0000000..20f45f6 --- /dev/null +++ b/control @@ -0,0 +1,37 @@ +Source: osmo-bts +Maintainer: Debian Mobcom Maintainers +Uploaders: Ruben Undheim + , Thorsten Alteholz +Section: net +Priority: optional +Build-Depends: debhelper (>= 11), + pkg-config, + libosmocore-dev (>= 0.11.0), + openbsc-dev (>= 1.1.0), + libosmo-abis-dev (>= 0.5.0), + libgps-dev, + libortp-dev, + libosmocoding0, + txt2man +Standards-Version: 4.1.4 +Vcs-Browser: https://salsa.debian.org/debian-mobcom-team/osmo-bts +Vcs-Git: https://salsa.debian.org/debian-mobcom-team/osmo-bts.git +Homepage: http://openbsc.osmocom.org/trac/wiki/OsmoBTS + +Package: osmo-bts +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends} +Description: Base Transceiver Station for GSM + OsmoBTS is a software implementation of Layer2/3 of a BTS. It implements the + following protocols/interfaces: + LAPDm (GSM 04.06) + RTP + A-bis/IP in IPA multiplex + OML (GSM TS 12.21) + RSL (GSM TS 08.58) + . + OsmoBTS is modular and has support for multiple back-ends. A back-end talks to + a specific L1/PHY implementation of the respective BTS hardware. Based on this + architecture, it should be relatively easy to add a new back-end to support + so-far unsupported GSM PHY/L1 and associated hardware. diff --git a/copyright b/copyright new file mode 100644 index 0000000..fb74d0b --- /dev/null +++ b/copyright @@ -0,0 +1,82 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: osmo-bts +Source: http://cgit.osmocom.org/osmo-bts/ +Files-Excluded: debian + +Files: * +Copyright: 2008-2014 Harald Welte + 2009,2011,2013 Andreas Eversberg + 2010,2011 On-Waves + 2012-2015 Holger Hans Peter Freyther + 2014 sysmocom s.f.m.c. Gmbh + 2015 Alexander Chemeris +License: AGPL-3+ + +Files: src/osmo-bts-sysmo/eeprom.c + src/osmo-bts-sysmo/eeprom.h +Copyright: 2012 Nutaq +License: MIT +Comment: Yves Godin is the author + +Files: src/common/pcu_sock.c +Copyright: 2008-2010 Harald Welte + 2009-2012 Andreas Eversberg + 2012 Holger Hans Peter Freyther +License: GPL-2+ + +Files: debian/* +Copyright: 2015-2016 Ruben Undheim +License: AGPL-3+ + + +License: AGPL-3+ + 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 . + + +License: GPL-2+ + This package 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, see . + . + On Debian systems, the complete text of the GNU General + Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". + + +License: MIT + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. diff --git a/gbp.conf b/gbp.conf new file mode 100644 index 0000000..cec628c --- /dev/null +++ b/gbp.conf @@ -0,0 +1,2 @@ +[DEFAULT] +pristine-tar = True diff --git a/man/genmanpages.sh b/man/genmanpages.sh new file mode 100755 index 0000000..b8904c0 --- /dev/null +++ b/man/genmanpages.sh @@ -0,0 +1,4 @@ +#!/bin/bash + + +txt2man -d "${CHANGELOG_DATE}" -t OSMOBTS-TRX -s 1 osmobts-trx.txt > osmobts-trx.1 diff --git a/man/osmobts-trx.txt b/man/osmobts-trx.txt new file mode 100644 index 0000000..b4453d7 --- /dev/null +++ b/man/osmobts-trx.txt @@ -0,0 +1,45 @@ +NAME + osmobts-trx - Base Transceiver Station for GSM + +SYNOPSIS + osmobts-trx [options] + +DESCRIPTION + OsmoBTS is a software implementation of Layer2/3 of a BTS. It implements the + following protocols/interfaces: + - LAPDm (GSM 04.06) + - RTP + - A-bis/IP in IPA multiplex + - OML (GSM TS 12.21) + - RSL (GSM TS 08.58) + + OsmoBTS is modular and has support for multiple back-ends. A back-end talks to + a specific L1/PHY implementation of the respective BTS hardware. Based on this + architecture, it should be relatively easy to add a new back-end to support + so-far unsupported GSM PHY/L1 and associated hardware. + + + +OPTIONS + -h,--help this text + -d,--debug MASK Enable debugging (e.g. -d DRSL:DOML:DLAPDM) + -D,--daemonize For the process into a background daemon + -c,--config-file Specify the filename of the config file + -s,--disable-color Don't use colors in stderr log output + -T,--timestamp Prefix every log line with a timestamp + -V,--version Print version information and exit + -e,--log-level Set a global log-level + -t,--trx-num Set number of TRX (default=1) + -i,--gsmtap-ip The destination IP used for GSMTAP. + -r,--realtime PRIO Set realtime scheduler with given prio + -I,--local-trx-ip Local IP for transceiver to connect (default=127.0.0.1) + + +SEE ALSO + osmo-nitb(1), osmo-bsc(1), osmo-trx(1) + +AUTHOR + This manual page was written by Ruben Undheim for the Debian project (and may be used by others). + + + diff --git a/osmo-bts.manpages b/osmo-bts.manpages new file mode 100644 index 0000000..c63baa9 --- /dev/null +++ b/osmo-bts.manpages @@ -0,0 +1 @@ +debian/man/osmobts-trx.1 diff --git a/patches/series b/patches/series new file mode 100644 index 0000000..5299247 --- /dev/null +++ b/patches/series @@ -0,0 +1 @@ +spelling.patch diff --git a/patches/spelling.patch b/patches/spelling.patch new file mode 100644 index 0000000..eaef35b --- /dev/null +++ b/patches/spelling.patch @@ -0,0 +1,54 @@ +Description: fix spelling stuff mentioned by lintian +Author: Thorsten Alteholz +Index: osmo-bts-0.8.1/src/osmo-bts-trx/trx_if.c +=================================================================== +--- osmo-bts-0.8.1.orig/src/osmo-bts-trx/trx_if.c 2018-07-16 13:55:18.710308370 +0200 ++++ osmo-bts-0.8.1/src/osmo-bts-trx/trx_if.c 2018-07-16 13:55:18.698308370 +0200 +@@ -532,7 +532,7 @@ + burst_len = EGPRS_BURST_LEN; + /* Accept bursts ending with 2 bytes of padding (OpenBTS compatible trx) or without them: */ + } else if (len != GSM_BURST_LEN + 10 && len != GSM_BURST_LEN + 8) { +- LOGP(DTRX, LOGL_NOTICE, "Got data message with invalid lenght " ++ LOGP(DTRX, LOGL_NOTICE, "Got data message with invalid length " + "'%d'\n", len); + return -EINVAL; + } +Index: osmo-bts-0.8.1/src/osmo-bts-trx/trx_vty.c +=================================================================== +--- osmo-bts-0.8.1.orig/src/osmo-bts-trx/trx_vty.c 2018-07-16 13:55:18.710308370 +0200 ++++ osmo-bts-0.8.1/src/osmo-bts-trx/trx_vty.c 2018-07-16 13:55:18.698308370 +0200 +@@ -234,7 +234,7 @@ + OSMOTRX_STR + "Set the maximum acceptable delay of a Normal Burst (in GSM symbols)." + " USE FOR TESTING ONLY, DON'T CHANGE IN PRODUCTION USE!" +- " During normal operation, Normal Bursts delay are controled by a Timing" ++ " During normal operation, Normal Bursts delay are controlled by a Timing" + " Advance control loop and thus Normal Bursts arrive to a BTS with no more" + " than a couple GSM symbols, which is already taken into account in osmo-trx." + " So changing this setting will have no effect in production installations" +Index: osmo-bts-0.8.1/src/common/rsl.c +=================================================================== +--- osmo-bts-0.8.1.orig/src/common/rsl.c 2018-07-16 13:55:18.710308370 +0200 ++++ osmo-bts-0.8.1/src/common/rsl.c 2018-07-16 13:55:18.698308370 +0200 +@@ -2815,7 +2815,7 @@ + + msg->lchan = lchan_lookup(trx, dch->chan_nr, "RSL rx IPACC: "); + if (!msg->lchan) { +- LOGP(DRSL, LOGL_ERROR, "Rx RSL %s for unknow lchan\n", ++ LOGP(DRSL, LOGL_ERROR, "Rx RSL %s for unknown lchan\n", + rsl_msg_name(dch->c.msg_type)); + return rsl_reject_unknown_lchan(msg); + } +Index: osmo-bts-0.8.1/src/common/pcu_sock.c +=================================================================== +--- osmo-bts-0.8.1.orig/src/common/pcu_sock.c 2018-07-16 13:55:18.710308370 +0200 ++++ osmo-bts-0.8.1/src/common/pcu_sock.c 2018-07-16 13:55:18.698308370 +0200 +@@ -641,7 +641,7 @@ + rc = pcu_rx_txt_ind(bts, &pcu_prim->u.txt_ind); + break; + default: +- LOGP(DPCU, LOGL_ERROR, "Received unknwon PCU msg type %d\n", ++ LOGP(DPCU, LOGL_ERROR, "Received unknown PCU msg type %d\n", + msg_type); + rc = -EINVAL; + } diff --git a/rules b/rules new file mode 100755 index 0000000..f4bb955 --- /dev/null +++ b/rules @@ -0,0 +1,31 @@ +#!/usr/bin/make -f +#export DH_VERBOSE = 1 + +export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed + +export DEB_BUILD_MAINT_OPTIONS = hardening=+all + +CHANGELOG_DATE ?= $(shell LC_ALL=C date -u -d "`dpkg-parsechangelog --show-field Date`" +"%d %B %Y") + +%: + dh $@ --with autoreconf + +override_dh_auto_configure: + dh_auto_configure -- --with-openbsc=/usr/include/osmocom --enable-trx + #dh_auto_configure -- --with-openbsc=/usr/include/osmocom --enable-trx --enable-sysmocom-bts + +override_dh_installman: + cd debian/man ; CHANGELOG_DATE="$(CHANGELOG_DATE)" ./genmanpages.sh + dh_installman + +override_dh_clean: + dh_clean + $(RM) debian/man/osmobts-trx.1 + $(RM) tests/package.m4 + $(RM) tests/testsuite + + +# 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/source/format b/source/format new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/watch b/watch new file mode 100644 index 0000000..8698fcf --- /dev/null +++ b/watch @@ -0,0 +1,2 @@ +version=4 +opts="mode=git, dversionmangle=s/\+ds//" https://git.osmocom.org/osmo-bts refs/tags/([\d\.]+) debian uupdate -- cgit v1.2.3 From ad0e376296dad8f052d20cc4e9719a8b434f1c33 Mon Sep 17 00:00:00 2001 From: Thorsten Alteholz Date: Mon, 16 Jul 2018 20:51:19 +0200 Subject: Import osmo-bts_0.8.1.orig.tar.xz [dgit import orig osmo-bts_0.8.1.orig.tar.xz] --- .gitignore | 82 + .gitreview | 3 + .mailmap | 12 + COPYING | 661 +++++ Makefile.am | 24 + README.md | 127 + configure.ac | 302 +++ contrib/dtx_check.gawk | 89 + contrib/dump_docs.py | 40 + contrib/eeprom_reader.c | 91 + contrib/jenkins_bts_model.sh | 45 + contrib/jenkins_bts_trx.sh | 24 + contrib/jenkins_common.sh | 47 + contrib/jenkins_lc15.sh | 21 + contrib/jenkins_oct.sh | 21 + contrib/jenkins_oct_and_bts_trx.sh | 27 + contrib/jenkins_sysmobts.sh | 28 + contrib/l1fwd.init | 31 + contrib/lc15bts-mgr.service | 29 + contrib/osmo-bts-lc15.service | 21 + contrib/osmo-bts-sysmo.service | 21 + contrib/respawn-only.sh | 13 + contrib/respawn.sh | 18 + contrib/screenrc-l1fwd | 3 + contrib/screenrc-sysmobts | 5 + contrib/si_check.gawk | 91 + contrib/superfemto.sh | 110 + contrib/sysmobts-mgr.service | 12 + contrib/sysmobts.init | 29 + contrib/sysmobts.service | 20 + doc/control_interface.txt | 61 + doc/examples/calypso/osmo-bts.cfg | 38 + doc/examples/litecell15/lc15bts-mgr.cfg | 43 + doc/examples/litecell15/osmo-bts.cfg | 43 + doc/examples/octphy/osmo-bts-trx2dsp1.cfg | 34 + doc/examples/octphy/osmo-bts.cfg | 31 + doc/examples/sysmo/osmo-bts.cfg | 29 + doc/examples/sysmo/sysmobts-mgr.cfg | 23 + doc/examples/trx/osmo-bts.cfg | 34 + doc/examples/virtual/openbsc-virtual.cfg | 151 ++ doc/examples/virtual/osmobts-virtual.cfg | 61 + doc/phy_link.txt | 57 + doc/startup.txt | 42 + git-version-gen | 151 ++ include/Makefile.am | 1 + include/osmo-bts/Makefile.am | 5 + include/osmo-bts/abis.h | 29 + include/osmo-bts/amr.h | 18 + include/osmo-bts/bts.h | 67 + include/osmo-bts/bts_model.h | 64 + include/osmo-bts/cbch.h | 16 + include/osmo-bts/control_if.h | 5 + include/osmo-bts/dtx_dl_amr_fsm.h | 44 + include/osmo-bts/gsm_data.h | 58 + include/osmo-bts/gsm_data_shared.h | 815 ++++++ include/osmo-bts/handover.h | 12 + include/osmo-bts/l1sap.h | 96 + include/osmo-bts/logging.h | 40 + include/osmo-bts/measurement.h | 11 + include/osmo-bts/msg_utils.h | 48 + include/osmo-bts/oml.h | 50 + include/osmo-bts/paging.h | 52 + include/osmo-bts/pcu_if.h | 24 + include/osmo-bts/pcuif_proto.h | 195 ++ include/osmo-bts/phy_link.h | 160 ++ include/osmo-bts/power_control.h | 7 + include/osmo-bts/rsl.h | 46 + include/osmo-bts/scheduler.h | 224 ++ include/osmo-bts/scheduler_backend.h | 94 + include/osmo-bts/signal.h | 19 + include/osmo-bts/tx_power.h | 78 + include/osmo-bts/vty.h | 32 + src/Makefile.am | 17 + src/common/Makefile.am | 17 + src/common/abis.c | 276 ++ src/common/amr.c | 170 ++ src/common/bts.c | 740 ++++++ src/common/bts_ctrl_commands.c | 93 + src/common/bts_ctrl_lookup.c | 115 + src/common/cbch.c | 192 ++ src/common/dtx_dl_amr_fsm.c | 477 ++++ src/common/gsm_data_shared.c | 829 +++++++ src/common/handover.c | 164 ++ src/common/l1sap.c | 1484 +++++++++++ src/common/lchan.c | 49 + src/common/load_indication.c | 94 + src/common/logging.c | 150 ++ src/common/main.c | 368 +++ src/common/measurement.c | 418 ++++ src/common/msg_utils.c | 603 +++++ src/common/oml.c | 1495 +++++++++++ src/common/paging.c | 630 +++++ src/common/pcu_sock.c | 966 +++++++ src/common/phy_link.c | 163 ++ src/common/power_control.c | 89 + src/common/rsl.c | 2900 ++++++++++++++++++++++ src/common/scheduler.c | 938 +++++++ src/common/scheduler_mframe.c | 824 ++++++ src/common/sysinfo.c | 177 ++ src/common/tx_power.c | 306 +++ src/common/vty.c | 1598 ++++++++++++ src/osmo-bts-litecell15/Makefile.am | 38 + src/osmo-bts-litecell15/calib_file.c | 456 ++++ src/osmo-bts-litecell15/hw_misc.c | 88 + src/osmo-bts-litecell15/hw_misc.h | 13 + src/osmo-bts-litecell15/l1_if.c | 1582 ++++++++++++ src/osmo-bts-litecell15/l1_if.h | 133 + src/osmo-bts-litecell15/l1_transp.h | 14 + src/osmo-bts-litecell15/l1_transp_hw.c | 326 +++ src/osmo-bts-litecell15/lc15bts.c | 332 +++ src/osmo-bts-litecell15/lc15bts.h | 64 + src/osmo-bts-litecell15/lc15bts_vty.c | 416 ++++ src/osmo-bts-litecell15/main.c | 222 ++ src/osmo-bts-litecell15/misc/lc15bts_bid.c | 140 ++ src/osmo-bts-litecell15/misc/lc15bts_bid.h | 51 + src/osmo-bts-litecell15/misc/lc15bts_bts.c | 131 + src/osmo-bts-litecell15/misc/lc15bts_bts.h | 21 + src/osmo-bts-litecell15/misc/lc15bts_clock.c | 260 ++ src/osmo-bts-litecell15/misc/lc15bts_clock.h | 16 + src/osmo-bts-litecell15/misc/lc15bts_led.c | 333 +++ src/osmo-bts-litecell15/misc/lc15bts_led.h | 22 + src/osmo-bts-litecell15/misc/lc15bts_mgr.c | 366 +++ src/osmo-bts-litecell15/misc/lc15bts_mgr.h | 422 ++++ src/osmo-bts-litecell15/misc/lc15bts_mgr_calib.c | 292 +++ src/osmo-bts-litecell15/misc/lc15bts_mgr_nl.c | 210 ++ src/osmo-bts-litecell15/misc/lc15bts_mgr_temp.c | 378 +++ src/osmo-bts-litecell15/misc/lc15bts_mgr_vty.c | 1074 ++++++++ src/osmo-bts-litecell15/misc/lc15bts_misc.c | 383 +++ src/osmo-bts-litecell15/misc/lc15bts_misc.h | 18 + src/osmo-bts-litecell15/misc/lc15bts_nl.c | 123 + src/osmo-bts-litecell15/misc/lc15bts_nl.h | 27 + src/osmo-bts-litecell15/misc/lc15bts_par.c | 232 ++ src/osmo-bts-litecell15/misc/lc15bts_par.h | 44 + src/osmo-bts-litecell15/misc/lc15bts_power.c | 210 ++ src/osmo-bts-litecell15/misc/lc15bts_power.h | 38 + src/osmo-bts-litecell15/misc/lc15bts_swd.c | 178 ++ src/osmo-bts-litecell15/misc/lc15bts_swd.h | 7 + src/osmo-bts-litecell15/misc/lc15bts_temp.c | 74 + src/osmo-bts-litecell15/misc/lc15bts_temp.h | 28 + src/osmo-bts-litecell15/misc/lc15bts_util.c | 164 ++ src/osmo-bts-litecell15/oml.c | 1937 +++++++++++++++ src/osmo-bts-litecell15/oml_router.c | 132 + src/osmo-bts-litecell15/oml_router.h | 13 + src/osmo-bts-litecell15/tch.c | 534 ++++ src/osmo-bts-litecell15/utils.c | 115 + src/osmo-bts-litecell15/utils.h | 13 + src/osmo-bts-octphy/Makefile.am | 13 + src/osmo-bts-octphy/l1_if.c | 1800 ++++++++++++++ src/osmo-bts-octphy/l1_if.h | 117 + src/osmo-bts-octphy/l1_oml.c | 1767 +++++++++++++ src/osmo-bts-octphy/l1_oml.h | 18 + src/osmo-bts-octphy/l1_tch.c | 283 +++ src/osmo-bts-octphy/l1_utils.c | 141 ++ src/osmo-bts-octphy/l1_utils.h | 9 + src/osmo-bts-octphy/main.c | 101 + src/osmo-bts-octphy/octphy_hw_api.c | 404 +++ src/osmo-bts-octphy/octphy_hw_api.h | 84 + src/osmo-bts-octphy/octphy_vty.c | 437 ++++ src/osmo-bts-octphy/octpkt.c | 158 ++ src/osmo-bts-octphy/octpkt.h | 22 + src/osmo-bts-omldummy/Makefile.am | 8 + src/osmo-bts-omldummy/bts_model.c | 217 ++ src/osmo-bts-omldummy/main.c | 53 + src/osmo-bts-omldummy/respawn.sh | 6 + src/osmo-bts-sysmo/Makefile.am | 43 + src/osmo-bts-sysmo/calib_file.c | 475 ++++ src/osmo-bts-sysmo/calib_fixup.c | 101 + src/osmo-bts-sysmo/eeprom.c | 1804 ++++++++++++++ src/osmo-bts-sysmo/eeprom.h | 304 +++ src/osmo-bts-sysmo/femtobts.c | 370 +++ src/osmo-bts-sysmo/femtobts.h | 110 + src/osmo-bts-sysmo/hw_misc.c | 113 + src/osmo-bts-sysmo/hw_misc.h | 12 + src/osmo-bts-sysmo/l1_fwd.h | 5 + src/osmo-bts-sysmo/l1_fwd_main.c | 236 ++ src/osmo-bts-sysmo/l1_if.c | 1891 ++++++++++++++ src/osmo-bts-sysmo/l1_if.h | 171 ++ src/osmo-bts-sysmo/l1_transp.h | 14 + src/osmo-bts-sysmo/l1_transp_fwd.c | 152 ++ src/osmo-bts-sysmo/l1_transp_hw.c | 329 +++ src/osmo-bts-sysmo/main.c | 194 ++ src/osmo-bts-sysmo/misc/sysmobts-calib.c | 537 ++++ src/osmo-bts-sysmo/misc/sysmobts-layer1.c | 800 ++++++ src/osmo-bts-sysmo/misc/sysmobts-layer1.h | 45 + src/osmo-bts-sysmo/misc/sysmobts_eeprom.h | 44 + src/osmo-bts-sysmo/misc/sysmobts_mgr.c | 336 +++ src/osmo-bts-sysmo/misc/sysmobts_mgr.h | 122 + src/osmo-bts-sysmo/misc/sysmobts_mgr_2050.c | 384 +++ src/osmo-bts-sysmo/misc/sysmobts_mgr_calib.c | 538 ++++ src/osmo-bts-sysmo/misc/sysmobts_mgr_nl.c | 186 ++ src/osmo-bts-sysmo/misc/sysmobts_mgr_temp.c | 321 +++ src/osmo-bts-sysmo/misc/sysmobts_mgr_vty.c | 531 ++++ src/osmo-bts-sysmo/misc/sysmobts_misc.c | 275 ++ src/osmo-bts-sysmo/misc/sysmobts_misc.h | 65 + src/osmo-bts-sysmo/misc/sysmobts_nl.c | 120 + src/osmo-bts-sysmo/misc/sysmobts_nl.h | 24 + src/osmo-bts-sysmo/misc/sysmobts_par.c | 382 +++ src/osmo-bts-sysmo/misc/sysmobts_par.h | 38 + src/osmo-bts-sysmo/misc/sysmobts_util.c | 256 ++ src/osmo-bts-sysmo/oml.c | 1959 +++++++++++++++ src/osmo-bts-sysmo/oml_router.c | 129 + src/osmo-bts-sysmo/oml_router.h | 13 + src/osmo-bts-sysmo/sysmobts_ctrl.c | 274 ++ src/osmo-bts-sysmo/sysmobts_vty.c | 541 ++++ src/osmo-bts-sysmo/tch.c | 685 +++++ src/osmo-bts-sysmo/utils.c | 113 + src/osmo-bts-sysmo/utils.h | 12 + src/osmo-bts-trx/Makefile.am | 11 + src/osmo-bts-trx/l1_if.c | 758 ++++++ src/osmo-bts-trx/l1_if.h | 81 + src/osmo-bts-trx/loops.c | 341 +++ src/osmo-bts-trx/loops.h | 27 + src/osmo-bts-trx/main.c | 146 ++ src/osmo-bts-trx/scheduler_trx.c | 1687 +++++++++++++ src/osmo-bts-trx/trx_if.c | 795 ++++++ src/osmo-bts-trx/trx_if.h | 34 + src/osmo-bts-trx/trx_vty.c | 603 +++++ src/osmo-bts-virtual/Makefile.am | 10 + src/osmo-bts-virtual/bts_model.c | 176 ++ src/osmo-bts-virtual/l1_if.c | 461 ++++ src/osmo-bts-virtual/l1_if.h | 20 + src/osmo-bts-virtual/main.c | 139 ++ src/osmo-bts-virtual/osmo_mcast_sock.c | 112 + src/osmo-bts-virtual/osmo_mcast_sock.h | 29 + src/osmo-bts-virtual/scheduler_virtbts.c | 618 +++++ src/osmo-bts-virtual/virtual_um.c | 100 + src/osmo-bts-virtual/virtual_um.h | 31 + src/osmo-bts-virtual/virtualbts_vty.c | 185 ++ tests/Makefile.am | 45 + tests/agch/Makefile.am | 8 + tests/agch/agch_test.c | 240 ++ tests/agch/agch_test.ok | 23 + tests/cipher/Makefile.am | 8 + tests/cipher/cipher_test.c | 85 + tests/cipher/cipher_test.ok | 1 + tests/handover/Makefile.am | 8 + tests/handover/handover_test.c | 278 +++ tests/handover/handover_test.ok | 1 + tests/meas/Makefile.am | 9 + tests/meas/meas_test.c | 197 ++ tests/meas/meas_test.ok | 542 ++++ tests/meas/sysmobts_fr_samples.h | 2601 +++++++++++++++++++ tests/misc/Makefile.am | 11 + tests/misc/misc_test.c | 199 ++ tests/misc/misc_test.ok | 6 + tests/paging/Makefile.am | 8 + tests/paging/paging_test.c | 132 + tests/paging/paging_test.ok | 3 + tests/power/Makefile.am | 9 + tests/power/power_test.c | 88 + tests/power/power_test.ok | 7 + tests/stubs.c | 57 + tests/sysmobts/Makefile.am | 17 + tests/sysmobts/sysmobts_test.c | 208 ++ tests/sysmobts/sysmobts_test.ok | 19 + tests/testsuite.at | 51 + tests/tx_power/Makefile.am | 8 + tests/tx_power/tx_power_test.c | 247 ++ tests/tx_power/tx_power_test.err | 38 + tests/tx_power/tx_power_test.ok | 23 + 260 files changed, 65400 insertions(+) create mode 100644 .gitignore create mode 100644 .gitreview create mode 100644 .mailmap create mode 100644 COPYING create mode 100644 Makefile.am create mode 100644 README.md create mode 100644 configure.ac create mode 100755 contrib/dtx_check.gawk create mode 100755 contrib/dump_docs.py create mode 100644 contrib/eeprom_reader.c create mode 100755 contrib/jenkins_bts_model.sh create mode 100755 contrib/jenkins_bts_trx.sh create mode 100644 contrib/jenkins_common.sh create mode 100755 contrib/jenkins_lc15.sh create mode 100755 contrib/jenkins_oct.sh create mode 100755 contrib/jenkins_oct_and_bts_trx.sh create mode 100755 contrib/jenkins_sysmobts.sh create mode 100755 contrib/l1fwd.init create mode 100644 contrib/lc15bts-mgr.service create mode 100644 contrib/osmo-bts-lc15.service create mode 100644 contrib/osmo-bts-sysmo.service create mode 100755 contrib/respawn-only.sh create mode 100755 contrib/respawn.sh create mode 100644 contrib/screenrc-l1fwd create mode 100644 contrib/screenrc-sysmobts create mode 100755 contrib/si_check.gawk create mode 100755 contrib/superfemto.sh create mode 100644 contrib/sysmobts-mgr.service create mode 100755 contrib/sysmobts.init create mode 100644 contrib/sysmobts.service create mode 100644 doc/control_interface.txt create mode 100644 doc/examples/calypso/osmo-bts.cfg create mode 100644 doc/examples/litecell15/lc15bts-mgr.cfg create mode 100644 doc/examples/litecell15/osmo-bts.cfg create mode 100644 doc/examples/octphy/osmo-bts-trx2dsp1.cfg create mode 100644 doc/examples/octphy/osmo-bts.cfg create mode 100644 doc/examples/sysmo/osmo-bts.cfg create mode 100644 doc/examples/sysmo/sysmobts-mgr.cfg create mode 100644 doc/examples/trx/osmo-bts.cfg create mode 100644 doc/examples/virtual/openbsc-virtual.cfg create mode 100644 doc/examples/virtual/osmobts-virtual.cfg create mode 100644 doc/phy_link.txt create mode 100644 doc/startup.txt create mode 100755 git-version-gen create mode 100644 include/Makefile.am create mode 100644 include/osmo-bts/Makefile.am create mode 100644 include/osmo-bts/abis.h create mode 100644 include/osmo-bts/amr.h create mode 100644 include/osmo-bts/bts.h create mode 100644 include/osmo-bts/bts_model.h create mode 100644 include/osmo-bts/cbch.h create mode 100644 include/osmo-bts/control_if.h create mode 100644 include/osmo-bts/dtx_dl_amr_fsm.h create mode 100644 include/osmo-bts/gsm_data.h create mode 100644 include/osmo-bts/gsm_data_shared.h create mode 100644 include/osmo-bts/handover.h create mode 100644 include/osmo-bts/l1sap.h create mode 100644 include/osmo-bts/logging.h create mode 100644 include/osmo-bts/measurement.h create mode 100644 include/osmo-bts/msg_utils.h create mode 100644 include/osmo-bts/oml.h create mode 100644 include/osmo-bts/paging.h create mode 100644 include/osmo-bts/pcu_if.h create mode 100644 include/osmo-bts/pcuif_proto.h create mode 100644 include/osmo-bts/phy_link.h create mode 100644 include/osmo-bts/power_control.h create mode 100644 include/osmo-bts/rsl.h create mode 100644 include/osmo-bts/scheduler.h create mode 100644 include/osmo-bts/scheduler_backend.h create mode 100644 include/osmo-bts/signal.h create mode 100644 include/osmo-bts/tx_power.h create mode 100644 include/osmo-bts/vty.h create mode 100644 src/Makefile.am create mode 100644 src/common/Makefile.am create mode 100644 src/common/abis.c create mode 100644 src/common/amr.c create mode 100644 src/common/bts.c create mode 100644 src/common/bts_ctrl_commands.c create mode 100644 src/common/bts_ctrl_lookup.c create mode 100644 src/common/cbch.c create mode 100644 src/common/dtx_dl_amr_fsm.c create mode 100644 src/common/gsm_data_shared.c create mode 100644 src/common/handover.c create mode 100644 src/common/l1sap.c create mode 100644 src/common/lchan.c create mode 100644 src/common/load_indication.c create mode 100644 src/common/logging.c create mode 100644 src/common/main.c create mode 100644 src/common/measurement.c create mode 100644 src/common/msg_utils.c create mode 100644 src/common/oml.c create mode 100644 src/common/paging.c create mode 100644 src/common/pcu_sock.c create mode 100644 src/common/phy_link.c create mode 100644 src/common/power_control.c create mode 100644 src/common/rsl.c create mode 100644 src/common/scheduler.c create mode 100644 src/common/scheduler_mframe.c create mode 100644 src/common/sysinfo.c create mode 100644 src/common/tx_power.c create mode 100644 src/common/vty.c create mode 100644 src/osmo-bts-litecell15/Makefile.am create mode 100644 src/osmo-bts-litecell15/calib_file.c create mode 100644 src/osmo-bts-litecell15/hw_misc.c create mode 100644 src/osmo-bts-litecell15/hw_misc.h create mode 100644 src/osmo-bts-litecell15/l1_if.c create mode 100644 src/osmo-bts-litecell15/l1_if.h create mode 100644 src/osmo-bts-litecell15/l1_transp.h create mode 100644 src/osmo-bts-litecell15/l1_transp_hw.c create mode 100644 src/osmo-bts-litecell15/lc15bts.c create mode 100644 src/osmo-bts-litecell15/lc15bts.h create mode 100644 src/osmo-bts-litecell15/lc15bts_vty.c create mode 100644 src/osmo-bts-litecell15/main.c create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_bid.c create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_bid.h create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_bts.c create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_bts.h create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_clock.c create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_clock.h create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_led.c create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_led.h create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_mgr.c create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_mgr.h create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_mgr_calib.c create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_mgr_nl.c create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_mgr_temp.c create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_mgr_vty.c create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_misc.c create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_misc.h create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_nl.c create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_nl.h create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_par.c create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_par.h create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_power.c create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_power.h create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_swd.c create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_swd.h create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_temp.c create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_temp.h create mode 100644 src/osmo-bts-litecell15/misc/lc15bts_util.c create mode 100644 src/osmo-bts-litecell15/oml.c create mode 100644 src/osmo-bts-litecell15/oml_router.c create mode 100644 src/osmo-bts-litecell15/oml_router.h create mode 100644 src/osmo-bts-litecell15/tch.c create mode 100644 src/osmo-bts-litecell15/utils.c create mode 100644 src/osmo-bts-litecell15/utils.h create mode 100644 src/osmo-bts-octphy/Makefile.am create mode 100644 src/osmo-bts-octphy/l1_if.c create mode 100644 src/osmo-bts-octphy/l1_if.h create mode 100644 src/osmo-bts-octphy/l1_oml.c create mode 100644 src/osmo-bts-octphy/l1_oml.h create mode 100644 src/osmo-bts-octphy/l1_tch.c create mode 100644 src/osmo-bts-octphy/l1_utils.c create mode 100644 src/osmo-bts-octphy/l1_utils.h create mode 100644 src/osmo-bts-octphy/main.c create mode 100644 src/osmo-bts-octphy/octphy_hw_api.c create mode 100644 src/osmo-bts-octphy/octphy_hw_api.h create mode 100644 src/osmo-bts-octphy/octphy_vty.c create mode 100644 src/osmo-bts-octphy/octpkt.c create mode 100644 src/osmo-bts-octphy/octpkt.h create mode 100644 src/osmo-bts-omldummy/Makefile.am create mode 100644 src/osmo-bts-omldummy/bts_model.c create mode 100644 src/osmo-bts-omldummy/main.c create mode 100755 src/osmo-bts-omldummy/respawn.sh create mode 100644 src/osmo-bts-sysmo/Makefile.am create mode 100644 src/osmo-bts-sysmo/calib_file.c create mode 100644 src/osmo-bts-sysmo/calib_fixup.c create mode 100644 src/osmo-bts-sysmo/eeprom.c create mode 100644 src/osmo-bts-sysmo/eeprom.h create mode 100644 src/osmo-bts-sysmo/femtobts.c create mode 100644 src/osmo-bts-sysmo/femtobts.h create mode 100644 src/osmo-bts-sysmo/hw_misc.c create mode 100644 src/osmo-bts-sysmo/hw_misc.h create mode 100644 src/osmo-bts-sysmo/l1_fwd.h create mode 100644 src/osmo-bts-sysmo/l1_fwd_main.c create mode 100644 src/osmo-bts-sysmo/l1_if.c create mode 100644 src/osmo-bts-sysmo/l1_if.h create mode 100644 src/osmo-bts-sysmo/l1_transp.h create mode 100644 src/osmo-bts-sysmo/l1_transp_fwd.c create mode 100644 src/osmo-bts-sysmo/l1_transp_hw.c create mode 100644 src/osmo-bts-sysmo/main.c create mode 100644 src/osmo-bts-sysmo/misc/sysmobts-calib.c create mode 100644 src/osmo-bts-sysmo/misc/sysmobts-layer1.c create mode 100644 src/osmo-bts-sysmo/misc/sysmobts-layer1.h create mode 100644 src/osmo-bts-sysmo/misc/sysmobts_eeprom.h create mode 100644 src/osmo-bts-sysmo/misc/sysmobts_mgr.c create mode 100644 src/osmo-bts-sysmo/misc/sysmobts_mgr.h create mode 100644 src/osmo-bts-sysmo/misc/sysmobts_mgr_2050.c create mode 100644 src/osmo-bts-sysmo/misc/sysmobts_mgr_calib.c create mode 100644 src/osmo-bts-sysmo/misc/sysmobts_mgr_nl.c create mode 100644 src/osmo-bts-sysmo/misc/sysmobts_mgr_temp.c create mode 100644 src/osmo-bts-sysmo/misc/sysmobts_mgr_vty.c create mode 100644 src/osmo-bts-sysmo/misc/sysmobts_misc.c create mode 100644 src/osmo-bts-sysmo/misc/sysmobts_misc.h create mode 100644 src/osmo-bts-sysmo/misc/sysmobts_nl.c create mode 100644 src/osmo-bts-sysmo/misc/sysmobts_nl.h create mode 100644 src/osmo-bts-sysmo/misc/sysmobts_par.c create mode 100644 src/osmo-bts-sysmo/misc/sysmobts_par.h create mode 100644 src/osmo-bts-sysmo/misc/sysmobts_util.c create mode 100644 src/osmo-bts-sysmo/oml.c create mode 100644 src/osmo-bts-sysmo/oml_router.c create mode 100644 src/osmo-bts-sysmo/oml_router.h create mode 100644 src/osmo-bts-sysmo/sysmobts_ctrl.c create mode 100644 src/osmo-bts-sysmo/sysmobts_vty.c create mode 100644 src/osmo-bts-sysmo/tch.c create mode 100644 src/osmo-bts-sysmo/utils.c create mode 100644 src/osmo-bts-sysmo/utils.h create mode 100644 src/osmo-bts-trx/Makefile.am create mode 100644 src/osmo-bts-trx/l1_if.c create mode 100644 src/osmo-bts-trx/l1_if.h create mode 100644 src/osmo-bts-trx/loops.c create mode 100644 src/osmo-bts-trx/loops.h create mode 100644 src/osmo-bts-trx/main.c create mode 100644 src/osmo-bts-trx/scheduler_trx.c create mode 100644 src/osmo-bts-trx/trx_if.c create mode 100644 src/osmo-bts-trx/trx_if.h create mode 100644 src/osmo-bts-trx/trx_vty.c create mode 100644 src/osmo-bts-virtual/Makefile.am create mode 100644 src/osmo-bts-virtual/bts_model.c create mode 100644 src/osmo-bts-virtual/l1_if.c create mode 100644 src/osmo-bts-virtual/l1_if.h create mode 100644 src/osmo-bts-virtual/main.c create mode 100644 src/osmo-bts-virtual/osmo_mcast_sock.c create mode 100644 src/osmo-bts-virtual/osmo_mcast_sock.h create mode 100644 src/osmo-bts-virtual/scheduler_virtbts.c create mode 100644 src/osmo-bts-virtual/virtual_um.c create mode 100644 src/osmo-bts-virtual/virtual_um.h create mode 100644 src/osmo-bts-virtual/virtualbts_vty.c create mode 100644 tests/Makefile.am create mode 100644 tests/agch/Makefile.am create mode 100644 tests/agch/agch_test.c create mode 100644 tests/agch/agch_test.ok create mode 100644 tests/cipher/Makefile.am create mode 100644 tests/cipher/cipher_test.c create mode 100644 tests/cipher/cipher_test.ok create mode 100644 tests/handover/Makefile.am create mode 100644 tests/handover/handover_test.c create mode 100644 tests/handover/handover_test.ok create mode 100644 tests/meas/Makefile.am create mode 100644 tests/meas/meas_test.c create mode 100644 tests/meas/meas_test.ok create mode 100644 tests/meas/sysmobts_fr_samples.h create mode 100644 tests/misc/Makefile.am create mode 100644 tests/misc/misc_test.c create mode 100644 tests/misc/misc_test.ok create mode 100644 tests/paging/Makefile.am create mode 100644 tests/paging/paging_test.c create mode 100644 tests/paging/paging_test.ok create mode 100644 tests/power/Makefile.am create mode 100644 tests/power/power_test.c create mode 100644 tests/power/power_test.ok create mode 100644 tests/stubs.c create mode 100644 tests/sysmobts/Makefile.am create mode 100644 tests/sysmobts/sysmobts_test.c create mode 100644 tests/sysmobts/sysmobts_test.ok create mode 100644 tests/testsuite.at create mode 100644 tests/tx_power/Makefile.am create mode 100644 tests/tx_power/tx_power_test.c create mode 100644 tests/tx_power/tx_power_test.err create mode 100644 tests/tx_power/tx_power_test.ok diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d81bd45 --- /dev/null +++ b/.gitignore @@ -0,0 +1,82 @@ +*.o +*.a +Makefile.in +Makefile +.deps + +btsconfig.h +btsconfig.h.in + +aclocal.m4 +autom4te.cache +config.log +config.status +config.guess +config.sub +configure +compile +depcomp +install-sh +missing +stamp-h1 +libtool +ltmain.sh +core +core.* + +# git-version-gen magic +.tarball-version +.version + +src/osmo-bts-sysmo/sysmobts-calib +src/osmo-bts-sysmo/l1fwd-proxy +src/osmo-bts-sysmo/osmo-bts-sysmo +src/osmo-bts-sysmo/osmo-bts-sysmo-remote +src/osmo-bts-sysmo/sysmobts-mgr +src/osmo-bts-sysmo/sysmobts-util + +src/osmo-bts-litecell15/lc15bts-mgr +src/osmo-bts-litecell15/lc15bts-util +src/osmo-bts-litecell15/misc/.dirstamp +src/osmo-bts-litecell15/osmo-bts-lc15 + +src/osmo-bts-trx/osmo-bts-trx + +src/osmo-bts-octphy/osmo-bts-octphy + +src/osmo-bts-virtual/osmo-bts-virtual +src/osmo-bts-omldummy/osmo-bts-omldummy + +tests/atconfig +tests/package.m4 +tests/agch/agch_test +tests/paging/paging_test +tests/cipher/cipher_test +tests/sysmobts/sysmobts_test +tests/meas/meas_test +tests/misc/misc_test +tests/handover/handover_test +tests/tx_power/tx_power_test +tests/testsuite +tests/testsuite.log + +# Possible generated file +doc/vty_reference.xml + +# Backups, vi, merges +*~ +*.sw? +*.orig +*.sav + +# debian +.tarball-version +debian/autoreconf.after +debian/autoreconf.before +debian/files +debian/*.debhelper.log +debian/*.substvars +debian/osmo-bts-trx-dbg/ +debian/osmo-bts-trx/ +debian/tmp/ +/tests/power/power_test diff --git a/.gitreview b/.gitreview new file mode 100644 index 0000000..2fcadd2 --- /dev/null +++ b/.gitreview @@ -0,0 +1,3 @@ +[gerrit] +host=gerrit.osmocom.org +project=osmo-bts diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..cda4057 --- /dev/null +++ b/.mailmap @@ -0,0 +1,12 @@ +Harald Welte +Harald Welte +Harald Welte +Holger Hans Peter Freyther +Holger Hans Peter Freyther +Holger Hans Peter Freyther +Andreas Eversberg +Andreas Eversberg +Andreas Eversberg +Pablo Neira Ayuso +Max Suraev +Tom Tsou diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..dba13ed --- /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. + 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. + + + Copyright (C) + + 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 . + +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 +. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..dc42574 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,24 @@ +AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6 + +SUBDIRS = include src tests + + +# package the contrib and doc +EXTRA_DIST = \ + contrib/dump_docs.py contrib/screenrc-l1fwd contrib/osmo-bts-sysmo.service \ + contrib/l1fwd.init contrib/screenrc-sysmobts contrib/respawn.sh \ + doc/examples/sysmo/osmo-bts.cfg \ + doc/examples/sysmo/sysmobts-mgr.cfg \ + doc/examples/virtual/openbsc-virtual.cfg \ + doc/examples/virtual/osmobts-virtual.cfg \ + git-version-gen .version \ + README.md + +@RELMAKE@ + +BUILT_SOURCES = $(top_srcdir)/.version + +$(top_srcdir)/.version: + echo $(VERSION) > $@-t && mv $@-t $@ +dist-hook: + echo $(VERSION) > $(distdir)/.tarball-version diff --git a/README.md b/README.md new file mode 100644 index 0000000..43c27b2 --- /dev/null +++ b/README.md @@ -0,0 +1,127 @@ +osmo-bts - Osmocom BTS Implementation +==================================== + +This repository contains a C-language implementation of a GSM Base +Transceiver Station (BTS). It is part of the +[Osmocom](https://osmocom.org/) Open Source Mobile Communications +project. + +This code implements Layer 2 and higher of a more or less conventional GSM BTS +(Base Transceiver Station) - however, using an Abis/IP interface, rather than +the old-fashioned E1/T1. + +Specifically, this includes + * BTS-side implementation of TS 08.58 (RSL) and TS 12.21 (OML) + * BTS-side implementation of LAPDm (using libosmocore/libosmogsm) + * A somewhat separated interface between those higher layer parts and the + Layer1 interface. + +Several kinds of BTS hardware are supported: + * sysmocom sysmoBTS + * Octasic octphy + * Nutaq litecell 1.5 + * software-defined radio based osmo-bts-trx (e.g. USRP B210, UmTRX) + +Homepage +-------- + +The official homepage of the project is +https://osmocom.org/projects/osmobts/wiki + +GIT Repository +-------------- + +You can clone from the official osmo-bts.git repository using + + git clone git://git.osmocom.org/osmo-bts.git + +There is a cgit interface at http://git.osmocom.org/osmo-bts/ + +Documentation +------------- + +We provide a +[User Manual](http://ftp.osmocom.org/docs/latest/osmobts-usermanual.pdf) +as well as a +[VTY Reference Manual](http://ftp.osmocom.org/docs/latest/osmobsc-vty-reference.pdf) +and a +[Abis refrence MAnual](http://ftp.osmocom.org/docs/latest/osmobts-abis.pdf) +describing the OsmoBTS specific A-bis dialect. + +Mailing List +------------ + +Discussions related to osmo-bts are happening on the +openbsc@lists.osmocom.org mailing list, please see +https://lists.osmocom.org/mailman/listinfo/openbsc for subscription +options and the list archive. + +Please observe the [Osmocom Mailing List +Rules](https://osmocom.org/projects/cellular-infrastructure/wiki/Mailing_List_Rules) +when posting. + +Contributing +------------ + +Our coding standards are described at +https://osmocom.org/projects/cellular-infrastructure/wiki/Coding_standards + +We us a gerrit based patch submission/review process for managing +contributions. Please see +https://osmocom.org/projects/cellular-infrastructure/wiki/Gerrit for +more details + +The current patch queue for osmo-bts can be seen at +https://gerrit.osmocom.org/#/q/project:osmo-bts+status:open + +Known Limitations +================= + +As of March 17, 2017, the following known limitations exist in this +implementation: + +Common Core +----------- + + * No Extended BCCH support + * System Information limited to 1,2,2bis,2ter,2quater,3,4,5,6,9,13 + * No RATSCCH in AMR + * Will reject TS 12.21 STARTING TIME in SET BTS ATTR / SET CHAN ATTR + * No support for frequency hopping + * No reporting of interference levels as part of TS 08.58 RF RES IND + * No error reporting in case PAGING COMMAND fails due to queue overflow + * No use of TS 08.58 BS Power and MS Power parameters + * No support of TS 08.58 MultiRate Control + * No support of TS 08.58 Supported Codec Types + * No support of Bter frame / ENHANCED MEASUREMENT REPORT + +osmo-bts-sysmo +-------------- + + * No CSD / ECSD support (not planned) + * GSM-R frequency band supported, but no NCH/ASCI/SoLSA + * All timeslots on one TRX have to use same training sequence (TSC) + * No multi-TRX support yet, though hardware+L1 support stacking + * Makes no use of 12.21 Intave Parameters and Interference + Level Boundaries + * MphConfig.CNF can be returned to the wrong callback. E.g. with Tx Power + and ciphering. The dispatch should take a look at the hLayer3. + +osmo-bts-octphy +--------------- + + * No support of EFR, HR voice codec (lack of PHY support?) + * No re-transmission of PHY primitives in case of time-out + * Link Quality / Measurement processing incomplete + * impossible to modify encryption parameters using RSL MODE MODIFY + * no clear indication of nominal transmit power, various power related + computations are likely off + * no OML attribute validation during bts_model_check_oml() + +osmo-bts-trx +------------ + + * TCH/F_PDCH cannel not working as voice (https://osmocom.org/issues/1865) + * No BER value delivered to OsmoPCU (https://osmocom.org/issues/1855) + * No 11bit RACH support (https://osmocom.org/issues/1854) + * No CBCH support (https://osmocom.org/issues/1617) diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..6456f8a --- /dev/null +++ b/configure.ac @@ -0,0 +1,302 @@ +dnl Process this file with autoconf to produce a configure script +AC_INIT([osmo-bts], + 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 checks for header files +AC_HEADER_STDC + +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 + +dnl checks for libraries +PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 0.11.0) +PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 0.11.0) +PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 0.11.0) +PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 0.11.0) +PKG_CHECK_MODULES(LIBOSMOCODEC, libosmocodec >= 0.11.0) +PKG_CHECK_MODULES(LIBOSMOCODING, libosmocoding >= 0.11.0) +PKG_CHECK_MODULES(LIBOSMOABIS, libosmoabis >= 0.5.0) +PKG_CHECK_MODULES(LIBOSMOTRAU, libosmotrau >= 0.5.0) +PKG_CHECK_MODULES(ORTP, ortp) + +AC_MSG_CHECKING([whether to enable support for sysmobts calibration tool]) +AC_ARG_ENABLE(sysmobts-calib, + AC_HELP_STRING([--enable-sysmobts-calib], + [enable code for sysmobts calibration tool [default=no]]), + [enable_sysmobts_calib="yes"],[enable_sysmobts_calib="no"]) +AC_MSG_RESULT([$enable_sysmobts_calib]) +AM_CONDITIONAL(ENABLE_SYSMOBTS_CALIB, test "x$enable_sysmobts_calib" = "xyes") + +AC_MSG_CHECKING([whether to enable support for sysmoBTS L1/PHY support]) +AC_ARG_ENABLE(sysmocom-bts, + AC_HELP_STRING([--enable-sysmocom-bts], + [enable code for sysmoBTS L1/PHY [default=no]]), + [enable_sysmocom_bts="yes"],[enable_sysmocom_bts="no"]) +AC_ARG_WITH([sysmobts], [AS_HELP_STRING([--with-sysmobts=INCLUDE_DIR], [Location of the sysmobts API header files, implies --enable-sysmocom-bts])], + [sysmobts_incdir="$withval"],[sysmobts_incdir="$incdir"]) +if test "x$sysmobts_incdir" != "x"; then + # --with-sysmobts was passed, imply enable_sysmocom_bts + enable_sysmocom_bts="yes" +fi +AC_SUBST([SYSMOBTS_INCDIR], -I$sysmobts_incdir) +AC_MSG_RESULT([$enable_sysmocom_bts]) +AM_CONDITIONAL(ENABLE_SYSMOBTS, test "x$enable_sysmocom_bts" = "xyes") +if test "$enable_sysmocom_bts" = "yes"; then + oldCPPFLAGS=$CPPFLAGS + CPPFLAGS="$CPPFLAGS $SYSMOBTS_INCDIR -I$srcdir/include" + AC_CHECK_HEADER([sysmocom/femtobts/superfemto.h],[], + [AC_MSG_ERROR([sysmocom/femtobts/superfemto.h can not be found in $sysmobts_incdir])], + [#include ]) + + # Check for the sbts2050_header.h that was added after the 3.6 release + AC_CHECK_HEADER([sysmocom/femtobts/sbts2050_header.h], [sysmo_uc_header="yes"], []) + if test "$sysmo_uc_header" = "yes" ; then + AC_DEFINE(BUILD_SBTS2050, 1, [Define if we want to build SBTS2050]) + fi + + PKG_CHECK_MODULES(LIBGPS, libgps) + CPPFLAGS=$oldCPPFLAGS +fi +AM_CONDITIONAL(BUILD_SBTS2050, test "x$sysmo_uc_header" = "xyes") + +AC_MSG_CHECKING([whether to enable support for osmo-trx based L1/PHY support]) +AC_ARG_ENABLE(trx, + AC_HELP_STRING([--enable-trx], + [enable code for osmo-trx L1/PHY [default=no]]), + [enable_trx="yes"],[enable_trx="no"]) +AC_MSG_RESULT([$enable_trx]) +AM_CONDITIONAL(ENABLE_TRX, test "x$enable_trx" = "xyes") + +AC_MSG_CHECKING([whether to enable support for Octasic OCTPHY-2G]) +AC_ARG_ENABLE(octphy, + AC_HELP_STRING([--enable-octphy], + [enable code for Octasic OCTPHY-2G [default=no]]), + [enable_octphy="yes"],[enable_octphy="no"]) +AC_ARG_WITH([octsdr-2g], [AS_HELP_STRING([--with-octsdr-2g], [Location of the OCTSDR-2G API header files])], + [octsdr2g_incdir="$withval"],[octsdr2g_incdir="`cd $srcdir; pwd`/src/osmo-bts-octphy/"]) +AC_SUBST([OCTSDR2G_INCDIR], -I$octsdr2g_incdir) +AC_MSG_RESULT([$enable_octphy]) +AM_CONDITIONAL(ENABLE_OCTPHY, test "x$enable_octphy" = "xyes") +if test "$enable_octphy" = "yes" ; then + oldCPPFLAGS=$CPPFLAGS + CPPFLAGS="$CPPFLAGS $OCTSDR2G_INCDIR -I$srcdir/include" + + AC_CHECK_HEADER([octphy/octvc1/gsm/octvc1_gsm_default.h],[], + [AC_MSG_ERROR([octphy/octvc1/gsm/octvc1_gsm_default.h can not be found in $octsdr2g_incdir])], + [#include ]) + + AC_CHECK_MEMBER([tOCTVC1_GSM_TRX_CONFIG.usCentreArfcn], + AC_DEFINE([OCTPHY_MULTI_TRX], + [1], + [Define to 1 if your octphy header files support multi-trx]), + [], + [#include ]) + + AC_CHECK_MEMBER([tOCTVC1_HW_RF_PORT_RX_STATS.Frequency], + AC_DEFINE([OCTPHY_USE_FREQUENCY], + [1], + [Define to 1 if your octphy header files support tOCTVC1_RADIO_FREQUENCY_VALUE type]), + [], + [#include ]) + + AC_CHECK_MEMBER([tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_RSP.TxConfig], + AC_DEFINE([OCTPHY_USE_TX_CONFIG], + [1], + [Define to 1 if your octphy header files support tOCTVC1_HW_RF_PORT_ANTENNA_TX_CONFIG type]), + [], + [#include ]) + + AC_CHECK_MEMBER([tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_RSP.RxConfig], + AC_DEFINE([OCTPHY_USE_RX_CONFIG], + [1], + [Define to 1 if your octphy header files support tOCTVC1_HW_RF_PORT_ANTENNA_RX_CONFIG type]), + [], + [#include ]) + + AC_CHECK_MEMBER([tOCTVC1_GSM_RF_CONFIG.ulTxAntennaId], + AC_DEFINE([OCTPHY_USE_ANTENNA_ID], + [1], + [Define to 1 if your octphy header files support antenna ids in tOCTVC1_GSM_RF_CONFIG]), + [], + [#include ]) + + AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_RSP.ulClkSourceSelection], + AC_DEFINE([OCTPHY_USE_CLK_SOURCE_SELECTION], + [1], + [Define to 1 if your octphy header files supports ulClkSourceSelection in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_RSP]), + [], + [#include ]) + + AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.lClockError], + AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_CLOCK_ERROR], + [1], + [Define to 1 if your octphy header files supports lClockError in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]), + [], + [#include ]) + + AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.lDroppedCycles], + AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DROPPED_CYCLES], + [1], + [Define to 1 if your octphy header files supports lDroppedCycles in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]), + [], + [#include ]) + + AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.ulPllFreqHz], + AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_PLL_FREQ_HZ], + [1], + [Define to 1 if your octphy header files supports ulPllFreqHz in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]), + [], + [#include ]) + + AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.ulPllFractionalFreqHz], + AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_PLL_FRACTIONAL_FREQ_HZ], + [1], + [Define to 1 if your octphy header files supports ulPllFractionalFreqHz in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]), + [], + [#include ]) + + AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.ulSlipCnt], + AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SLIP_CNT], + [1], + [Define to 1 if your octphy header files supports ulSlipCnt in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]), + [], + [#include ]) + + AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.ulSyncLosseCnt], + AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SYNC_LOSSE_CNT], + [1], + [Define to 1 if your octphy header files supports ulSyncLosseCnt in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]), + [], + [#include ]) + + AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.ulSyncLossCnt], + AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SYNC_LOSS_CNT], + [1], + [Define to 1 if your octphy header files supports ulSyncLossCnt in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]), + [], + [#include ]) + + AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.ulSourceState], + AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SOURCE_STATE], + [1], + [Define to 1 if your octphy header files supports ulSourceState in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]), + [], + [#include ]) + + AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.ulDacState], + AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DAC_STATE], + [1], + [Define to 1 if your octphy header files supports ulDacState in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]), + [], + [#include ]) + + AC_CHECK_MEMBER([tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP.ulDriftElapseTimeUs], + AC_DEFINE([OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DRIFT_ELAPSE_TIME_US], + [1], + [Define to 1 if your octphy header files supports ulDriftElapseTimeUs in tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP]), + [], + [#include ]) + + CPPFLAGS=$oldCPPFLAGS +fi + +AC_MSG_CHECKING([whether to enable NuRAN Wireless Litecell 1.5 hardware support]) +AC_ARG_ENABLE(litecell15, + AC_HELP_STRING([--enable-litecell15], + [enable code for NuRAN Wireless Litecell15 bts [default=no]]), + [enable_litecell15="yes"],[enable_litecell15="no"]) +AC_ARG_WITH([litecell15], [AS_HELP_STRING([--with-litecell15=INCLUDE_DIR], [Location of the litecell 1.5 API header files])], + [litecell15_incdir="$withval"],[litecell15_incdir="$incdir"]) +AC_SUBST([LITECELL15_INCDIR], -I$litecell15_incdir) +AC_MSG_RESULT([$enable_litecell15]) +AM_CONDITIONAL(ENABLE_LC15BTS, test "x$enable_litecell15" = "xyes") +if test "$enable_litecell15" = "yes"; then + oldCPPFLAGS=$CPPFLAGS + CPPFLAGS="$CPPFLAGS $LITECELL15_INCDIR -I$srcdir/include" + AC_CHECK_HEADER([nrw/litecell15/litecell15.h],[], + [AC_MSG_ERROR([nrw/litecell15/litecell15.h can not be found in $litecell15_incdir])], + [#include ]) + PKG_CHECK_MODULES(LIBSYSTEMD, libsystemd) + CPPFLAGS=$oldCPPFLAGS +fi + +AC_MSG_RESULT([CFLAGS="$CFLAGS"]) +AC_MSG_RESULT([CPPFLAGS="$CPPFLAGS"]) + +AM_CONFIG_HEADER(btsconfig.h) + +AC_OUTPUT( + src/Makefile + src/common/Makefile + src/osmo-bts-virtual/Makefile + src/osmo-bts-omldummy/Makefile + src/osmo-bts-sysmo/Makefile + src/osmo-bts-litecell15/Makefile + src/osmo-bts-trx/Makefile + src/osmo-bts-octphy/Makefile + include/Makefile + include/osmo-bts/Makefile + tests/Makefile + tests/paging/Makefile + tests/agch/Makefile + tests/cipher/Makefile + tests/sysmobts/Makefile + tests/misc/Makefile + tests/handover/Makefile + tests/tx_power/Makefile + tests/power/Makefile + tests/meas/Makefile + Makefile) diff --git a/contrib/dtx_check.gawk b/contrib/dtx_check.gawk new file mode 100755 index 0000000..9a3ddcf --- /dev/null +++ b/contrib/dtx_check.gawk @@ -0,0 +1,89 @@ +#!/usr/bin/gawk -f + +# Expected input format: FN TYPE + +BEGIN { + DELTA = 0 + ERR = 0 + FORCE = 0 + FN = 0 + SILENCE = 0 + TYPE = "" + CHK = "" + U_MAX = 8 * 20 + 120 / 26 + U_MIN = 8 * 20 - 120 / 26 + F_MAX = 3 * 20 + 120 / 26 + F_MIN = 3 * 20 - 120 / 26 +} + +{ + if (NR > 2) { # we have data from previous record to compare to + DELTA = ($1 - FN) * 120 / 26 + CHK = "OK" + if ("FACCH" == $2 && "ONSET" == TYPE) { # ONSET due to FACCH is NOT a talkspurt + SILENCE = 1 + } + if (("UPDATE" == TYPE || "FIRST" == TYPE) && ("FACCH" == $2 || "SPEECH" == $2)) { # check for missing ONSET: + CHK = "FAIL: missing ONSET (" $2 ") after " TYPE "." + ERR++ + } + if ("SID_P1" == $2) { + CHK = "FAIL: regular AMR payload with FT SID and STI=0 (should be either pyaload Update or STI=1)." + ERR++ + } + if ("FORCED_FIRST" == $2 || "FORCED_NODATA" == $2 || "FORCED_F_P2" == $2 || "FORCED_F_INH" == $2 || "FORCED_U_INH" == $2) { + CHK = "FAIL: event " $2 " inserted by DSP." + FORCE++ + ERR++ + } + if ("FIRST_P2" != $2 && "FIRST_P1" == TYPE) { + CHK = "FAIL: " TYPE " followed by " $2 " instead of P2." + ERR++ + } + if ("FIRST" == $2 && "FIRST" == TYPE) { + CHK = "FAIL: multiple SID FIRST in a row." + ERR++ + } + if ("OK" == CHK && "ONSET" != $2) { # check inter-SID distances: + if ("UPDATE" == TYPE) { + if (DELTA > U_MAX) { + CHK = "FAIL: delta (" $1 - FN "fn) from previous SID UPDATE (@" FN ") too big " DELTA "ms > " U_MAX "ms." + ERR++ + } + if ("UPDATE" == $2 && DELTA < U_MIN) { + CHK = "FAIL: delta (" $1 - FN "fn) from previous SID UPDATE (@" FN ") too small " DELTA "ms < " U_MIN "ms." + ERR++ + } + } + if ("FIRST" == TYPE) { + if (DELTA > F_MAX) { + CHK = "FAIL: delta (" $1 - FN "fn) from previous SID FIRST (@" FN ") too big " DELTA "ms > " F_MAX "ms." + ERR++ + } + if ("UPDATE" == $2 && DELTA < F_MIN) { + CHK = "FAIL: delta (" $1 - FN "fn) from previous SID UPDATE (@" FN ") too small " DELTA "ms < " F_MIN "ms." + ERR++ + } + } + } + if ("FACCH" == TYPE && "FIRST" != $2 && "FACCH" != $2 && 1 == SILENCE) { # check FACCH handling + CHK = "FAIL: incorrect silence resume with " $2 " after FACCH." + ERR++ + } + } + if ("SPEECH" == $2 || "ONSET" == $2) { # talkspurt + SILENCE = 0 + } + if ("UPDATE" == $2 || "FIRST" == $2) { # silence + SILENCE = 1 + } + print $1, $2, CHK + if ($2 != "EMPTY") { # skip over EMPTY records + TYPE = $2 + FN = $1 + } +} + +END { + print "Check completed: found " ERR " errors (" FORCE " events inserted by DSP) in " NR " records." +} diff --git a/contrib/dump_docs.py b/contrib/dump_docs.py new file mode 100755 index 0000000..59f2a61 --- /dev/null +++ b/contrib/dump_docs.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python + +""" +Start the process and dump the documentation to the doc dir +""" + +import socket, subprocess, time,os + +env = os.environ +env['L1FWD_BTS_HOST'] = '127.0.0.1' + +bts_proc = subprocess.Popen(["./src/osmo-bts-sysmo/sysmobts-remote", + "-c", "./doc/examples/sysmo/osmo-bts.cfg"], env = env, + stdin=None, stdout=None) +time.sleep(1) + +try: + sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sck.setblocking(1) + sck.connect(("localhost", 4241)) + sck.recv(4096) + + # Now send the command + sck.send("show online-help\r") + xml = "" + while True: + data = sck.recv(4096) + xml = "%s%s" % (xml, data) + if data.endswith('\r\nOsmoBTS> '): + break + + # Now write everything until the end to the file + out = open('doc/vty_reference.xml', 'w') + out.write(xml[18:-11]) + out.close() +finally: + # Clean-up + bts_proc.kill() + bts_proc.wait() + diff --git a/contrib/eeprom_reader.c b/contrib/eeprom_reader.c new file mode 100644 index 0000000..d0d4136 --- /dev/null +++ b/contrib/eeprom_reader.c @@ -0,0 +1,91 @@ +/* GPLv3+ to read sysmobts-v2 revD or later EEPROM from userspace */ + + +#include +#include + +#include +#include +#include + + +#include +#include +#include + +#include +#include +#include +#include + + +/* Can read a 16bit at24 eeprom with 8192 byte in storage (24c64) */ +static int dump_eeprom(int fd, int out) +{ +#define STEP 8192 +#define SIZE 8192 + uint8_t buf[STEP + 2]; + int rc = 0; + int i; + + for (i = 0; i < SIZE; i += STEP) { + /* write the address */ + buf[0] = i >> 8; + buf[1] = i; + rc = write(fd, buf, 2); + if (rc != 2) { + fprintf(stderr, "writing address failed: %d/%d/%s\n", rc, errno, strerror(errno)); + return 1; + } + + /* execute step amount of reads */ + rc = read(fd, buf, STEP); + if (rc != STEP) { + fprintf(stderr, "Failed to read: %d/%d/%s\n", rc, errno, strerror(errno)); + return 1; + } + + write(out, buf, STEP); + } + return 0; +} + +int main(int argc, char **argv) +{ + int i2c_fd, out_fd; + char *filename = "/dev/i2c-1"; + char *out_file = "eeprom.out"; + int addr = 0x50; + int rc; + + i2c_fd = open(filename, O_RDWR); + if (i2c_fd < 0) { + fprintf(stderr, "Failed to open i2c device %d/%d/%s\n", + i2c_fd, errno, strerror(errno)); + return EXIT_FAILURE; + } + + /* force using that address it is already bound with a driver */ + rc = ioctl(i2c_fd, I2C_SLAVE_FORCE, addr); + if (rc < 0) { + fprintf(stderr, "Failed to claim i2c device %d/%d/%s\n", + rc, errno, strerror(errno)); + return EXIT_FAILURE; + } + + if (argc >= 2) + out_file = argv[1]; + out_fd = open(out_file, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (out_fd < 0) { + fprintf(stderr, "Failed to open out device %s %d/%d/%s\n", + out_file, rc, errno, strerror(errno)); + return EXIT_FAILURE; + } + + if (dump_eeprom(i2c_fd, out_fd) != 0) { + unlink(out_file); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/contrib/jenkins_bts_model.sh b/contrib/jenkins_bts_model.sh new file mode 100755 index 0000000..2488f71 --- /dev/null +++ b/contrib/jenkins_bts_model.sh @@ -0,0 +1,45 @@ +#!/bin/sh +# this is a dispatcher script which will call the bts-model-specific +# script based on the bts model specified as command line argument + +bts_model="$1" + +if [ "x$bts_model" = "x" ]; then + echo "Error: You have to specify the BTS model as first argument, e.g. $0 sysmo" + exit 2 +fi + +if [ ! -d "./contrib" ]; then + echo "Run ./contrib/jenkins_bts_model.sh from the root of the osmo-bts tree" + exit 1 +fi + +set -x -e + +case "$bts_model" in + + sysmo) + ./contrib/jenkins_sysmobts.sh + ;; + + oct) + ./contrib/jenkins_oct.sh + ;; + + lc15) + ./contrib/jenkins_lc15.sh + ;; + + trx) + ./contrib/jenkins_bts_trx.sh + ;; + + oct+trx) + ./contrib/jenkins_oct_and_bts_trx.sh + ;; + + *) + set +x + echo "Unknown BTS model '$bts_model'" + ;; +esac diff --git a/contrib/jenkins_bts_trx.sh b/contrib/jenkins_bts_trx.sh new file mode 100755 index 0000000..3e3d8d3 --- /dev/null +++ b/contrib/jenkins_bts_trx.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# jenkins build helper script for osmo-bts-trx + +# shellcheck source=contrib/jenkins_common.sh +. $(dirname "$0")/jenkins_common.sh + +export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH" +export LD_LIBRARY_PATH="$inst/lib" + +osmo-build-dep.sh libosmocore "" --disable-doxygen + +osmo-build-dep.sh libosmo-abis + +cd "$deps" + +configure_flags="\ + --with-osmo-pcu=$deps/osmo-pcu/include \ + --enable-trx \ + --enable-sanitize \ + " + +build_bts "osmo-bts-trx" "$configure_flags" + +osmo-clean-workspace.sh diff --git a/contrib/jenkins_common.sh b/contrib/jenkins_common.sh new file mode 100644 index 0000000..bdb12d5 --- /dev/null +++ b/contrib/jenkins_common.sh @@ -0,0 +1,47 @@ +#!/bin/sh + +# this is a common helper script that is shared among all BTS model +# specific helper scripts like jenkins_sysmobts.sh. You shouldn't call +# this directly, but rather indirectly via the bts-specific scripts + +if ! [ -x "$(command -v osmo-deps.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 -p "$deps" + +verify_value_string_arrays_are_terminated.py $(find . -name "*.[hc]") + +# generic project build function, usage: +# build "PROJECT-NAME" "CONFIGURE OPTIONS" +build_bts() { + set +x + echo + echo + echo + echo " =============================== $1 ===============================" + echo + set -x + + cd $deps + osmo-deps.sh libosmocore + cd $base + shift + conf_flags="$*" + autoreconf --install --force + ./configure $conf_flags + $MAKE $PARALLEL_MAKE + $MAKE check || cat-testlogs.sh + DISTCHECK_CONFIGURE_FLAGS="$conf_flags" $MAKE distcheck || cat-testlogs.sh +} diff --git a/contrib/jenkins_lc15.sh b/contrib/jenkins_lc15.sh new file mode 100755 index 0000000..38c1f33 --- /dev/null +++ b/contrib/jenkins_lc15.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# jenkins build helper script for osmo-bts-lc15 + +# shellcheck source=contrib/jenkins_common.sh +. $(dirname "$0")/jenkins_common.sh + +osmo-build-dep.sh libosmocore "" --disable-doxygen + +export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH" +export LD_LIBRARY_PATH="$inst/lib" + +osmo-build-dep.sh libosmo-abis + +cd "$deps" +osmo-layer1-headers.sh lc15 "$FIRMWARE_VERSION" + +configure_flags="--enable-sanitize --with-litecell15=$deps/layer1-headers/inc/ --enable-litecell15" + +build_bts "osmo-bts-lc15" "$configure_flags" + +osmo-clean-workspace.sh diff --git a/contrib/jenkins_oct.sh b/contrib/jenkins_oct.sh new file mode 100755 index 0000000..efbd368 --- /dev/null +++ b/contrib/jenkins_oct.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# jenkins build helper script for osmo-bts-octphy + +# shellcheck source=contrib/jenkins_common.sh +. $(dirname "$0")/jenkins_common.sh + +osmo-build-dep.sh libosmocore "" --disable-doxygen + +export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH" +export LD_LIBRARY_PATH="$inst/lib" + +osmo-build-dep.sh libosmo-abis + +cd "$deps" +osmo-layer1-headers.sh oct "$FIRMWARE_VERSION" + +configure_flags="--enable-sanitize --with-octsdr-2g=$deps/layer1-headers/ --enable-octphy" + +build_bts "osmo-bts-octphy" "$configure_flags" + +osmo-clean-workspace.sh diff --git a/contrib/jenkins_oct_and_bts_trx.sh b/contrib/jenkins_oct_and_bts_trx.sh new file mode 100755 index 0000000..f68a9d3 --- /dev/null +++ b/contrib/jenkins_oct_and_bts_trx.sh @@ -0,0 +1,27 @@ +#!/bin/sh +# jenkins build helper script for osmo-bts-octphy + osmo-bts-trx + +# shellcheck source=contrib/jenkins_common.sh +. $(dirname "$0")/jenkins_common.sh + +export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH" +export LD_LIBRARY_PATH="$inst/lib" + +osmo-build-dep.sh libosmocore "" --disable-doxygen + +osmo-build-dep.sh libosmo-abis + +cd "$deps" + +osmo-layer1-headers.sh oct "$FIRMWARE_VERSION" + +configure_flags="\ + --with-osmo-pcu=$deps/osmo-pcu/include \ + --with-octsdr-2g=$deps/layer1-headers/ \ + --enable-octphy \ + --enable-trx \ + " + +build_bts "osmo-bts-octphy+trx" "$configure_flags" + +osmo-clean-workspace.sh diff --git a/contrib/jenkins_sysmobts.sh b/contrib/jenkins_sysmobts.sh new file mode 100755 index 0000000..7488419 --- /dev/null +++ b/contrib/jenkins_sysmobts.sh @@ -0,0 +1,28 @@ +#!/bin/sh +# jenkins build helper script for osmo-bts-sysmo + +# shellcheck source=contrib/jenkins_common.sh +. $(dirname "$0")/jenkins_common.sh + +osmo-build-dep.sh libosmocore "" --disable-doxygen + +export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH" +export LD_LIBRARY_PATH="$inst/lib" + +osmo-build-dep.sh libosmo-abis + +cd "$deps" +osmo-layer1-headers.sh sysmo "$FIRMWARE_VERSION" +mkdir -p "$inst/include/sysmocom/femtobts" +ln -s $deps/layer1-headers/include/* "$inst/include/sysmocom/femtobts/" + +configure_flags="--enable-sanitize --enable-sysmocom-bts --with-sysmobts=$inst/include/" + +# This will not work for the femtobts +if [ $FIRMWARE_VERSION != "femtobts_v2.7" ]; then + configure_flags="$configure_flags --enable-sysmobts-calib" +fi + +build_bts "osmo-bts-sysmo" "$configure_flags" + +osmo-clean-workspace.sh diff --git a/contrib/l1fwd.init b/contrib/l1fwd.init new file mode 100755 index 0000000..b228580 --- /dev/null +++ b/contrib/l1fwd.init @@ -0,0 +1,31 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: l1fwd +# Required-Start: +# Required-Stop: $local_fs +# Default-Start: 5 +# Default-Stop: 0 6 +# Short-Description: Start screen session with l1fwd software +# Description: +### END INIT INFO + +. /etc/default/rcS + +case "$1" in + start) + /usr/bin/screen -d -m -c /etc/osmocom/screenrc-l1fwd + ;; + stop) + echo "This script doesn't support stop" + exit 1 + ;; + restart|reload|force-reload) + exit 0 + ;; + show) + ;; + *) + echo "Usage: sysmobts {start|stop|show|reload|restart}" >&2 + exit 1 + ;; +esac diff --git a/contrib/lc15bts-mgr.service b/contrib/lc15bts-mgr.service new file mode 100644 index 0000000..bf788e6 --- /dev/null +++ b/contrib/lc15bts-mgr.service @@ -0,0 +1,29 @@ +[Unit] +Description=osmo-bts manager for LC15 / sysmoBTS 2100 +After=lc15-sysdev-remap.service +Wants=lc15-sysdev-remap.service + +[Service] +Type=simple +NotifyAccess=all +WatchdogSec=21780s +Restart=always +RestartSec=2 + +# Make sure directories and symbolic link exist +ExecStartPre=/bin/sh -c 'test -d /mnt/storage/var/run/lc15bts-mgr || mkdir -p /mnt/storage/var/run/lc15bts-mgr ; test -d /var/run/lc15bts-mgr || ln -sf /mnt/storage/var/run/lc15bts-mgr/ /var/run' +# Make sure BTS operation hour exist +ExecStartPre=/bin/sh -c 'test -f /mnt/storage/var/run/lc15bts-mgr/hours-running || echo 0 > /mnt/storage/var/run/lc15bts-mgr/hours-running' +# Shutdown all PA correctly +ExecStartPre=/bin/sh -c 'echo disabled > /var/lc15/pa-state/pa0/state; echo disabled > /var/lc15/pa-state/pa1/state' +ExecStartPre=/bin/sh -c 'echo 0 > /var/lc15/pa-supply/max_microvolts; echo 0 > /var/lc15/pa-supply/min_microvolts' + +ExecStart=/usr/bin/lc15bts-mgr -s -c /etc/osmocom/lc15bts-mgr.cfg + +# Shutdown all PA correctly +ExecStopPost=/bin/sh -c 'echo disabled > /var/lc15/pa-state/pa0/state; echo disabled > /var/lc15/pa-state/pa1/state' +ExecStopPost=/bin/sh -c 'echo 0 > /var/lc15/pa-supply/max_microvolts; echo 0 > /var/lc15/pa-supply/min_microvolts' + +[Install] +WantedBy=multi-user.target +Alias=osmo-bts-mgr.service diff --git a/contrib/osmo-bts-lc15.service b/contrib/osmo-bts-lc15.service new file mode 100644 index 0000000..6aa9751 --- /dev/null +++ b/contrib/osmo-bts-lc15.service @@ -0,0 +1,21 @@ +[Unit] +Description=osmo-bts for LC15 / sysmoBTS 2100 + +[Service] +Type=simple +ExecStartPre=/bin/sh -c 'echo 1 > /sys/class/leds/usr0/brightness' +ExecStartPre=/bin/sh -c 'echo 1 > /sys/class/leds/usr1/brightness' +ExecStart=/usr/bin/osmo-bts-lc15 -t 2 -s -c /etc/osmocom/osmo-bts.cfg -M +ExecStopPost=/bin/sh -c 'echo 1 > /sys/class/leds/usr0/brightness' +ExecStopPost=/bin/sh -c 'echo 0 > /sys/class/leds/usr1/brightness' +Restart=always +RestartSec=2 +RestartPreventExitStatus=1 + +# The msg queues must be read fast enough +CPUSchedulingPolicy=rr +CPUSchedulingPriority=1 + +[Install] +WantedBy=multi-user.target +Alias=osmo-bts.service diff --git a/contrib/osmo-bts-sysmo.service b/contrib/osmo-bts-sysmo.service new file mode 100644 index 0000000..65b1f00 --- /dev/null +++ b/contrib/osmo-bts-sysmo.service @@ -0,0 +1,21 @@ +[Unit] +Description=osmo-bts for sysmocom sysmoBTS + +[Service] +Type=simple +ExecStartPre=/bin/sh -c 'echo 0 > /sys/class/leds/activity_led/brightness' +ExecStart=/usr/bin/osmo-bts-sysmo -s -c /etc/osmocom/osmo-bts.cfg -M +ExecStopPost=/bin/sh -c 'echo 0 > /sys/class/leds/activity_led/brightness' +ExecStopPost=/bin/sh -c 'cat /lib/firmware/sysmobts-v?.bit > /dev/fpgadl_par0 ; sleep 3s; cat /lib/firmware/sysmobts-v?.out > /dev/dspdl_dm644x_0; sleep 1s' +Restart=always +RestartSec=2 +RestartPreventExitStatus=1 + +# The msg queues must be read fast enough +CPUSchedulingPolicy=rr +CPUSchedulingPriority=1 + +[Install] +WantedBy=multi-user.target +Alias=sysmobts.service +Alias=osmo-bts.service diff --git a/contrib/respawn-only.sh b/contrib/respawn-only.sh new file mode 100755 index 0000000..478abd6 --- /dev/null +++ b/contrib/respawn-only.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +PID=$$ +echo "-1000" > /proc/$PID/oom_score_adj + +trap "{ kill 0; kill -2 0; }" EXIT + +while [ -f $1 ]; do + (echo "0" > /proc/self/oom_score_adj && exec nice -n -20 $*) & + LAST_PID=$! + wait $LAST_PID + sleep 10s +done diff --git a/contrib/respawn.sh b/contrib/respawn.sh new file mode 100755 index 0000000..196edad --- /dev/null +++ b/contrib/respawn.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +PID=$$ +echo "-1000" > /proc/$PID/oom_score_adj + +trap "kill 0" EXIT + +while [ -e /etc/passwd ]; do + cat /lib/firmware/sysmobts-v?.bit > /dev/fpgadl_par0 + sleep 2s + cat /lib/firmware/sysmobts-v?.out > /dev/dspdl_dm644x_0 + sleep 1s + echo "0" > /sys/class/leds/activity_led/brightness + (echo "0" > /proc/self/oom_score_adj && exec nice -n -20 $*) & + LAST_PID=$! + wait $LAST_PID + sleep 10s +done diff --git a/contrib/screenrc-l1fwd b/contrib/screenrc-l1fwd new file mode 100644 index 0000000..4256a38 --- /dev/null +++ b/contrib/screenrc-l1fwd @@ -0,0 +1,3 @@ +chdir /tmp +screen -t BTS 0 /etc/osmocom/respawn.sh /usr/bin/l1fwd-proxy +detach diff --git a/contrib/screenrc-sysmobts b/contrib/screenrc-sysmobts new file mode 100644 index 0000000..9c810d9 --- /dev/null +++ b/contrib/screenrc-sysmobts @@ -0,0 +1,5 @@ +chdir /tmp +screen -t BTS 0 /etc/osmocom/respawn.sh /usr/bin/osmo-bts-sysmo -c /etc/osmocom/osmo-bts.cfg -r 1 -M +screen -t PCU 1 /etc/osmocom/respawn-only.sh /usr/bin/osmo-pcu -c /etc/osmocom/osmo-pcu.cfg -e +screen -t MGR 2 /etc/osmocom/respawn-only.sh /usr/bin/sysmobts-mgr -n -c /etc/osmocom/sysmobts-mgr.cfg +detach diff --git a/contrib/si_check.gawk b/contrib/si_check.gawk new file mode 100755 index 0000000..0a54ed4 --- /dev/null +++ b/contrib/si_check.gawk @@ -0,0 +1,91 @@ +#!/usr/bin/gawk -f + +# Usage example: +# tshark -2 -t r -E 'header=n' -E 'separator=,' -E 'quote=n' -T fields -e gsmtap.frame_nr -e gsmtap.ts -e gsmtap.arfcn -e _ws.col.Info -Y 'gsmtap' -r test.pcapng.gz | grep Information | env ARFCN=878 ./si_check.gawk +# read summary on number of bis/ter messages and adjust BT_BOTH and BT_NONE environment variables accordingly + +BEGIN { + FS = "," + FAILED = 0 + IGNORE = 0 + BIS = 0 + TER = 0 + QUA = 0 + BT_BOTH = ENVIRON["BOTH"] + BT_NONE = ENVIRON["NONE"] + TC_INDEX = 0 + TC4[4] = 0 +} + +{ # expected .csv input as follows: gsmtap.frame_nr,gsmtap.ts,gsmtap.arfcn,_ws.col.Info + if ("ARFCN" in ENVIRON) { # ARFCN filtering is enabled + if (ENVIRON["ARFCN"] != $3) { # ignore other ARFCNs + IGNORE++ + next + } + } + type = get_si_type($4) + tc = get_tc($1) + result = "FAIL" + + if (1 == check_si_tc(tc, type)) { result = "OK" } + else { FAILED++ } + + if (4 == tc) { + TC4[TC_INDEX] = type + TC_INDEX = int((TC_INDEX + 1) % 4) + if (0 == check_tc4c(type) && "OK" == result) { + result = "FAIL" + FAILED++ + } + } + if (type == "2bis") { BIS++ } + if (type == "2ter") { TER++ } + if (type == "2quater") { QUA++ } + # for (i in TC4) print TC4[i] # debugging + printf "ARFCN=%d FN=%d TS=%d TC=%d TYPE=%s %s\n", $3, $1, $2, tc, type, result +} + +END { + printf "check completed: total %d, failed %d, ignored %d, ok %d\nSI2bis = %d, SI2ter = %d, SI2quater = %d\n", NR, FAILED, IGNORE, NR - FAILED - IGNORE, BIS, TER, QUA + if ((BIS > 0 || TER > 0) && BT_NONE) { printf "please re-run with correct environment: unset 'NONE' variable\n" } + if ((BIS > 0 && TER > 0) && !BT_BOTH) { printf "please re-run with correct environment: set 'BOTH' variable\n" } +} + +func get_si_type(s, x) { # we rely on format of Info column in wireshark output - if it's changed we're screwed + return x[split(s, x, " ")] +} + +func get_tc(f) { # N. B: all numbers in awk are float + return int(int(f / 51) % 8) +} + +func check_tc4c(si, count) { # check for "once in 4 consecutive occurrences" rule + count = 0 + if ("2quater" != si || "2ter" != si) { return 1 } # rules is not applicable to other types + if (BT_NONE && "2quater" == si) { return 0 } # should be on TC=5 instead + if (!BT_BOTH && "2ter" == si) { return 0 } # should be on TC=5 instead + if (0 in TC4 && 1 in TC4 && 2 in TC4 && 3 in TC4) { # only check if we have 4 consecutive occurrences already + if (si == TC4[0]) { count++ } + if (si == TC4[1]) { count++ } + if (si == TC4[2]) { count++ } + if (si == TC4[3]) { count++ } + if (0 == count) { return 0 } + } + return 1 +} + +func check_si_tc(tc, si) { # check that SI scheduling on BCCH Norm is matching rules from 3GPP TS 05.02 § 6.3.1.3 + switch (si) { + case "1": return (0 == tc) ? 1 : 0 + case "2": return (1 == tc) ? 1 : 0 + case "2bis": return (5 == tc) ? 1 : 0 + case "13": return (4 == tc) ? 1 : 0 + case "9": return (4 == tc) ? 1 : 0 + case "2ter": if (BT_BOTH) { return (4 == tc) ? 1 : 0 } else { return (5 == tc) ? 1 : 0 } + case "2quater": if (BT_NONE) { return (5 == tc) ? 1 : 0 } else { return (4 == tc) ? 1 : 0 } + case "3": return (2 == tc || 6 == tc) ? 1 : 0 + case "4": return (3 == tc || 7 == tc) ? 1 : 0 + } + return 0 +} diff --git a/contrib/superfemto.sh b/contrib/superfemto.sh new file mode 100755 index 0000000..19dc410 --- /dev/null +++ b/contrib/superfemto.sh @@ -0,0 +1,110 @@ +#!/bin/sh + +# Split common DSP call log file (produced by superfemto-compatible firmware) into 4 separate call leg files (MO/MT & DL/UL) with events in format "FN EVENT_TYPE": +# MO Mobile Originated +# MT Mobile Terminated +# DL DownLink (BTS -> L1) +# UL UpLink (L1 -> BTS) + +if [ -z $1 ]; then + echo "expecting DSP log file name as parameter" + exit 1 +fi + +# MO handle appear 1st in the logs +MO=$(grep 'h=' $1 | head -n 1 | cut -f2 -d',' | cut -f2 -d= | cut -f1 -d']') + +# direction markers: +DLST="_CodeBurst" +ULST="_DecodeAndIdentify" + +# DL sed filters: +D_EMP='s/ Empty frame request!/EMPTY/i' +D_FAC='s/ Coding a FACCH\/. frame !!/FACCH/i' +D_FST='s/ Coding a RTP SID First frame !!/FIRST/i' +D_FS1='s/ Coding a SID First P1 frame !!/FIRST_P1/i' +D_FS2='s/ Coding a SID First P2 frame !!/FIRST_P2/i' +D_RP1='s/ Coding a RTP SID P1 frame !!/SID_P1/i' +D_UPD='s/ Coding a RTP SID Update frame !!/UPDATE/i' +D_SPE='s/ Coding a RTP Speech frame !!/SPEECH/i' +D_ONS='s/ Coding a Onset frame !!/ONSET/i' +D_FO1='s/ A speech frame is following a NoData or SID First without an Onset./FORCED_FIRST/i' +D_FO2='s/ A speech frame is following a NoData without an Onset./FORCED_NODATA/i' +D_FP2='s/ A speech frame is following a NoData or SID_FIRST_P2 without an Onset./FORCED_F_P2/i' +D_FIN='s/ A speech frame is following a SID_FIRST without inhibit. A SID_FIRST_INH will be inserted./FORCED_F_INH/i' +D_UIN='s/ A speech frame is following a SID_UPDATE without inhibit. A SID_UPDATE_INH will be inserted./FORCED_U_INH/i' + +# UL sed filters: +U_NOD='s/ It is a No Data frame !!/NODATA/i' +U_ONS='s/ It is an ONSET frame !!/ONSET/i' +U_UPD='s/ It is a SID UPDATE frame !!/UPDATE/i' +U_FST='s/ It is a SID FIRST frame !!/FIRST/i' +U_FP1='s/ It is a SID-First P1 frame !!/FIRST_P1/i' +U_FP2='s/ It is a SID-First P2 frame !!/FIRST_P2/i' +U_SPE='s/ It is a SPEECH frame *!!/SPEECH/i' +U_UIN='s/ It is a SID update InH frame !!/UPD_INH/i' +U_FIN='s/ It is a SID-First InH frame !!/FST_INH/i' +U_RAT='s/ It is a RATSCCH data frame !!/RATSCCH/i' + +DL () { # filter downlink-related entries + grep $DLST $1 > $1.DL.tmp +} + +UL () { # uplink does not require special fix + grep $ULST $1 > $1.UL.tmp.fix +} + +DL $1 +UL $1 + +FIX() { # add MO/MT marker from preceding line to inserted ONSETs so filtering works as expected + cat $1.DL.tmp | awk 'BEGIN{ FS=" h="; H="" } { if (NF > 1) { H = $2; print $1 "h=" $2 } else { print $1 ", h=" H } }' > $1.DL.tmp.fix +} + +FIX $1 + +MO() { # filter MO call DL or UL logs + grep "h=$MO" $1.tmp.fix > $1.MO.raw +} + +MT() { # filter MT call DL or UL logs + grep -v "h=$MO" $1.tmp.fix > $1.MT.raw +} + +MO $1.DL +MT $1.DL +MO $1.UL +MT $1.UL + +PREP() { # prepare logs for reformatting + cat $1.raw | cut -f2 -d')' | cut -f1 -d',' | cut -f2 -d'>' | sed 's/\[u32Fn/fn/' | sed 's/\[ u32Fn/fn/' | sed 's/fn = /fn=/' | sed 's/fn=//' | sed 's/\[Fn=//' | sed 's/ An Onset will be inserted.//' > $1.tmp1 +} + +PREP "$1.DL.MT" +PREP "$1.DL.MO" +PREP "$1.UL.MT" +PREP "$1.UL.MO" + +RD() { # reformat DL logs for consistency checks + cat $1.tmp1 | sed "$D_FST" | sed "$D_SPE" | sed "$D_FS1" | sed "$D_FS2" | sed "$D_UIN" | sed "$D_FIN" | sed "$D_UPD" | sed "$D_INH" | sed "$D_RP1" | sed "$D_ONS" | sed "$D_EMP" | sed "$D_FAC" | sed "$D_FO1" | sed "$D_FO2" | sed "$D_FP2" > $1.tmp2 +} + +RU() { # reformat UL logs for consistency checks + cat $1.tmp1 | sed "$U_FST" | sed "$U_SPE" | sed "$U_FP1" | sed "$U_FP2" | sed "$U_UPD" | sed "$U_ONS" | sed "$U_NOD" | sed "$U_UIN" | sed "$U_FIN" | sed "$U_RAT" > $1.tmp2 +} + +RD "$1.DL.MT" +RD "$1.DL.MO" +RU "$1.UL.MT" +RU "$1.UL.MO" + +SW() { # swap fields + cat $1.tmp2 | awk '{ print $2, $1 }' > $1 +} + +SW "$1.DL.MT" +SW "$1.DL.MO" +SW "$1.UL.MT" +SW "$1.UL.MO" + +rm $1.*.tmp* diff --git a/contrib/sysmobts-mgr.service b/contrib/sysmobts-mgr.service new file mode 100644 index 0000000..4346991 --- /dev/null +++ b/contrib/sysmobts-mgr.service @@ -0,0 +1,12 @@ +[Unit] +Description=osmo-bts manager for sysmoBTS + +[Service] +Type=simple +ExecStart=/usr/bin/sysmobts-mgr -ns -c /etc/osmocom/sysmobts-mgr.cfg +Restart=always +RestartSec=2 + +[Install] +WantedBy=multi-user.target +Alias=osmo-bts-mgr.service diff --git a/contrib/sysmobts.init b/contrib/sysmobts.init new file mode 100755 index 0000000..2b9d281 --- /dev/null +++ b/contrib/sysmobts.init @@ -0,0 +1,29 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: sysmobts +# Required-Start: +# Required-Stop: $local_fs +# Default-Start: 5 +# Default-Stop: 0 6 +# Short-Description: Start screen session with sysmobts software +# Description: +### END INIT INFO + +case "$1" in + start) + /usr/bin/screen -d -m -c /etc/osmocom/screenrc-sysmobts -S sysmobts + ;; + stop) + /usr/bin/screen -d -r sysmobts -X quit + exit 1 + ;; + restart|reload|force-reload) + exit 0 + ;; + show) + ;; + *) + echo "Usage: sysmobts {start|stop|show|reload|restart}" >&2 + exit 1 + ;; +esac diff --git a/contrib/sysmobts.service b/contrib/sysmobts.service new file mode 100644 index 0000000..64e0127 --- /dev/null +++ b/contrib/sysmobts.service @@ -0,0 +1,20 @@ +[Unit] +Description=sysmocom sysmoBTS + +[Service] +Type=simple +ExecStartPre=/bin/sh -c 'echo 0 > /sys/class/leds/activity_led/brightness' +ExecStart=/usr/bin/osmo-bts-sysmo -s -c /etc/osmocom/osmo-bts.cfg -M +ExecStopPost=/bin/sh -c 'echo 0 > /sys/class/leds/activity_led/brightness' +ExecStopPost=/bin/sh -c 'cat /lib/firmware/sysmobts-v?.bit > /dev/fpgadl_par0 ; sleep 3s; cat /lib/firmware/sysmobts-v?.out > /dev/dspdl_dm644x_0; sleep 1s' +Restart=always +RestartSec=2 +RestartPreventExitStatus=1 + +# The msg queues must be read fast enough +CPUSchedulingPolicy=rr +CPUSchedulingPriority=1 + +[Install] +WantedBy=multi-user.target +Alias=osmo-bts-sysmo.service diff --git a/doc/control_interface.txt b/doc/control_interface.txt new file mode 100644 index 0000000..5ad9717 --- /dev/null +++ b/doc/control_interface.txt @@ -0,0 +1,61 @@ +The osmo-bts control interface is currently supporting the following operations: + +h2. generic + +h3. trx.0.thermal-attenuation + +The idea of this paramter is to attenuate the system output power as part of +thermal management. In some cases the PA might be passing a critical level, +so an external control process can use this attribute to reduce the system +output power. + +Please note that all values in the context of transmit power calculation +are integers in milli-dB (1/10000 bel), so the below example is setting +the attenuation at 3 dB: + +
+bsc_control.py -d localhost -p 4238 -s trx.0.thermal-attenuation 3000
+Got message: SET_REPLY 1 trx.0.thermal-attenuation 3000
+
+ +
+bsc_control.py -d localhost -p 4238 -g trx.0.thermal-attenuation
+Got message: GET_REPLY 1 trx.0.thermal-attenuation 3000
+
+ + +h2. sysmobts specific + +h3. trx.0.clock-info + +obtain information on the current clock status: + +
+bsc_control.py -d localhost -p 4238 -g trx.0.clock-info
+Got message: GET_REPLY 1 trx.0.clock-info -100,ocxo,0,0,gps
+
+ +which is to be interpreted as: +* current clock correction value is -100 ppb +* current clock source is OCXO +* deviation between clock source and calibration source is 0 ppb +* resolution of clock error measurement is 0 ppt (0 means no result yet) +* current calibration source is GPS + +When this attribute is set, any value passed on is discarded, but the clock +calibration process is re-started. + + +h3. trx.0.clock-correction + +This attribute can get and set the current clock correction value: + +
+bsc_control.py -d localhost -p 4238 -g trx.0.clock-correction
+Got message: GET_REPLY 1 trx.0.clock-correction -100
+
+ +
+bsc_control.py -d localhost -p 4238 -s trx.0.clock-correction -- -99
+Got message: SET_REPLY 1 trx.0.clock-correction success
+
diff --git a/doc/examples/calypso/osmo-bts.cfg b/doc/examples/calypso/osmo-bts.cfg new file mode 100644 index 0000000..fa01953 --- /dev/null +++ b/doc/examples/calypso/osmo-bts.cfg @@ -0,0 +1,38 @@ +! +! OsmoBTS configuration example for CalypsoBTS +! http://osmocom.org/projects/baseband/wiki/CalypsoBTS +!! +! +log stderr + logging color 1 + logging timestamp 0 + logging level rsl notice + logging level oml notice + logging level rll notice + logging level rr notice + logging level meas error + logging level pag error + logging level l1c error + logging level l1p error + logging level dsp error + logging level abis error +! +line vty + no login +! +phy 0 + instance 0 + osmotrx rx-gain 1 + osmotrx ip local 127.0.0.1 + osmotrx ip remote 127.0.0.1 + osmotrx timing-advance-loop + osmotrx ms-power-loop -65 + osmotrx legacy-setbsic +bts 0 + oml remote-ip 127.0.0.1 + ipa unit-id 1801 0 + gsmtap-sapi pdtch + gsmtap-sapi ccch + band 900 + trx 0 + phy 0 instance 0 diff --git a/doc/examples/litecell15/lc15bts-mgr.cfg b/doc/examples/litecell15/lc15bts-mgr.cfg new file mode 100644 index 0000000..e67742c --- /dev/null +++ b/doc/examples/litecell15/lc15bts-mgr.cfg @@ -0,0 +1,43 @@ +! +! lc15bts-mgr (0.3.0.284-a7c2-dirty) configuration saved from vty +!! +! +log stderr + logging filter all 1 + logging color 1 + logging print category 0 + logging timestamp 0 + logging level temp info + logging level fw info + logging level find info + logging level calib info + logging level lglobal notice + logging level llapd notice + logging level linp notice + logging level lmux notice + logging level lmi notice + logging level lmib notice + logging level lsms notice + logging level lctrl notice + logging level lgtp notice +! +line vty + no login +! +lc15bts-mgr + limits supply_volt + threshold warning min 17500 + threshold critical min 19000 + limits tx0_vswr + threshold warning max 1000 + limits tx1_vswr + threshold warning max 1000 + limits supply_pwr + threshold warning max 110 + threshold critical max 120 + limits pa0_pwr + threshold warning max 50 + threshold critical max 60 + limits pa1_pwr + threshold warning max 50 + threshold critical max 60 diff --git a/doc/examples/litecell15/osmo-bts.cfg b/doc/examples/litecell15/osmo-bts.cfg new file mode 100644 index 0000000..f0bafe8 --- /dev/null +++ b/doc/examples/litecell15/osmo-bts.cfg @@ -0,0 +1,43 @@ +! +! OsmoBTS (0.0.1.100-0455-dirty) configuration saved from vty +!! +! +log stderr + logging color 1 + logging timestamp 0 + logging level rsl info + logging level oml info + logging level rll notice + logging level rr notice + logging level meas notice + logging level pag info + logging level l1c info + logging level l1p info + logging level dsp debug + logging level abis notice + logging level rtp notice + logging level lglobal notice + logging level llapd notice + logging level linp notice + logging level lmux notice + logging level lmi notice + logging level lmib notice + logging level lsms notice +! +line vty + no login +! +phy 0 + instance 0 + trx-calibration-path /mnt/rom/factory/calib +phy 1 + instance 0 + trx-calibration-path /mnt/rom/factory/calib +bts 0 + band 900 + ipa unit-id 1500 0 + oml remote-ip 192.168.234.185 + trx 0 + phy 0 instance 0 + trx 1 + phy 1 instance 0 diff --git a/doc/examples/octphy/osmo-bts-trx2dsp1.cfg b/doc/examples/octphy/osmo-bts-trx2dsp1.cfg new file mode 100644 index 0000000..3406e07 --- /dev/null +++ b/doc/examples/octphy/osmo-bts-trx2dsp1.cfg @@ -0,0 +1,34 @@ +! +! OsmoBTS () configuration saved from vty +!! +! +log stderr + logging color 1 + logging timestamp 0 + logging level rsl info + logging level oml info + logging level rll notice + logging level rr notice + logging level meas notice + logging level pag info + logging level l1c info + logging level l1p info + logging level dsp info + logging level abis notice +! +line vty + no login +! +phy 0 + octphy hw-addr 00:0c:de:ad:fa:ce + octphy net-device eth2 + instance 0 + instance 1 +bts 0 + band 1800 + ipa unit-id 1234 0 + oml remote-ip 127.0.0.1 + trx 0 + phy 0 instance 0 + trx 1 + phy 0 instance 1 diff --git a/doc/examples/octphy/osmo-bts.cfg b/doc/examples/octphy/osmo-bts.cfg new file mode 100644 index 0000000..c729d1a --- /dev/null +++ b/doc/examples/octphy/osmo-bts.cfg @@ -0,0 +1,31 @@ +! +! OsmoBTS () configuration saved from vty +!! +! +log stderr + logging color 1 + logging timestamp 0 + logging level rsl info + logging level oml info + logging level rll notice + logging level rr notice + logging level meas notice + logging level pag info + logging level l1c info + logging level l1p info + logging level dsp info + logging level abis notice +! +line vty + no login +! +phy 0 + octphy hw-addr 00:0C:90:2e:80:1e + octphy net-device eth0.2342 + instance 0 +bts 0 + band 1800 + ipa unit-id 1234 0 + oml remote-ip 127.0.0.1 + trx 0 + phy 0 instance 0 diff --git a/doc/examples/sysmo/osmo-bts.cfg b/doc/examples/sysmo/osmo-bts.cfg new file mode 100644 index 0000000..87dfae0 --- /dev/null +++ b/doc/examples/sysmo/osmo-bts.cfg @@ -0,0 +1,29 @@ +! +! OsmoBTS () configuration saved from vty +!! +! +log stderr + logging color 1 + logging timestamp 0 + logging level rsl info + logging level oml info + logging level rll notice + logging level rr notice + logging level meas notice + logging level pag info + logging level l1c info + logging level l1p info + logging level dsp info + logging level abis notice +! +line vty + no login +! +phy 0 + instance 0 +bts 0 + band 1800 + ipa unit-id 666 0 + oml remote-ip 10.1.2.3 + trx 0 + phy 0 instance 0 diff --git a/doc/examples/sysmo/sysmobts-mgr.cfg b/doc/examples/sysmo/sysmobts-mgr.cfg new file mode 100644 index 0000000..0cb1d8c --- /dev/null +++ b/doc/examples/sysmo/sysmobts-mgr.cfg @@ -0,0 +1,23 @@ +! +! SysmoMgr (0.3.0.141-33e5) configuration saved from vty +!! +! +log stderr + logging filter all 1 + logging color 1 + logging timestamp 0 + logging level temp info + logging level fw info + logging level find info + logging level lglobal notice + logging level llapd notice + logging level linp notice + logging level lmux notice + logging level lmi notice + logging level lmib notice + logging level lsms notice +! +line vty + no login +! +sysmobts-mgr diff --git a/doc/examples/trx/osmo-bts.cfg b/doc/examples/trx/osmo-bts.cfg new file mode 100644 index 0000000..805d929 --- /dev/null +++ b/doc/examples/trx/osmo-bts.cfg @@ -0,0 +1,34 @@ +! +! OsmoBTS () configuration saved from vty +!! +! +log stderr + logging color 1 + logging timestamp 0 + logging level rsl notice + logging level oml notice + logging level rll notice + logging level rr notice + logging level meas error + logging level pag error + logging level l1c error + logging level l1p error + logging level dsp error + logging level abis error +! +line vty + no login +! +phy 0 + instance 0 + osmotrx rx-gain 1 + osmotrx ip local 127.0.0.1 + osmotrx ip remote 127.0.0.1 +bts 0 + band 1800 + ipa unit-id 6969 0 + oml remote-ip 192.168.122.1 + gsmtap-sapi ccch + gsmtap-sapi pdtch + trx 0 + phy 0 instance 0 diff --git a/doc/examples/virtual/openbsc-virtual.cfg b/doc/examples/virtual/openbsc-virtual.cfg new file mode 100644 index 0000000..8044fa8 --- /dev/null +++ b/doc/examples/virtual/openbsc-virtual.cfg @@ -0,0 +1,151 @@ +! +! OpenBSC (0.15.0.629-34f0-dirty) configuration saved from vty +!! +! +log stderr + logging filter all 1 + logging color 0 + logging print category 1 + logging timestamp 1 + logging level all info + logging level rll notice + logging level cc notice + logging level mm debug + logging level rr notice + logging level rsl notice + logging level nm info + logging level mncc notice + logging level pag notice + logging level meas notice + logging level sccp notice + logging level msc notice + logging level mgcp notice + logging level ho notice + logging level db notice + logging level ref notice + logging level gprs debug + logging level ns info + logging level bssgp debug + logging level llc debug + logging level sndcp debug + logging level nat notice + logging level ctrl notice + logging level smpp debug + logging level filter debug + logging level ranap debug + logging level sua debug + logging level lglobal notice + logging level llapd notice + logging level linp notice + logging level lmux notice + logging level lmi notice + logging level lmib notice + logging level lsms notice + logging level lctrl notice + logging level lgtp notice + logging level lstats notice + logging level lgsup notice + logging level loap notice +! +stats interval 5 +! +line vty + no login +! +e1_input + e1_line 0 driver ipa + e1_line 0 port 0 + no e1_line 0 keepalive +network + network country code 262 + mobile network code 42 + short name OpenBSC + long name OpenBSC + auth policy accept-all + authorized-regexp .* + location updating reject cause 13 + encryption a5 0 + neci 1 + paging any use tch 0 + rrlp mode ms-based + mm info 1 + handover 0 + handover window rxlev averaging 10 + handover window rxqual averaging 1 + handover window rxlev neighbor averaging 10 + handover power budget interval 6 + handover power budget hysteresis 3 + handover maximum distance 9999 + timer t3101 10 + timer t3103 0 + timer t3105 0 + timer t3107 0 + timer t3109 4 + timer t3111 0 + timer t3113 60 + timer t3115 0 + timer t3117 0 + timer t3119 0 + timer t3122 10 + timer t3141 0 + subscriber-keep-in-ram 0 + bts 0 + type sysmobts + band DCS1800 + cell_identity 6969 + location_area_code 1 + base_station_id_code 63 + ms max power 0 + cell reselection hysteresis 4 + rxlev access min 0 + periodic location update 30 + radio-link-timeout 32 + channel allocator descending + 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 + ip.access unit_id 6969 0 + oml ip.access stream_id 255 line 0 + neighbor-list mode automatic + codec-support fr + gprs mode none + no force-combined-si + trx 0 + rf_locked 0 + arfcn 666 + nominal power 0 + max_power_red 0 + rsl e1 tei 0 + timeslot 0 + phys_chan_config CCCH+SDCCH4 + hopping enabled 0 + timeslot 1 + phys_chan_config SDCCH8 + 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 +mncc-int + default-codec tch-f fr + default-codec tch-h hr +nitb + subscriber-create-on-demand + subscriber-create-on-demand random 1 24 + assign-tmsi diff --git a/doc/examples/virtual/osmobts-virtual.cfg b/doc/examples/virtual/osmobts-virtual.cfg new file mode 100644 index 0000000..fa22316 --- /dev/null +++ b/doc/examples/virtual/osmobts-virtual.cfg @@ -0,0 +1,61 @@ +! +! OsmoBTS (0.4.0.216-bc49-dirty) configuration saved from vty +!! +! +log stderr + logging filter all 0 + logging color 0 + logging print category 1 + logging timestamp 0 + logging level rsl info + logging level oml info + logging level rll notice + logging level rr notice + logging level meas notice + logging level pag info + logging level l1c info + logging level l1p info + logging level dsp error + logging level pcu notice + logging level ho debug + logging level trx notice + logging level loop notice + logging level abis debug + logging level rtp notice + logging level sum error + logging level lglobal notice + logging level llapd notice + logging level linp notice + logging level lmux notice + logging level lmi notice + logging level lmib notice + logging level lsms notice + logging level lctrl notice + logging level lgtp notice + logging level lstats error +! +line vty + no login +! +e1_input + e1_line 0 driver ipa + e1_line 0 port 0 + no e1_line 0 keepalive +phy 0 + instance 0 +bts 0 + band DCS1800 + ipa unit-id 6969 0 + oml remote-ip 127.0.0.1 + rtp jitter-buffer 100 + paging queue-size 200 + paging lifetime 0 + uplink-power-target -75 + min-qual-rach 50 + min-qual-norm -5 + trx 0 + power-ramp max-initial 23000 mdBm + power-ramp step-size 2000 mdB + power-ramp step-interval 1 + ms-power-control dsp + phy 0 instance 0 diff --git a/doc/phy_link.txt b/doc/phy_link.txt new file mode 100644 index 0000000..c49e328 --- /dev/null +++ b/doc/phy_link.txt @@ -0,0 +1,57 @@ + +== OsmoBTS PHY interface abstraction + +The OsmoBTS PHY interface serves as an abstraction layer between given +PHY hardware and the actual logical transceivers (TRXs) of a BTS inside +the OsmoBTS code base. + + +=== PHY link + +A PHY link is a physical connection / link towards a given PHY. This +might be, for example, + +* a set of file descriptors to device nodes in the /dev/ directory + (sysmobts, litecell15) +* a packet socket for sending raw Ethernet frames to an OCTPHY +* a set of UDP sockets for interacting with OsmoTRX + +Each PHY interface has a set of attribute/parameters and a list of 1 to +n PHY instances. + +PHY links are numbered 0..n globally inside OsmoBTS. + +Each PHY link is configured via the VTY using its individual top-level +vty node. Given the different bts-model / phy specific properties, the +VTY configuration options (if any) of the PHY instance differ between +BTS models. + +The PHY links and instances must be configured above the BTS/TRX nodes +in the configuration file. If the file is saved via the VTY, the code +automatically ensures this. + + +=== PHY instance + +A PHY instance is an instance of a PHY, accessed via a PHY link. + +In the case of osmo-bts-sysmo and osmo-bts-trx, there is only one +instance in every PHY link. This is due to the fact that the API inside +that PHY link does not permit for distinguishing multiple different +logical TRXs. + +Other PHY implementations like the OCTPHY however do support addressing +multiple PHY instances via a single PHY link. + +PHY instances are numbered 0..n inside each PHY link. + +Each PHY instance is configured via the VTY as a separate node beneath each +PHY link. Given the different bts-model / phy specific properties, the +VTY configuration options (if any) of the PHY instance differ between +BTS models. + + +=== Mapping PHY instances to TRXs + +Each TRX node in the VTY must use the 'phy N instance M' command in +order to specify which PHY instance is allocated to this specific TRX. diff --git a/doc/startup.txt b/doc/startup.txt new file mode 100644 index 0000000..50766e4 --- /dev/null +++ b/doc/startup.txt @@ -0,0 +1,42 @@ + +== start-up / sequencing during OsmoBTS start + +The start-up procedure of OsmoBTS can be described as follows: + +|=== +| bts-specific | main() | +| common | bts_main() | initialization of talloc contexts +| common | osmo_init_logging2() | initialization of logging +| common | handle_options() | common option parsing +| bts-specific | bts_model_handle_options() | model-specific option parsing +| common | gsm_bts_alloc() | allocation of BTS/TRX/TS data structures +| common | vty_init() | Initialziation of VTY core, libosmo-abis and osmo-bts VTY +| common | main() | Setting of scheduler RR priority (if configured) +| common | main() | Initialization of GSMTAP (if configured) +| common | bts_init() | configuration of defaults in bts/trx/s object +| bts-specific | bts_model_init | ? +| common | abis_init() | Initialization of libosmo-abis +| common | vty_read_config_file() | Reading of configuration file +| bts-specific | bts_model_phy_link_set_defaults() | Called for every PHY link created +| bts-specific | bts_model_phy_instance_set_defaults() | Called for every PHY Instance created +| common | bts_controlif_setup() | Initialization of Control Interface +| bts-specific | bts_model_ctrl_cmds_install() +| common | telnet_init() | Initialization of telnet interface +| common | pcu_sock_init() | Initializaiton of PCU socket +| common | main() | Installation of signal handlers +| common | abis_open() | Start of the A-bis connection to BSC +| common | phy_links_open() | Iterate over list of configured PHY links +| bts-specific | bts_model_phy_link_open() | Open each of the configured PHY links +| common | write_pid_file() | Generate the pid file +| common | osmo_daemonize() | Fork as daemon in background (if configured) +| common | bts_main() | Run main loop until global variable quit >= 2 +| bts-specific | bts_model_oml_estab() | Called by core once OML link is established +| bts-specific | bts_model_check_oml() | called each time OML sets some attributes on a MO, checks if attributes are valid +| bts-specific | bts_model_apply_oml() | called each time OML sets some attributes on a MO, stores attribute contents in data structures +| bts-specific | bts_model_opstart() | for NM_OC_BTS, NM_OC_SITE_MANAGER, NM_OC_GPRS_NSE, NM_OC_GPRS_CELL, NMO_OC_GPRS_NSVC +| bts-specific | bts_model_opstart() | for NM_OC_RADIO_CARRIER for each trx +| bts-specific | bts_model_opstart() | for NM_OC_BASEB_TRANSC for each trx +| bts-specific | bts_model_opstart() | for NM_OC_CHANNEL for each timeslot on each trx +| bts-specific | bts_model_change_power() | change transmit power for each trx (power ramp-up/ramp-down + +| bts-specific | bts_model_abis_close() | called when either one of the RSL links or the OML link are down diff --git a/git-version-gen b/git-version-gen new file mode 100755 index 0000000..42cf3d2 --- /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 . + +# 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 0000000..7585a65 --- /dev/null +++ b/include/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = osmo-bts diff --git a/include/osmo-bts/Makefile.am b/include/osmo-bts/Makefile.am new file mode 100644 index 0000000..a15ce3d --- /dev/null +++ b/include/osmo-bts/Makefile.am @@ -0,0 +1,5 @@ +noinst_HEADERS = abis.h bts.h bts_model.h gsm_data.h gsm_data_shared.h logging.h measurement.h \ + oml.h paging.h rsl.h signal.h vty.h amr.h pcu_if.h pcuif_proto.h \ + handover.h msg_utils.h tx_power.h control_if.h cbch.h l1sap.h \ + power_control.h scheduler.h scheduler_backend.h phy_link.h \ + dtx_dl_amr_fsm.h diff --git a/include/osmo-bts/abis.h b/include/osmo-bts/abis.h new file mode 100644 index 0000000..62407ec --- /dev/null +++ b/include/osmo-bts/abis.h @@ -0,0 +1,29 @@ +#ifndef _ABIS_H +#define _ABIS_H + +#include +#include + +#include + +#define OML_RETRY_TIMER 5 +#define OML_PING_TIMER 20 + +enum { + LINK_STATE_IDLE = 0, + LINK_STATE_RETRYING, + LINK_STATE_CONNECTING, + LINK_STATE_CONNECT, +}; + +void abis_init(struct gsm_bts *bts); +struct e1inp_line *abis_open(struct gsm_bts *bts, char *dst_host, + char *model_name); + + +int abis_oml_sendmsg(struct msgb *msg); +int abis_bts_rsl_sendmsg(struct msgb *msg); + +uint32_t get_signlink_remote_ip(struct e1inp_sign_link *link); + +#endif /* _ABIS_H */ diff --git a/include/osmo-bts/amr.h b/include/osmo-bts/amr.h new file mode 100644 index 0000000..f313287 --- /dev/null +++ b/include/osmo-bts/amr.h @@ -0,0 +1,18 @@ +#ifndef _OSMO_BTS_AMR_H +#define _OSMO_BTS_AMR_H + +#include + +#define AMR_TOC_QBIT 0x04 +#define AMR_CMR_NONE 0xF + +void amr_log_mr_conf(int ss, int logl, const char *pfx, + struct amr_multirate_conf *amr_mrc); + +int amr_parse_mr_conf(struct amr_multirate_conf *amr_mrc, + const uint8_t *mr_conf, unsigned int len); +void amr_set_mode_pref(uint8_t *data, const struct amr_multirate_conf *amr_mrc, + uint8_t cmi, uint8_t cmr); +unsigned int amr_get_initial_mode(struct gsm_lchan *lchan); + +#endif /* _OSMO_BTS_AMR_H */ diff --git a/include/osmo-bts/bts.h b/include/osmo-bts/bts.h new file mode 100644 index 0000000..34ba956 --- /dev/null +++ b/include/osmo-bts/bts.h @@ -0,0 +1,67 @@ +#ifndef _BTS_H +#define _BTS_H + +#include +#include + +enum bts_global_status { + BTS_STATUS_RF_ACTIVE, + BTS_STATUS_RF_MUTE, + BTS_STATUS_LAST, +}; + +enum { + BTS_CTR_PAGING_RCVD, + BTS_CTR_PAGING_DROP, + BTS_CTR_PAGING_SENT, + BTS_CTR_RACH_RCVD, + BTS_CTR_RACH_DROP, + BTS_CTR_RACH_HO, + BTS_CTR_RACH_CS, + BTS_CTR_RACH_PS, + BTS_CTR_AGCH_RCVD, + BTS_CTR_AGCH_SENT, + BTS_CTR_AGCH_DELETED, +}; + +extern void *tall_bts_ctx; + +int bts_init(struct gsm_bts *bts); +void bts_shutdown(struct gsm_bts *bts, const char *reason); + +struct gsm_bts *create_bts(uint8_t num_trx, char *id); +int create_ms(struct gsm_bts_trx *trx, int maskc, uint8_t *maskv_tx, + uint8_t *maskv_rx); +void destroy_bts(struct gsm_bts *bts); +int work_bts(struct gsm_bts *bts); +int bts_link_estab(struct gsm_bts *bts); +int trx_link_estab(struct gsm_bts_trx *trx); +int trx_set_available(struct gsm_bts_trx *trx, int avail); +void bts_new_si(void *arg); +void bts_setup_slot(struct gsm_bts_trx_ts *slot, uint8_t comb); + +int bts_agch_enqueue(struct gsm_bts *bts, struct msgb *msg); +struct msgb *bts_agch_dequeue(struct gsm_bts *bts); +int bts_agch_max_queue_length(int T, int bcch_conf); +int bts_ccch_copy_msg(struct gsm_bts *bts, uint8_t *out_buf, struct gsm_time *gt, + int is_ag_res); + +uint8_t *bts_sysinfo_get(struct gsm_bts *bts, const struct gsm_time *g_time); +uint8_t *lchan_sacch_get(struct gsm_lchan *lchan); +int lchan_init_lapdm(struct gsm_lchan *lchan); + +void load_timer_start(struct gsm_bts *bts); +uint8_t num_agch(struct gsm_bts_trx *trx, const char * arg); +void bts_update_status(enum bts_global_status which, int on); + +int trx_ms_pwr_ctrl_is_osmo(struct gsm_bts_trx *trx); + +struct gsm_time *get_time(struct gsm_bts *bts); + +int bts_main(int argc, char **argv); + +int bts_supports_cm(struct gsm_bts *bts, enum gsm_phys_chan_config pchan, + enum gsm48_chan_mode cm); + +#endif /* _BTS_H */ + diff --git a/include/osmo-bts/bts_model.h b/include/osmo-bts/bts_model.h new file mode 100644 index 0000000..7a87d78 --- /dev/null +++ b/include/osmo-bts/bts_model.h @@ -0,0 +1,64 @@ +#ifndef BTS_MODEL_H +#define BTS_MODEL_H + +#include + +#include +#include + +#include + +struct phy_link; +struct phy_instance; + +/* BTS model specific functions needed by the common code */ + +int bts_model_init(struct gsm_bts *bts); + +int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, + struct tlv_parsed *old_attr, struct tlv_parsed *new_attr, + void *obj); + +int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, + struct tlv_parsed *new_attr, int obj_kind, void *obj); + +int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, + void *obj); + +int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo, + void *obj, uint8_t adm_state); + +int bts_model_trx_deact_rf(struct gsm_bts_trx *trx); +int bts_model_trx_close(struct gsm_bts_trx *trx); + +int bts_model_vty_init(struct gsm_bts *bts); + +void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts); +void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx); +void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink); +void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst); + +int bts_model_oml_estab(struct gsm_bts *bts); + +int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm); +int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan); + +int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap); + +int bts_model_lchan_deactivate(struct gsm_lchan *lchan); +int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan); + +void bts_model_abis_close(struct gsm_bts *bts); + +int bts_model_ctrl_cmds_install(struct gsm_bts *bts); + +int bts_model_handle_options(int argc, char **argv); +void bts_model_print_help(); + +void bts_model_phy_link_set_defaults(struct phy_link *plink); +void bts_model_phy_instance_set_defaults(struct phy_instance *pinst); + +int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts); +int bts_model_ts_connect(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config as_pchan); + +#endif diff --git a/include/osmo-bts/cbch.h b/include/osmo-bts/cbch.h new file mode 100644 index 0000000..b4ac409 --- /dev/null +++ b/include/osmo-bts/cbch.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +#include +#include + +/* incoming SMS broadcast command from RSL */ +int bts_process_smscb_cmd(struct gsm_bts *bts, + struct rsl_ie_cb_cmd_type cmd_type, + uint8_t msg_len, const uint8_t *msg); + +/* call-back from bts model specific code when it wants to obtain a CBCH + * block for a given gsm_time. outbuf must have 23 bytes of space. */ +int bts_cbch_get(struct gsm_bts *bts, uint8_t *outbuf, struct gsm_time *g_time); diff --git a/include/osmo-bts/control_if.h b/include/osmo-bts/control_if.h new file mode 100644 index 0000000..490c87a --- /dev/null +++ b/include/osmo-bts/control_if.h @@ -0,0 +1,5 @@ +#pragma once + +int bts_ctrl_cmds_install(struct gsm_bts *bts); +struct ctrl_handle *bts_controlif_setup(struct gsm_bts *bts, + const char *bind_addr, uint16_t port); diff --git a/include/osmo-bts/dtx_dl_amr_fsm.h b/include/osmo-bts/dtx_dl_amr_fsm.h new file mode 100644 index 0000000..c66ac7d --- /dev/null +++ b/include/osmo-bts/dtx_dl_amr_fsm.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include + +/* DTX DL AMR FSM */ + +#define X(s) (1 << (s)) + +enum dtx_dl_amr_fsm_states { + ST_VOICE, + ST_SID_F1, + ST_SID_F2, + ST_F1_INH_V, + ST_F1_INH_F, + ST_U_INH_V, + ST_U_INH_F, + ST_U_NOINH, + ST_F1_INH_V_REC, + ST_F1_INH_F_REC, + ST_U_INH_V_REC, + ST_U_INH_F_REC, + ST_SID_U, + ST_ONSET_V, + ST_ONSET_F, + ST_ONSET_V_REC, + ST_ONSET_F_REC, + ST_FACCH, +}; + +enum dtx_dl_amr_fsm_events { + E_VOICE, + E_ONSET, + E_FACCH, + E_COMPL, + E_FIRST, + E_INHIB, + E_SID_F, + E_SID_U, +}; + +extern const struct value_string dtx_dl_amr_fsm_event_names[]; +extern struct osmo_fsm dtx_dl_amr_fsm; diff --git a/include/osmo-bts/gsm_data.h b/include/osmo-bts/gsm_data.h new file mode 100644 index 0000000..9e62cdf --- /dev/null +++ b/include/osmo-bts/gsm_data.h @@ -0,0 +1,58 @@ +#ifndef _GSM_DATA_H +#define _GSM_DATA_H + +#include +#include +#include +#include + +#include +#include + +#define GSM_FR_BITS 260 +#define GSM_EFR_BITS 244 + +#define GSM_FR_BYTES 33 /* TS 101318 Chapter 5.1: 260 bits + 4bit sig */ +#define GSM_HR_BYTES 14 /* TS 101318 Chapter 5.2: 112 bits, no sig */ +#define GSM_EFR_BYTES 31 /* TS 101318 Chapter 5.3: 244 bits + 4bit sig */ + +#define GSM_SUPERFRAME (26*51) /* 1326 TDMA frames */ +#define GSM_HYPERFRAME (2048*GSM_SUPERFRAME) /* GSM_HYPERFRAME frames */ + +#define GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DEFAULT 41 +#define GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DISABLE 999999 +#define GSM_BTS_AGCH_QUEUE_LOW_LEVEL_DEFAULT 41 +#define GSM_BTS_AGCH_QUEUE_HIGH_LEVEL_DEFAULT 91 + +struct gsm_network { + struct llist_head bts_list; + unsigned int num_bts; + struct osmo_plmn_id plmn; + struct pcu_sock_state *pcu_state; +}; + +enum lchan_ciph_state { + LCHAN_CIPH_NONE, + LCHAN_CIPH_RX_REQ, + LCHAN_CIPH_RX_CONF, + LCHAN_CIPH_RXTX_REQ, + LCHAN_CIPH_RX_CONF_TX_REQ, + LCHAN_CIPH_RXTX_CONF, +}; + +#include + +void lchan_set_state(struct gsm_lchan *lchan, enum gsm_lchan_state state); +int conf_lchans_as_pchan(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config pchan); + +/* cipher code */ +#define CIPHER_A5(x) (1 << (x-1)) + +int bts_supports_cipher(struct gsm_bts *bts, int rsl_cipher); + +bool ts_is_pdch(const struct gsm_bts_trx_ts *ts); + +int bts_model_check_cm_mode(enum gsm_phys_chan_config pchan, enum gsm48_chan_mode cm); + +#endif /* _GSM_DATA_H */ diff --git a/include/osmo-bts/gsm_data_shared.h b/include/osmo-bts/gsm_data_shared.h new file mode 100644 index 0000000..812d086 --- /dev/null +++ b/include/osmo-bts/gsm_data_shared.h @@ -0,0 +1,815 @@ +#ifndef _GSM_DATA_SHAREDH +#define _GSM_DATA_SHAREDH + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* 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 + +/* 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 + +#define MAX_BTS_FEATURES 128 + +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 pcu_sock_state; +struct smscb_msg; + +/* Network Management State */ +struct gsm_nm_state { + uint8_t operational; + uint8_t administrative; + uint8_t availability; +}; + +struct gsm_abis_mo { + /* A-bis OML Object Class */ + uint8_t obj_class; + /* is there still some procedure pending? */ + uint8_t procedure_pending; + /* A-bis OML Object Instance */ + struct abis_om_obj_inst obj_inst; + /* human-readable name */ + const char *name; + /* NM State */ + struct gsm_nm_state nm_state; + /* Attributes configured in this MO */ + struct tlv_parsed *nm_attr; + /* BTS to which this MO belongs */ + struct gsm_bts *bts; +}; + +#define MAX_A5_KEY_LEN (128/8) +#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 +#define LCHAN_SAPI_REL 3 + +/* state of a logical channel */ +enum gsm_lchan_state { + LCHAN_S_NONE, /* channel is not active */ + LCHAN_S_ACT_REQ, /* channel activation requested */ + LCHAN_S_ACTIVE, /* channel is active and operational */ + LCHAN_S_REL_REQ, /* channel release has been requested */ + LCHAN_S_REL_ERR, /* channel is in an error state */ + LCHAN_S_BROKEN, /* channel is somehow unusable */ + LCHAN_S_INACTIVE, /* channel is set inactive */ +}; + +/* 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 1/256 bits) */ + int16_t ta_offs_256bits; + /* 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, +}; + +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; + /* 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; + /* State */ + enum gsm_lchan_state state; + const char *broken_reason; + /* Power levels for MS and BTS */ + uint8_t bs_power; + uint8_t ms_power; + /* Encryption information */ + struct { + uint8_t alg_id; + uint8_t key_len; + uint8_t key[MAX_A5_KEY_LEN]; + } encr; + + /* AMR bits */ + uint8_t mr_bts_lv[7]; + + /* Established data link layer services */ + int sacch_deact; + + struct { + uint32_t bound_ip; + uint32_t connect_ip; + uint16_t bound_port; + uint16_t connect_port; + uint16_t conn_id; + uint8_t rtp_payload; + uint8_t rtp_payload2; + uint8_t speech_mode; + struct osmo_rtp_socket *rtp_socket; + } abis_ip; + + uint8_t rqd_ta; + + char *name; + + /* Number of different GsmL1_Sapi_t used in osmo_bts_sysmo is 23. + * Currently we don't share these headers so this is a magic number. */ + struct llist_head sapi_cmds; + uint8_t sapis_dl[23]; + uint8_t sapis_ul[23]; + struct lapdm_channel lapdm_ch; + struct llist_head dl_tch_queue; + struct { + /* bitmask of all SI that are present/valid in si_buf */ + uint32_t valid; + uint32_t last; + /* buffers where we put the pre-computed SI: + SI2Q_MAX_NUM is the max number of SI2quater messages (see 3GPP TS 44.018) */ + sysinfo_buf_t buf[_MAX_SYSINFO_TYPE][SI2Q_MAX_NUM]; + } si; + struct { + uint8_t flags; + /* RSL measurment result number, 0 at lchan_act */ + uint8_t res_nr; + /* current Tx power level of the BTS */ + uint8_t bts_tx_pwr; + /* number of measurements stored in array below */ + uint8_t num_ul_meas; + struct bts_ul_meas uplink[MAX_NUM_UL_MEAS]; + /* last L1 header from the MS */ + uint8_t l1_info[2]; + struct gsm_meas_rep_unidir ul_res; + int16_t ms_toa256; + } meas; + struct { + struct amr_multirate_conf amr_mr; + struct { + struct osmo_fsm_inst *dl_amr_fsm; + /* TCH cache */ + uint8_t cache[20]; + /* FACCH cache */ + uint8_t facch[GSM_MACBLOCK_LEN]; + uint8_t len; + uint32_t fn; + bool is_update; + /* set for each SID frame to detect talkspurt for codecs + without explicit ONSET event */ + bool ul_sid; + /* indicates if DTXd was active during DL measurement + period */ + bool dl_active; + } dtx; + uint8_t last_cmr; + uint32_t last_fn; + } tch; + + /* 3GPP TS 48.058 § 9.3.37: [0; 255] ok, -1 means invalid*/ + int16_t ms_t_offs; + /* 3GPP TS 45.010 § 1.2 round trip propagation delay (in symbols) or -1 */ + int16_t p_offs; + + /* BTS-side ciphering state (rx only, bi-directional, ...) */ + uint8_t ciph_state; + uint8_t ciph_ns; + uint8_t loopback; + struct { + uint8_t active; + uint8_t ref; + /* T3105: PHYS INF retransmission */ + struct osmo_timer_list t3105; + /* counts up to Ny1 */ + unsigned int phys_info_count; + } ho; + /* S counter for link loss */ + int s; + /* Kind of the release/activation. E.g. RSL or PCU */ + int rel_act_kind; + /* RTP header Marker bit to indicate beginning of speech after pause */ + bool rtp_tx_marker; + /* power handling */ + struct { + uint8_t current; + uint8_t fixed; + } ms_power_ctrl; + + struct msgb *pending_rel_ind_msg; + + /* ECU (Error Concealment Unit) state */ + union { + struct osmo_ecu_fr_state fr; + } ecu_state; +}; + +extern const struct value_string lchan_ciph_state_names[]; +static inline const char *lchan_ciph_state_name(uint8_t state) { + return get_value_string(lchan_ciph_state_names, state); +} + +enum { + TS_F_PDCH_ACTIVE = 0x1000, + TS_F_PDCH_ACT_PENDING = 0x2000, + TS_F_PDCH_DEACT_PENDING = 0x4000, + TS_F_PDCH_PENDING_MASK = 0x6000 /*< + TS_F_PDCH_ACT_PENDING | TS_F_PDCH_DEACT_PENDING */ +} gsm_bts_trx_ts_flags; + +/* 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; + + enum gsm_phys_chan_config pchan; + + struct { + enum gsm_phys_chan_config pchan_is; + enum gsm_phys_chan_config pchan_want; + struct msgb *pending_chan_activ; + } dyn; + + unsigned int flags; + 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; + + 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? */ + uint8_t rsl_tei; + struct e1inp_sign_link *rsl_link; + + /* 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 */ + + struct trx_power_params power_params; + int ms_power_control; + + struct { + void *l1h; + } role_bts; + + union { + struct { + unsigned int test_state; + uint8_t test_nr; + struct rxlev_stats rxlev_stat; + } ipaccess; + }; + 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_variant { + BTS_UNKNOWN, + BTS_OSMO_LITECELL15, + BTS_OSMO_OCTPHY, + BTS_OSMO_SYSMO, + BTS_OSMO_TRX, + BTS_OSMO_VIRTUAL, + BTS_OSMO_OMLDUMMY, + _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; + +/* N. B: always add new features to the end of the list (right before _NUM_BTS_FEAT) to avoid breaking compatibility + with BTS compiled against earlier version of this header. Also make sure that the description strings + gsm_bts_features_descs[] in gsm_data_shared.c are also updated accordingly! */ +enum gsm_bts_features { + BTS_FEAT_HSCSD, + BTS_FEAT_GPRS, + BTS_FEAT_EGPRS, + BTS_FEAT_ECSD, + BTS_FEAT_HOPPING, + BTS_FEAT_MULTI_TSC, + BTS_FEAT_OML_ALERTS, + BTS_FEAT_AGCH_PCH_PROP, + BTS_FEAT_CBCH, + BTS_FEAT_SPEECH_F_V1, + BTS_FEAT_SPEECH_H_V1, + BTS_FEAT_SPEECH_F_EFR, + BTS_FEAT_SPEECH_F_AMR, + BTS_FEAT_SPEECH_H_AMR, + _NUM_BTS_FEAT +}; + +extern const struct value_string gsm_bts_features_descs[]; + +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; +}; + +/* 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_variant variant; + 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? */ + uint8_t oml_tei; + struct e1inp_sign_link *oml_link; + + /* 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; + + /* 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; + }; + + /* 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; + int bcch_change_mark; + + struct rate_ctr_group *ctrs; + bool supp_meas_toa256; + + struct { + /* Interference Boundaries for OML */ + int16_t boundary[6]; + uint8_t intave; + } interference; + unsigned int t200_ms[7]; + unsigned int t3105_ms; + struct { + uint8_t overload_period; + struct { + /* Input parameters from OML */ + uint8_t load_ind_thresh; /* percent */ + uint8_t load_ind_period; /* seconds */ + /* Internal data */ + struct osmo_timer_list timer; + unsigned int pch_total; + unsigned int pch_used; + } ccch; + struct { + /* Input parameters from OML */ + int16_t busy_thresh; /* in dBm */ + uint16_t averaging_slots; + /* Internal data */ + unsigned int total; /* total nr */ + unsigned int busy; /* above busy_thresh */ + unsigned int access; /* access bursts */ + } rach; + } load; + uint8_t ny1; + uint8_t max_ta; + + /* AGCH queuing */ + struct { + struct llist_head queue; + int length; + int max_length; + + int thresh_level; /* Cleanup threshold in percent of max len */ + int low_level; /* Low water mark in percent of max len */ + int high_level; /* High water mark in percent of max len */ + + /* TODO: Use a rate counter group instead */ + uint64_t dropped_msgs; + uint64_t merged_msgs; + uint64_t rejected_msgs; + uint64_t agch_msgs; + uint64_t pch_msgs; + } agch_queue; + + struct paging_state *paging_state; + char *bsc_oml_host; + struct llist_head oml_queue; + unsigned int rtp_jitter_buf_ms; + bool rtp_jitter_adaptive; + struct { + uint8_t ciphers; /* flags A5/1==0x1, A5/2==0x2, A5/3==0x4 */ + } support; + struct { + uint8_t tc4_ctr; + } si; + struct gsm_time gsm_time; + /* Radio Link Timeout counter. -1 disables timeout for + * lab/measurement purpose */ + int radio_link_timeout; + + int ul_power_target; /* Uplink Rx power target */ + + /* used by the sysmoBTS to adjust band */ + uint8_t auto_band; + + struct { + struct llist_head queue; /* list of struct smscb_msg */ + struct smscb_msg *cur_msg; /* current SMS-CB */ + } smscb_state; + + float min_qual_rach; /* minimum quality for RACH bursts */ + float min_qual_norm; /* minimum quality for normal daata */ + uint16_t max_ber10k_rach; /* Maximum permitted RACH BER in 0.01% */ + + struct { + char *sock_path; + } pcu; + + struct { + uint32_t last_fn; + struct timeval tv_clock; + struct osmo_timer_list fn_timer; + } vbts; + +}; + + +struct gsm_bts *gsm_bts_alloc(void *talloc_ctx, uint8_t bts_num); +struct gsm_bts *gsm_bts_num(struct gsm_network *net, int num); + +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 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[]; +const struct value_string gsm_pchant_names[13]; +const struct value_string gsm_pchant_descs[13]; +const char *gsm_pchan_name(enum gsm_phys_chan_config 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); +const char *gsm_lchans_name(enum gsm_lchan_state s); + +static inline char *gsm_lchan_name(const struct gsm_lchan *lchan) +{ + return lchan->name; +} + +static inline int gsm_bts_set_feature(struct gsm_bts *bts, enum gsm_bts_features feat) +{ + OSMO_ASSERT(_NUM_BTS_FEAT < MAX_BTS_FEATURES); + return bitvec_set_bit_pos(&bts->features, feat, 1); +} + +static inline bool gsm_bts_has_feature(const struct gsm_bts *bts, enum gsm_bts_features feat) +{ + OSMO_ASSERT(_NUM_BTS_FEAT < MAX_BTS_FEATURES); + return bitvec_get_bit_pos(&bts->features, feat); +} + +void gsm_abis_mo_reset(struct gsm_abis_mo *mo); + +struct gsm_abis_mo * +gsm_objclass2mo(struct gsm_bts *bts, uint8_t obj_class, + const struct abis_om_obj_inst *obj_inst); + +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); + +/* 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)); + +#define BSIC2BCC(bsic) ((bsic) & 0x3) + +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 ts_subslots(struct gsm_bts_trx_ts *ts); +bool ts_is_tch(struct gsm_bts_trx_ts *ts); +const char *gsm_trx_unit_id(struct gsm_bts_trx *trx); + +#endif diff --git a/include/osmo-bts/handover.h b/include/osmo-bts/handover.h new file mode 100644 index 0000000..35d5c69 --- /dev/null +++ b/include/osmo-bts/handover.h @@ -0,0 +1,12 @@ +#pragma once + +enum { + HANDOVER_NONE = 0, + HANDOVER_ENABLED, + HANDOVER_WAIT_FRAME, +}; + +void handover_rach(struct gsm_lchan *lchan, uint8_t ra, uint8_t acc_delay); +void handover_frame(struct gsm_lchan *lchan); +void handover_reset(struct gsm_lchan *lchan); + diff --git a/include/osmo-bts/l1sap.h b/include/osmo-bts/l1sap.h new file mode 100644 index 0000000..ad13145 --- /dev/null +++ b/include/osmo-bts/l1sap.h @@ -0,0 +1,96 @@ +#ifndef L1SAP_H +#define L1SAP_H + +#include + +/* lchan link ID */ +#define LID_SACCH 0x40 +#define LID_DEDIC 0x00 + +/* timeslot and subslot from chan_nr */ +#define L1SAP_CHAN2TS(chan_nr) (chan_nr & 7) +#define L1SAP_CHAN2SS_TCHH(chan_nr) ((chan_nr >> 3) & 1) +#define L1SAP_CHAN2SS_SDCCH4(chan_nr) ((chan_nr >> 3) & 3) +#define L1SAP_CHAN2SS_SDCCH8(chan_nr) ((chan_nr >> 3) & 7) + +/* logical channel from chan_nr + link_id */ +#define L1SAP_IS_LINK_SACCH(link_id) ((link_id & 0xC0) == LID_SACCH) +#define L1SAP_IS_CHAN_TCHF(chan_nr) ((chan_nr & 0xf8) == 0x08) +#define L1SAP_IS_CHAN_TCHH(chan_nr) ((chan_nr & 0xf0) == 0x10) +#define L1SAP_IS_CHAN_SDCCH4(chan_nr) ((chan_nr & 0xe0) == 0x20) +#define L1SAP_IS_CHAN_SDCCH8(chan_nr) ((chan_nr & 0xc0) == 0x40) +#define L1SAP_IS_CHAN_BCCH(chan_nr) ((chan_nr & 0xf8) == 0x80) +#define L1SAP_IS_CHAN_RACH(chan_nr) ((chan_nr & 0xf8) == 0x88) +#define L1SAP_IS_CHAN_AGCH_PCH(chan_nr) ((chan_nr & 0xf8) == 0x90) +#define L1SAP_IS_CHAN_PDCH(chan_nr) ((chan_nr & 0xf8) == 0xc0) + +/* rach type from ra */ +#define L1SAP_IS_PACKET_RACH(ra) ((ra & 0xf0) == 0x70 && (ra & 0x0f) != 0x0f) + +/* CCCH block from frame number */ +#define L1SAP_FN2CCCHBLOCK(fn) ((fn % 51) / 5 - 1) + +/* PTCH layout from frame number */ +#define L1SAP_FN2MACBLOCK(fn) ((fn % 52) / 4) +#define L1SAP_FN2PTCCHBLOCK(fn) ((fn / 104) & 3) + +/* Calculate PTCCH occurrence, See also 3GPP TS 05.02, Clause 7, Table 6 of 9 */ +#define L1SAP_IS_PTCCH(fn) (((fn % 52) == 12) || ((fn % 52) == 38)) + + +static const uint8_t fill_frame[GSM_MACBLOCK_LEN] = { + 0x03, 0x03, 0x01, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, + 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, + 0x2B, 0x2B, 0x2B +}; + +/* subslot from any chan_nr */ +static inline uint8_t l1sap_chan2ss(uint8_t chan_nr) +{ + if (L1SAP_IS_CHAN_SDCCH8(chan_nr)) + return L1SAP_CHAN2SS_SDCCH8(chan_nr); + if (L1SAP_IS_CHAN_SDCCH4(chan_nr)) + return L1SAP_CHAN2SS_SDCCH4(chan_nr); + if (L1SAP_IS_CHAN_TCHH(chan_nr)) + return L1SAP_CHAN2SS_TCHH(chan_nr); + return 0; +} + +struct gsm_lchan *get_lchan_by_chan_nr(struct gsm_bts_trx *trx, + unsigned int chan_nr); + +/* allocate a msgb containing a osmo_phsap_prim + optional l2 data */ +struct msgb *l1sap_msgb_alloc(unsigned int l2_len); + +/* any L1 prim received from bts model */ +int l1sap_up(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap); + +/* pcu (socket interface) sends us a data request primitive */ +int l1sap_pdch_req(struct gsm_bts_trx_ts *ts, int is_ptcch, uint32_t fn, + uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len); + +/* call-back function for incoming RTP */ +void l1sap_rtp_rx_cb(struct osmo_rtp_socket *rs, const uint8_t *rtp_pl, + unsigned int rtp_pl_len, uint16_t seq_number, + uint32_t timestamp, bool marker); + +/* channel control */ +int l1sap_chan_act(struct gsm_bts_trx *trx, uint8_t chan_nr, struct tlv_parsed *tp); +int l1sap_chan_rel(struct gsm_bts_trx *trx, uint8_t chan_nr); +int l1sap_chan_deact_sacch(struct gsm_bts_trx *trx, uint8_t chan_nr); +int l1sap_chan_modify(struct gsm_bts_trx *trx, uint8_t chan_nr); + +extern const struct value_string gsmtap_sapi_names[]; +extern struct gsmtap_inst *gsmtap; +extern uint32_t gsmtap_sapi_mask; +extern uint8_t gsmtap_sapi_acch; + +int add_l1sap_header(struct gsm_bts_trx *trx, struct msgb *rmsg, + struct gsm_lchan *lchan, uint8_t chan_nr, uint32_t fn, + uint16_t ber10k, int16_t lqual_cb); + +#define msgb_l1sap_prim(msg) ((struct osmo_phsap_prim *)(msg)->l1h) + +int bts_check_for_first_ciphrd(struct gsm_lchan *lchan, + uint8_t *data, int len); +#endif /* L1SAP_H */ diff --git a/include/osmo-bts/logging.h b/include/osmo-bts/logging.h new file mode 100644 index 0000000..852c383 --- /dev/null +++ b/include/osmo-bts/logging.h @@ -0,0 +1,40 @@ +#ifndef _LOGGING_H +#define _LOGGING_H + +#define DEBUG +#include + +enum { + DRSL, + DOML, + DRLL, + DRR, + DMEAS, + DPAG, + DL1C, + DL1P, + DDSP, + DPCU, + DHO, + DTRX, + DLOOP, + DABIS, + DRTP, + DSUM, +}; + +extern const struct log_info bts_log_info; + +/* LOGP with gsm_time prefix */ +#define LOGPGT(ss, lvl, gt, fmt, args...) \ + LOGP(ss, lvl, "%s " fmt, osmo_dump_gsmtime(gt), ## args) +#define DEBUGPGT(ss, gt, fmt, args...) \ + LOGP(ss, LOGL_DEBUG, "%s " fmt, osmo_dump_gsmtime(gt), ## args) + +/* LOGP with frame number prefix */ +#define LOGPFN(ss, lvl, fn, fmt, args...) \ + LOGP(ss, lvl, "%s " fmt, gsm_fn_as_gsmtime_str(fn), ## args) +#define DEBUGPFN(ss, fn, fmt, args...) \ + LOGP(ss, LOGL_DEBUG, "%s " fmt, gsm_fn_as_gsmtime_str(fn), ## args) + +#endif /* _LOGGING_H */ diff --git a/include/osmo-bts/measurement.h b/include/osmo-bts/measurement.h new file mode 100644 index 0000000..e32c8e8 --- /dev/null +++ b/include/osmo-bts/measurement.h @@ -0,0 +1,11 @@ +#ifndef OSMO_BTS_MEAS_H +#define OSMO_BTS_MEAS_H + +#define MEAS_MAX_TIMING_ADVANCE 63 +#define MEAS_MIN_TIMING_ADVANCE 0 + +int lchan_new_ul_meas(struct gsm_lchan *lchan, struct bts_ul_meas *ulm, uint32_t fn); + +int lchan_meas_check_compute(struct gsm_lchan *lchan, uint32_t fn); + +#endif diff --git a/include/osmo-bts/msg_utils.h b/include/osmo-bts/msg_utils.h new file mode 100644 index 0000000..7ddbe88 --- /dev/null +++ b/include/osmo-bts/msg_utils.h @@ -0,0 +1,48 @@ +/* + * Routines to check the structurally integrity of messages + */ + +#pragma once + +#include +#include + +#include + +#include + +struct msgb; + +/* Access 1st part of msgb control buffer */ +#define rtpmsg_marker_bit(x) ((x)->cb[0]) + +/* Access 2nd part of msgb control buffer */ +#define rtpmsg_seq(x) ((x)->cb[1]) + +/* Access 3rd part of msgb control buffer */ +#define rtpmsg_ts(x) ((x)->cb[2]) + +/** + * Classification of OML message. ETSI for plain GSM 12.21 + * messages and IPA/Osmo for manufacturer messages. + */ +enum { + OML_MSG_TYPE_ETSI, + OML_MSG_TYPE_IPA, + OML_MSG_TYPE_OSMO, +}; + +void lchan_set_marker(bool t, struct gsm_lchan *lchan); +bool dtx_dl_amr_enabled(const struct gsm_lchan *lchan); +void dtx_dispatch(struct gsm_lchan *lchan, enum dtx_dl_amr_fsm_events e); +bool dtx_recursion(const struct gsm_lchan *lchan); +void dtx_int_signal(struct gsm_lchan *lchan); +bool dtx_is_first_p1(const struct gsm_lchan *lchan); +void dtx_cache_payload(struct gsm_lchan *lchan, const uint8_t *l1_payload, + size_t length, uint32_t fn, int update); +int dtx_dl_amr_fsm_step(struct gsm_lchan *lchan, const uint8_t *rtp_pl, + size_t rtp_pl_len, uint32_t fn, uint8_t *l1_payload, + bool marker, uint8_t *len, uint8_t *ft_out); +uint8_t repeat_last_sid(struct gsm_lchan *lchan, uint8_t *dst, uint32_t fn); +int msg_verify_ipa_structure(struct msgb *msg); +int msg_verify_oml_structure(struct msgb *msg); diff --git a/include/osmo-bts/oml.h b/include/osmo-bts/oml.h new file mode 100644 index 0000000..139464e --- /dev/null +++ b/include/osmo-bts/oml.h @@ -0,0 +1,50 @@ +#ifndef _OML_H +#define _OML_H + +#include + +struct gsm_bts; +struct gsm_abis_mo; +struct msgb; +struct gsm_lchan; + + +int oml_init(struct gsm_abis_mo *mo); +int down_oml(struct gsm_bts *bts, struct msgb *msg); + +struct msgb *oml_msgb_alloc(void); +int oml_send_msg(struct msgb *msg, int is_mauf); +int oml_mo_send_msg(struct gsm_abis_mo *mo, struct msgb *msg, uint8_t msg_type); +int oml_mo_opstart_ack(struct gsm_abis_mo *mo); +int oml_mo_opstart_nack(struct gsm_abis_mo *mo, uint8_t nack_cause); +int oml_mo_statechg_ack(struct gsm_abis_mo *mo); +int oml_mo_statechg_nack(struct gsm_abis_mo *mo, uint8_t nack_cause); + +/* Change the state and send STATE CHG REP */ +int oml_mo_state_chg(struct gsm_abis_mo *mo, int op_state, int avail_state); + +/* First initialization of MO, does _not_ generate state changes */ +void oml_mo_state_init(struct gsm_abis_mo *mo, int op_state, int avail_state); + +/* Update admin state and send ACK/NACK */ +int oml_mo_rf_lock_chg(struct gsm_abis_mo *mo, uint8_t mute_state[8], + int success); + +/* Transmit STATE CHG REP even if there was no state change */ +int oml_tx_state_changed(struct gsm_abis_mo *mo); + +int oml_mo_tx_sw_act_rep(struct gsm_abis_mo *mo); + +int oml_fom_ack_nack(struct msgb *old_msg, uint8_t cause); + +int oml_mo_fom_ack_nack(struct gsm_abis_mo *mo, uint8_t orig_msg_type, + uint8_t cause); + +/* Configure LAPDm T200 timers for this lchan according to OML */ +int oml_set_lchan_t200(struct gsm_lchan *lchan); +extern const unsigned int oml_default_t200_ms[7]; + +/* Transmit failure event report */ +void oml_fail_rep(uint16_t cause_value, const char *fmt, ...); + +#endif // _OML_H */ diff --git a/include/osmo-bts/paging.h b/include/osmo-bts/paging.h new file mode 100644 index 0000000..7fc0bf0 --- /dev/null +++ b/include/osmo-bts/paging.h @@ -0,0 +1,52 @@ +#ifndef OSMO_BTS_PAGING_H +#define OSMO_BTS_PAGING_H + +#include +#include +#include + +struct paging_state; +struct gsm_bts; + +/* initialize paging code */ +struct paging_state *paging_init(struct gsm_bts *bts, + unsigned int num_paging_max, + unsigned int paging_lifetime); + +/* (re) configure paging code */ +void paging_config(struct paging_state *ps, + unsigned int num_paging_max, + unsigned int paging_lifetime); + +void paging_reset(struct paging_state *ps); + +/* The max number of paging entries */ +unsigned int paging_get_queue_max(struct paging_state *ps); +void paging_set_queue_max(struct paging_state *ps, unsigned int queue_max); + +/* The lifetime of a paging entry */ +unsigned int paging_get_lifetime(struct paging_state *ps); +void paging_set_lifetime(struct paging_state *ps, unsigned int lifetime); + +/* update with new SYSTEM INFORMATION parameters */ +int paging_si_update(struct paging_state *ps, struct gsm48_control_channel_descr *chan_desc); + +/* Add an identity to the paging queue */ +int paging_add_identity(struct paging_state *ps, uint8_t paging_group, + const uint8_t *identity_lv, uint8_t chan_needed); + +/* Add an IMM.ASS message to the paging queue */ +int paging_add_imm_ass(struct paging_state *ps, const uint8_t *data, + uint8_t len); + +/* generate paging message for given gsm time */ +int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *gt, + int *is_empty); + + +/* inspection methods below */ +int paging_group_queue_empty(struct paging_state *ps, uint8_t group); +int paging_queue_length(struct paging_state *ps); +int paging_buffer_space(struct paging_state *ps); + +#endif diff --git a/include/osmo-bts/pcu_if.h b/include/osmo-bts/pcu_if.h new file mode 100644 index 0000000..98efb57 --- /dev/null +++ b/include/osmo-bts/pcu_if.h @@ -0,0 +1,24 @@ +#ifndef _PCU_IF_H +#define _PCU_IF_H + +extern int pcu_direct; + +int pcu_tx_info_ind(void); +int pcu_tx_si13(const struct gsm_bts *bts, bool enable); +int pcu_tx_rts_req(struct gsm_bts_trx_ts *ts, uint8_t is_ptcch, uint32_t fn, + uint16_t arfcn, uint8_t block_nr); +int pcu_tx_data_ind(struct gsm_bts_trx_ts *ts, uint8_t is_ptcch, uint32_t fn, + uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len, + int8_t rssi, uint16_t ber10k, int16_t bto, int16_t lqual); +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); +int pcu_tx_time_ind(uint32_t fn); +int pcu_tx_pag_req(const uint8_t *identity_lv, uint8_t chan_needed); +int pcu_tx_pch_data_cnf(uint32_t fn, uint8_t *data, uint8_t len); + +int pcu_sock_init(const char *path); +void pcu_sock_exit(void); + +bool pcu_connected(void); + +#endif /* _PCU_IF_H */ diff --git a/include/osmo-bts/pcuif_proto.h b/include/osmo-bts/pcuif_proto.h new file mode 100644 index 0000000..b06077c --- /dev/null +++ b/include/osmo-bts/pcuif_proto.h @@ -0,0 +1,195 @@ +#ifndef _PCUIF_PROTO_H +#define _PCUIF_PROTO_H + +#include + +#define PCU_SOCK_DEFAULT "/tmp/pcu_bts" + +#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/osmo-bts/phy_link.h b/include/osmo-bts/phy_link.h new file mode 100644 index 0000000..0ffc58e --- /dev/null +++ b/include/osmo-bts/phy_link.h @@ -0,0 +1,160 @@ +#pragma once + +#include +#include +#include + +#include + +#include +#include "btsconfig.h" + +struct gsm_bts_trx; +struct virt_um_inst; + +enum phy_link_type { + PHY_LINK_T_NONE, + PHY_LINK_T_SYSMOBTS, + PHY_LINK_T_OSMOTRX, + PHY_LINK_T_VIRTUAL, +}; + +enum phy_link_state { + PHY_LINK_SHUTDOWN, + PHY_LINK_CONNECTING, + PHY_LINK_CONNECTED, +}; + +/* A PHY link represents the connection to a given PHYsical layer + * implementation. That PHY link contains 1...N PHY instances, one for + * each TRX */ +struct phy_link { + struct llist_head list; + int num; + enum phy_link_type type; + enum phy_link_state state; + struct llist_head instances; + char *description; + union { + struct { + } sysmobts; + struct { + char *local_ip; + char *remote_ip; + uint16_t base_port_local; + uint16_t base_port_remote; + struct osmo_fd trx_ofd_clk; + bool trx_ta_loop; + bool trx_ms_power_loop; + int8_t trx_target_rssi; + uint32_t clock_advance; + uint32_t rts_advance; + bool use_legacy_setbsic; + } osmotrx; + struct { + char *mcast_dev; /* Network device for multicast */ + char *bts_mcast_group; /* BTS are listening to this group */ + uint16_t bts_mcast_port; + char *ms_mcast_group; /* MS are listening to this group */ + uint16_t ms_mcast_port; + struct virt_um_inst *virt_um; + } virt; + struct { + /* MAC address of the PHY */ + struct sockaddr_ll phy_addr; + /* Network device name */ + char *netdev_name; + + /* configuration */ + uint32_t rf_port_index; +#if OCTPHY_USE_ANTENNA_ID == 1 + uint32_t rx_ant_id; + uint32_t tx_ant_id; +#endif + uint32_t rx_gain_db; + bool tx_atten_flag; + uint32_t tx_atten_db; +#if OCTPHY_MULTI_TRX == 1 + /* arfcn used by TRX with id 0 */ + uint16_t center_arfcn; +#endif + + struct octphy_hdl *hdl; + } octphy; + } u; +}; + +struct phy_instance { + /* liked inside phy_link.linstances */ + struct llist_head list; + int num; + char *description; + char version[MAX_VERSION_LENGTH]; + /* pointer to the PHY link to which we belong */ + struct phy_link *phy_link; + + /* back-pointer to the TRX to which we're associated */ + struct gsm_bts_trx *trx; + + union { + struct { + /* configuration */ + uint8_t clk_use_eeprom; + uint32_t dsp_trace_f; + int clk_cal; + uint8_t clk_src; + char *calib_path; + + struct femtol1_hdl *hdl; + } sysmobts; + struct { + struct trx_l1h *hdl; + bool sw_act_reported; + } osmotrx; + struct { + struct l1sched_trx sched; + } virt; + struct { + /* logical transceiver number within one PHY */ + uint32_t trx_id; + /* trx lock state variable */ + int trx_locked; + } octphy; + struct { + /* configuration */ + uint32_t dsp_trace_f; + char *calib_path; + int minTxPower; + int maxTxPower; + struct lc15l1_hdl *hdl; + uint8_t max_cell_size; /* 0:166 qbits*/ + uint8_t diversity_mode; /* 0: SISO A, 1: SISO B, 2: MRC */ + uint8_t pedestal_mode; /* 0: unused TS is OFF, 1: unused TS is in minimum Tx power */ + uint8_t dsp_alive_period; /* DSP alive timer period */ + uint8_t tx_pwr_adj_mode; /* 0: no auto adjust power, 1: auto adjust power using RMS detector */ + uint8_t tx_pwr_red_8psk; /* 8-PSK maximum Tx power reduction level in dB */ + } lc15; + } u; +}; + +struct phy_link *phy_link_by_num(int num); +struct phy_link *phy_link_create(void *ctx, int num); +void phy_link_destroy(struct phy_link *plink); +void phy_link_state_set(struct phy_link *plink, enum phy_link_state state); +int phy_links_open(void); + +struct phy_instance *phy_instance_by_num(struct phy_link *plink, int num); +struct phy_instance *phy_instance_create(struct phy_link *plink, int num); +void phy_instance_link_to_trx(struct phy_instance *pinst, struct gsm_bts_trx *trx); +void phy_instance_destroy(struct phy_instance *pinst); +const char *phy_instance_name(struct phy_instance *pinst); + +void phy_user_statechg_notif(struct phy_instance *pinst, enum phy_link_state link_state); + +static inline struct phy_instance *trx_phy_instance(struct gsm_bts_trx *trx) +{ + OSMO_ASSERT(trx); + return trx->role_bts.l1h; +} + +int bts_model_phy_link_open(struct phy_link *plink); diff --git a/include/osmo-bts/power_control.h b/include/osmo-bts/power_control.h new file mode 100644 index 0000000..43d4b59 --- /dev/null +++ b/include/osmo-bts/power_control.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include + +int lchan_ms_pwr_ctrl(struct gsm_lchan *lchan, + const uint8_t ms_power, const int rxLevel); diff --git a/include/osmo-bts/rsl.h b/include/osmo-bts/rsl.h new file mode 100644 index 0000000..ddd756e --- /dev/null +++ b/include/osmo-bts/rsl.h @@ -0,0 +1,46 @@ +#ifndef _RSL_H +#define _RSL_H + +/** + * What kind of release/activation is done? A silent one for + * the PDCH or one triggered through RSL? + */ +enum { + LCHAN_REL_ACT_RSL, + LCHAN_REL_ACT_PCU, + LCHAN_REL_ACT_OML, + LCHAN_REL_ACT_REACT, /* remove once auto-activation hack is removed from opstart_compl() */ +}; + +#define LCHAN_FN_DUMMY 0xFFFFFFFF +#define LCHAN_FN_WAIT 0xFFFFFFFE + +int msgb_queue_flush(struct llist_head *list); + +int down_rsl(struct gsm_bts_trx *trx, struct msgb *msg); +int rsl_tx_rf_res(struct gsm_bts_trx *trx); +int rsl_tx_chan_rqd(struct gsm_bts_trx *trx, struct gsm_time *gtime, + uint8_t ra, uint8_t acc_delay); +int rsl_tx_est_ind(struct gsm_lchan *lchan, uint8_t link_id, uint8_t *data, int len); + +int rsl_tx_chan_act_acknack(struct gsm_lchan *lchan, uint8_t cause); +int rsl_tx_conn_fail(struct gsm_lchan *lchan, uint8_t cause); +int rsl_tx_rf_rel_ack(struct gsm_lchan *lchan); +int rsl_tx_hando_det(struct gsm_lchan *lchan, uint8_t *ho_delay); + +int lchan_deactivate(struct gsm_lchan *lchan); + +/* call-back for LAPDm code, called when it wants to send msgs UP */ +int lapdm_rll_tx_cb(struct msgb *msg, struct lapdm_entity *le, void *ctx); + +int rsl_tx_ipac_dlcx_ind(struct gsm_lchan *lchan, uint8_t cause); +int rsl_tx_ccch_load_ind_pch(struct gsm_bts *bts, uint16_t paging_avail); +int rsl_tx_ccch_load_ind_rach(struct gsm_bts *bts, uint16_t total, + uint16_t busy, uint16_t access); + +void cb_ts_disconnected(struct gsm_bts_trx_ts *ts); +void cb_ts_connected(struct gsm_bts_trx_ts *ts); +void ipacc_dyn_pdch_complete(struct gsm_bts_trx_ts *ts, int rc); + +#endif // _RSL_H */ + diff --git a/include/osmo-bts/scheduler.h b/include/osmo-bts/scheduler.h new file mode 100644 index 0000000..98f38d3 --- /dev/null +++ b/include/osmo-bts/scheduler.h @@ -0,0 +1,224 @@ +#ifndef TRX_SCHEDULER_H +#define TRX_SCHEDULER_H + +#include + +#include + +/* These types define the different channels on a multiframe. + * Each channel has queues and can be activated individually. + */ +enum trx_chan_type { + TRXC_IDLE = 0, + TRXC_FCCH, + TRXC_SCH, + TRXC_BCCH, + TRXC_RACH, + TRXC_CCCH, + TRXC_TCHF, + TRXC_TCHH_0, + TRXC_TCHH_1, + TRXC_SDCCH4_0, + TRXC_SDCCH4_1, + TRXC_SDCCH4_2, + TRXC_SDCCH4_3, + TRXC_SDCCH8_0, + TRXC_SDCCH8_1, + TRXC_SDCCH8_2, + TRXC_SDCCH8_3, + TRXC_SDCCH8_4, + TRXC_SDCCH8_5, + TRXC_SDCCH8_6, + TRXC_SDCCH8_7, + TRXC_SACCHTF, + TRXC_SACCHTH_0, + TRXC_SACCHTH_1, + TRXC_SACCH4_0, + TRXC_SACCH4_1, + TRXC_SACCH4_2, + TRXC_SACCH4_3, + TRXC_SACCH8_0, + TRXC_SACCH8_1, + TRXC_SACCH8_2, + TRXC_SACCH8_3, + TRXC_SACCH8_4, + TRXC_SACCH8_5, + TRXC_SACCH8_6, + TRXC_SACCH8_7, + TRXC_PDTCH, + TRXC_PTCCH, + _TRX_CHAN_MAX +}; + +extern const struct value_string trx_chan_type_names[]; + +#define GSM_BURST_LEN 148 +#define GPRS_BURST_LEN GSM_BURST_LEN +#define EGPRS_BURST_LEN 444 + +enum trx_burst_type { + TRX_BURST_GMSK, + TRX_BURST_8PSK, +}; + +/* States each channel on a multiframe */ +struct l1sched_chan_state { + /* scheduler */ + uint8_t active; /* Channel is active */ + ubit_t *dl_bursts; /* burst buffer for TX */ + enum trx_burst_type dl_burst_type; /* GMSK or 8PSK burst type */ + sbit_t *ul_bursts; /* burst buffer for RX */ + uint32_t ul_first_fn; /* fn of first burst */ + uint8_t ul_mask; /* mask of received bursts */ + + /* RSSI / TOA */ + uint8_t rssi_num; /* number of RSSI values */ + float rssi_sum; /* sum of RSSI values */ + uint8_t toa_num; /* number of TOA values */ + int32_t toa256_sum; /* sum of TOA values (1/256 symbol) */ + + /* loss detection */ + uint8_t lost; /* (SACCH) loss detection */ + + /* mode */ + uint8_t rsl_cmode, tch_mode; /* mode for TCH channels */ + + /* AMR */ + uint8_t codec[4]; /* 4 possible codecs for amr */ + int codecs; /* number of possible codecs */ + float ber_sum; /* sum of bit error rates */ + int ber_num; /* number of bit error rates */ + uint8_t ul_ft; /* current uplink FT index */ + uint8_t dl_ft; /* current downlink FT index */ + uint8_t ul_cmr; /* current uplink CMR index */ + uint8_t dl_cmr; /* current downlink CMR index */ + uint8_t amr_loop; /* if AMR loop is enabled */ + + /* TCH/H */ + uint8_t dl_ongoing_facch; /* FACCH/H on downlink */ + uint8_t ul_ongoing_facch; /* FACCH/H on uplink */ + + /* encryption */ + int ul_encr_algo; /* A5/x encry algo downlink */ + int dl_encr_algo; /* A5/x encry algo uplink */ + int ul_encr_key_len; + int dl_encr_key_len; + uint8_t ul_encr_key[MAX_A5_KEY_LEN]; + uint8_t dl_encr_key[MAX_A5_KEY_LEN]; + + /* measurements */ + struct { + uint8_t clock; /* cyclic clock counter */ + int8_t rssi[32]; /* last RSSI values */ + int rssi_count; /* received RSSI values */ + int rssi_valid_count; /* number of stored value */ + int rssi_got_burst; /* any burst received so far */ + int32_t toa256_sum; /* sum of TOA values (1/256 symbol) */ + int toa_num; /* number of TOA value */ + } meas; + + /* handover */ + uint8_t ho_rach_detect; /* if rach detection is on */ +}; + +struct l1sched_ts { + uint8_t mf_index; /* selected multiframe index */ + uint32_t mf_last_fn; /* last received frame number */ + uint8_t mf_period; /* period of multiframe */ + const struct trx_sched_frame *mf_frames; /* pointer to frame layout */ + + struct llist_head dl_prims; /* Queue primitives for TX */ + + /* Channel states for all logical channels */ + struct l1sched_chan_state chan_state[_TRX_CHAN_MAX]; +}; + +struct l1sched_trx { + struct gsm_bts_trx *trx; + struct l1sched_ts ts[TRX_NR_TS]; +}; + +struct l1sched_ts *l1sched_trx_get_ts(struct l1sched_trx *l1t, uint8_t tn); + +/*! \brief how many frame numbers in advance we should send bursts to PHY */ +extern uint32_t trx_clock_advance; +/*! \brief advance RTS.ind to L2 by that many clocks */ +extern uint32_t trx_rts_advance; +/*! \brief last frame number as received from PHY */ +extern uint32_t transceiver_last_fn; + + +/*! \brief Initialize the scheduler data structures */ +int trx_sched_init(struct l1sched_trx *l1t, struct gsm_bts_trx *trx); + +/*! \brief De-initialize the scheduler data structures */ +void trx_sched_exit(struct l1sched_trx *l1t); + +/*! \brief Handle a PH-DATA.req from L2 down to L1 */ +int trx_sched_ph_data_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap); + +/*! \brief Handle a PH-TCH.req from L2 down to L1 */ +int trx_sched_tch_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap); + +/*! \brief PHY informs us of new (current) GSM frame number */ +int trx_sched_clock(struct gsm_bts *bts, uint32_t fn); + +/*! \brief handle an UL burst received by PHY */ +int trx_sched_ul_burst(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + sbit_t *bits, uint16_t nbits, int8_t rssi, int16_t toa); + +/*! \brief set multiframe scheduler to given physical channel config */ +int trx_sched_set_pchan(struct l1sched_trx *l1t, uint8_t tn, + enum gsm_phys_chan_config pchan); + +/*! \brief set all matching logical channels active/inactive */ +int trx_sched_set_lchan(struct l1sched_trx *l1t, uint8_t chan_nr, uint8_t link_id, + int active); + +/*! \brief set mode of all matching logical channels to given mode(s) */ +int trx_sched_set_mode(struct l1sched_trx *l1t, uint8_t chan_nr, uint8_t rsl_cmode, + uint8_t tch_mode, int codecs, uint8_t codec0, uint8_t codec1, + uint8_t codec2, uint8_t codec3, uint8_t initial_codec, + uint8_t handover); + +/*! \brief set ciphering on given logical channels */ +int trx_sched_set_cipher(struct l1sched_trx *l1t, uint8_t chan_nr, int downlink, + int algo, uint8_t *key, int key_len); + +/* \brief close all logical channels and reset timeslots */ +void trx_sched_reset(struct l1sched_trx *l1t); + + +/* frame structures */ +struct trx_sched_frame { + /*! \brief downlink TRX channel type */ + enum trx_chan_type dl_chan; + /*! \brief downlink block ID */ + uint8_t dl_bid; + /*! \brief uplink TRX channel type */ + enum trx_chan_type ul_chan; + /*! \brief uplink block ID */ + uint8_t ul_bid; +}; + +/* multiframe structure */ +struct trx_sched_multiframe { + /*! \brief physical channel config (channel combination) */ + enum gsm_phys_chan_config pchan; + /*! \brief applies to which timeslots? */ + uint8_t slotmask; + /*! \brief repeats how many frames */ + uint8_t period; + /*! \brief pointer to scheduling structure */ + const struct trx_sched_frame *frames; + /*! \brief human-readable name */ + const char *name; +}; + +int find_sched_mframe_idx(enum gsm_phys_chan_config pchan, uint8_t tn); + +/*! Determine if given frame number contains SACCH (true) or other (false) burst */ +bool trx_sched_is_sacch_fn(struct gsm_bts_trx_ts *ts, uint32_t fn, bool uplink); +extern const struct trx_sched_multiframe trx_sched_multiframes[]; + +#endif /* TRX_SCHEDULER_H */ diff --git a/include/osmo-bts/scheduler_backend.h b/include/osmo-bts/scheduler_backend.h new file mode 100644 index 0000000..dbd9319 --- /dev/null +++ b/include/osmo-bts/scheduler_backend.h @@ -0,0 +1,94 @@ +#pragma once + +#define LOGL1S(subsys, level, l1t, tn, chan, fn, fmt, args ...) \ + LOGP(subsys, level, "%s %s %s: " fmt, \ + gsm_fn_as_gsmtime_str(fn), \ + gsm_ts_name(&(l1t)->trx->ts[tn]), \ + chan >=0 ? trx_chan_desc[chan].name : "", ## args) + +typedef int trx_sched_rts_func(struct l1sched_trx *l1t, uint8_t tn, + uint32_t fn, enum trx_chan_type chan); + +typedef ubit_t *trx_sched_dl_func(struct l1sched_trx *l1t, uint8_t tn, + uint32_t fn, enum trx_chan_type chan, + uint8_t bid, uint16_t *nbits); + +typedef int trx_sched_ul_func(struct l1sched_trx *l1t, uint8_t tn, + uint32_t fn, enum trx_chan_type chan, + uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256); + +struct trx_chan_desc { + /*! \brief Is this on a PDCH (PS) ? */ + int pdch; + /*! \brief TRX Channel Type */ + enum trx_chan_type chan; + /*! \brief Channel Number (like in RSL) */ + uint8_t chan_nr; + /*! \brief Link ID (like in RSL) */ + uint8_t link_id; + /*! \brief Human-readable name */ + const char *name; + /*! \brief function to call when we want to generate RTS.req to L2 */ + trx_sched_rts_func *rts_fn; + /*! \brief function to call when DATA.req received from L2 */ + trx_sched_dl_func *dl_fn; + /*! \brief function to call when burst received from PHY */ + trx_sched_ul_func *ul_fn; + /*! \brief is this channel automatically active at start? */ + int auto_active; +}; +extern const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX]; + +extern const ubit_t _sched_tsc[8][26]; +extern const ubit_t _sched_egprs_tsc[8][78]; +const ubit_t _sched_fcch_burst[148]; +const ubit_t _sched_sch_train[64]; + +struct msgb *_sched_dequeue_prim(struct l1sched_trx *l1t, int8_t tn, uint32_t fn, + enum trx_chan_type chan); + +int _sched_compose_ph_data_ind(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t *l2, + uint8_t l2_len, float rssi, + int16_t ta_offs_256bits, int16_t link_qual_cb, + uint16_t ber10k, + enum osmo_ph_pres_info_type presence_info); + +int _sched_compose_tch_ind(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t *tch, uint8_t tch_len); + +ubit_t *tx_idle_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits); +ubit_t *tx_fcch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits); +ubit_t *tx_sch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits); +ubit_t *tx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits); +ubit_t *tx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits); +ubit_t *tx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits); +ubit_t *tx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits); +int rx_rach_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256); +int rx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256); +int rx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256); +int rx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256); +int rx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256); + +const ubit_t *_sched_dl_burst(struct l1sched_trx *l1t, uint8_t tn, + uint32_t fn, uint16_t *nbits); +int _sched_rts(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn); +void _sched_act_rach_det(struct l1sched_trx *l1t, uint8_t tn, uint8_t ss, int activate); diff --git a/include/osmo-bts/signal.h b/include/osmo-bts/signal.h new file mode 100644 index 0000000..01d4099 --- /dev/null +++ b/include/osmo-bts/signal.h @@ -0,0 +1,19 @@ +#ifndef OSMO_BTS_SIGNAL_H +#define OSMO_BTS_SIGNAL_H + +#include + +enum sig_subsys { + SS_GLOBAL, + SS_FAIL, +}; + +enum signals_global { + S_NEW_SYSINFO, + S_NEW_OP_STATE, + S_NEW_NSE_ATTR, + S_NEW_CELL_ATTR, + S_NEW_NSVC_ATTR, +}; + +#endif diff --git a/include/osmo-bts/tx_power.h b/include/osmo-bts/tx_power.h new file mode 100644 index 0000000..21887c7 --- /dev/null +++ b/include/osmo-bts/tx_power.h @@ -0,0 +1,78 @@ +#pragma once + +#include +#include + +/* our unit is 'milli dB" or "milli dBm", i.e. 1/1000 of a dB(m) */ +#define to_mdB(x) (x * 1000) + +/* PA calibration table */ +struct pa_calibration { + int delta_mdB[1024]; /* gain delta at given ARFCN */ + /* FIXME: thermal calibration */ +}; + +/* representation of a RF power amplifier */ +struct power_amp { + /* nominal gain of the PA */ + int nominal_gain_mdB; + /* table with calibrated actual gain for each ARFCN */ + struct pa_calibration calib; +}; + +/* Transmit power related parameters of a transceiver */ +struct trx_power_params { + /* specified maximum output of TRX at full power, has to be + * initialized by BTS model at startup*/ + int trx_p_max_out_mdBm; + + /* intended current total system output power */ + int p_total_tgt_mdBm; + + /* actual current total system output power, filled in by tx_power code */ + int p_total_cur_mdBm; + + /* current temporary attenuation due to thermal management, + * set by thermal management code via control interface */ + int thermal_attenuation_mdB; + + /* external gain (+) or attenuation (-) added by the user, configured + * by the user via VTY */ + int user_gain_mdB; + + /* calibration table of internal PA */ + struct power_amp pa; + + /* calibration table of user PA */ + struct power_amp user_pa; + + /* power ramping related data */ + struct { + /* maximum initial Pout including all PAs */ + int max_initial_pout_mdBm; + /* temporary attenuation due to power ramping */ + int attenuation_mdB; + unsigned int step_size_mdB; + unsigned int step_interval_sec; + struct osmo_timer_list step_timer; + } ramp; +}; + +int get_p_max_out_mdBm(struct gsm_bts_trx *trx); + +int get_p_nominal_mdBm(struct gsm_bts_trx *trx); + +int get_p_target_mdBm(struct gsm_bts_trx *trx, uint8_t bs_power_ie); +int get_p_target_mdBm_lchan(struct gsm_lchan *lchan); + +int get_p_trxout_target_mdBm(struct gsm_bts_trx *trx, uint8_t bs_power_ie); +int get_p_trxout_target_mdBm_lchan(struct gsm_lchan *lchan); + +int get_p_trxout_actual_mdBm(struct gsm_bts_trx *trx, uint8_t bs_power_ie); +int get_p_trxout_actual_mdBm_lchan(struct gsm_lchan *lchan); + +int power_ramp_start(struct gsm_bts_trx *trx, int p_total_tgt_mdBm, int bypass); + +void power_trx_change_compl(struct gsm_bts_trx *trx, int p_trxout_cur_mdBm); + +int power_ramp_initial_power_mdBm(struct gsm_bts_trx *trx); diff --git a/include/osmo-bts/vty.h b/include/osmo-bts/vty.h new file mode 100644 index 0000000..d27acb5 --- /dev/null +++ b/include/osmo-bts/vty.h @@ -0,0 +1,32 @@ +#ifndef OSMOBTS_VTY_H +#define OSMOBTS_VTY_H + +#include +#include + +enum bts_vty_node { + /* PHY_NODE must come before BTS node to ensure the phy + * instances are created at the time the TRX nodes want to refer + * to them */ + PHY_NODE = _LAST_OSMOVTY_NODE + 1, + PHY_INST_NODE, + BTS_NODE, + TRX_NODE, +}; + +extern struct cmd_element ournode_exit_cmd; +extern struct cmd_element ournode_end_cmd; + +extern struct cmd_element cfg_bts_auto_band_cmd; +extern struct cmd_element cfg_bts_no_auto_band_cmd; + +struct phy_instance *vty_get_phy_instance(struct vty *vty, int phy_nr, int inst_nr); + +int bts_vty_go_parent(struct vty *vty); +int bts_vty_is_config_node(struct vty *vty, int node); + +int bts_vty_init(struct gsm_bts *bts, const struct log_info *cat); + +extern struct vty_app_info bts_vty_info; + +#endif diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..501591d --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,17 @@ +SUBDIRS = common osmo-bts-virtual osmo-bts-omldummy + +if ENABLE_SYSMOBTS +SUBDIRS += osmo-bts-sysmo +endif + +if ENABLE_TRX +SUBDIRS += osmo-bts-trx +endif + +if ENABLE_OCTPHY +SUBDIRS += osmo-bts-octphy +endif + +if ENABLE_LC15BTS +SUBDIRS += osmo-bts-litecell15 +endif diff --git a/src/common/Makefile.am b/src/common/Makefile.am new file mode 100644 index 0000000..113ff2f --- /dev/null +++ b/src/common/Makefile.am @@ -0,0 +1,17 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOCODEC_CFLAGS) +LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOCODEC_LIBS) + +if ENABLE_LC15BTS +AM_CFLAGS += -DENABLE_LC15BTS +endif + +noinst_LIBRARIES = libbts.a libl1sched.a +libbts_a_SOURCES = gsm_data_shared.c sysinfo.c logging.c abis.c oml.c bts.c \ + rsl.c vty.c paging.c measurement.c amr.c lchan.c \ + load_indication.c pcu_sock.c handover.c msg_utils.c \ + tx_power.c bts_ctrl_commands.c bts_ctrl_lookup.c \ + l1sap.c cbch.c power_control.c main.c phy_link.c \ + dtx_dl_amr_fsm.c scheduler_mframe.c + +libl1sched_a_SOURCES = scheduler.c diff --git a/src/common/abis.c b/src/common/abis.c new file mode 100644 index 0000000..6aa2f1d --- /dev/null +++ b/src/common/abis.c @@ -0,0 +1,276 @@ +/* Abis/IP interface routines utilizing libosmo-abis (Pablo) */ + +/* (C) 2011 by Andreas Eversberg + * (C) 2011-2013 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include "btsconfig.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +static struct gsm_bts *g_bts; + +int abis_oml_sendmsg(struct msgb *msg) +{ + struct gsm_bts *bts = msg->trx->bts; + + if (!bts->oml_link) { + llist_add_tail(&msg->list, &bts->oml_queue); + return 0; + } else { + /* osmo-bts uses msg->trx internally, but libosmo-abis uses + * the signalling link at msg->dst */ + msg->dst = bts->oml_link; + return abis_sendmsg(msg); + } +} + +static void drain_oml_queue(struct gsm_bts *bts) +{ + struct msgb *msg, *msg2; + + llist_for_each_entry_safe(msg, msg2, &bts->oml_queue, list) { + /* osmo-bts uses msg->trx internally, but libosmo-abis uses + * the signalling link at msg->dst */ + llist_del(&msg->list); + msg->dst = bts->oml_link; + abis_sendmsg(msg); + } +} + +int abis_bts_rsl_sendmsg(struct msgb *msg) +{ + OSMO_ASSERT(msg->trx); + + if (msg->trx->bts->variant == BTS_OSMO_OMLDUMMY) { + msgb_free(msg); + return 0; + } + + /* osmo-bts uses msg->trx internally, but libosmo-abis uses + * the signalling link at msg->dst */ + msg->dst = msg->trx->rsl_link; + return abis_sendmsg(msg); +} + +static struct e1inp_sign_link *sign_link_up(void *unit, struct e1inp_line *line, + enum e1inp_sign_type type) +{ + struct e1inp_sign_link *sign_link = NULL; + struct gsm_bts_trx *trx; + int trx_nr; + + switch (type) { + case E1INP_SIGN_OML: + LOGP(DABIS, LOGL_INFO, "OML Signalling link up\n"); + e1inp_ts_config_sign(&line->ts[E1INP_SIGN_OML-1], line); + sign_link = g_bts->oml_link = + e1inp_sign_link_create(&line->ts[E1INP_SIGN_OML-1], + E1INP_SIGN_OML, NULL, 255, 0); + drain_oml_queue(g_bts); + sign_link->trx = g_bts->c0; + bts_link_estab(g_bts); + break; + default: + trx_nr = type - E1INP_SIGN_RSL; + LOGP(DABIS, LOGL_INFO, "RSL Signalling link for TRX%d up\n", + trx_nr); + trx = gsm_bts_trx_num(g_bts, trx_nr); + if (!trx) { + LOGP(DABIS, LOGL_ERROR, "TRX%d does not exixt!\n", + trx_nr); + break; + } + e1inp_ts_config_sign(&line->ts[type-1], line); + sign_link = trx->rsl_link = + e1inp_sign_link_create(&line->ts[type-1], + E1INP_SIGN_RSL, NULL, 0, 0); + sign_link->trx = trx; + trx_link_estab(trx); + break; + } + + return sign_link; +} + +static void sign_link_down(struct e1inp_line *line) +{ + struct gsm_bts_trx *trx; + LOGP(DABIS, LOGL_ERROR, "Signalling link down\n"); + + /* First remove the OML signalling link */ + if (g_bts->oml_link) + e1inp_sign_link_destroy(g_bts->oml_link); + g_bts->oml_link = NULL; + + /* Then iterate over the RSL signalling links */ + llist_for_each_entry(trx, &g_bts->trx_list, list) { + if (trx->rsl_link) { + e1inp_sign_link_destroy(trx->rsl_link); + trx->rsl_link = NULL; + } + } + + bts_model_abis_close(g_bts); +} + + +/* callback for incoming mesages from A-bis/IP */ +static int sign_link_cb(struct msgb *msg) +{ + struct e1inp_sign_link *link = msg->dst; + + /* osmo-bts code assumes msg->trx is set, but libosmo-abis works + * with the sign_link stored in msg->dst, so we have to convert + * here */ + msg->trx = link->trx; + + switch (link->type) { + case E1INP_SIGN_OML: + down_oml(link->trx->bts, msg); + break; + case E1INP_SIGN_RSL: + down_rsl(link->trx, msg); + break; + default: + msgb_free(msg); + break; + } + + return 0; +} + +uint32_t get_signlink_remote_ip(struct e1inp_sign_link *link) +{ + int fd = link->ts->driver.ipaccess.fd.fd; + struct sockaddr_in sin; + socklen_t slen = sizeof(sin); + int rc; + + rc = getpeername(fd, (struct sockaddr *)&sin, &slen); + if (rc < 0) { + LOGP(DOML, LOGL_ERROR, "Cannot determine remote IP Addr: %s\n", + strerror(errno)); + return 0; + } + + /* we assume that the soket is AF_INET. As Abis/IP contains + * lots of hard-coded IPv4 addresses, this safe */ + OSMO_ASSERT(sin.sin_family == AF_INET); + + return ntohl(sin.sin_addr.s_addr); +} + + +static int inp_s_cbfn(unsigned int subsys, unsigned int signal, + void *hdlr_data, void *signal_data) +{ + if (subsys != SS_L_INPUT) + return 0; + + DEBUGP(DABIS, "Input Signal %u received\n", signal); + + return 0; +} + + +static struct ipaccess_unit bts_dev_info = { + .unit_name = "sysmoBTS", + .equipvers = "", /* FIXME: read this from hw */ + .swversion = PACKAGE_VERSION, + .location1 = "", + .location2 = "", + .serno = "", +}; + +static struct e1inp_line_ops line_ops = { + .cfg = { + .ipa = { + .role = E1INP_LINE_R_BTS, + .dev = &bts_dev_info, + }, + }, + .sign_link_up = sign_link_up, + .sign_link_down = sign_link_down, + .sign_link = sign_link_cb, +}; + +void abis_init(struct gsm_bts *bts) +{ + g_bts = bts; + + oml_init(&bts->mo); + libosmo_abis_init(NULL); + + osmo_signal_register_handler(SS_L_INPUT, &inp_s_cbfn, bts); +} + +struct e1inp_line *abis_open(struct gsm_bts *bts, char *dst_host, + char *model_name) +{ + struct e1inp_line *line; + + /* patch in various data from VTY and othe sources */ + line_ops.cfg.ipa.addr = dst_host; + osmo_get_macaddr(bts_dev_info.mac_addr, "eth0"); + bts_dev_info.site_id = bts->ip_access.site_id; + bts_dev_info.bts_id = bts->ip_access.bts_id; + bts_dev_info.unit_name = model_name; + if (bts->description) + bts_dev_info.unit_name = bts->description; + bts_dev_info.location2 = model_name; + + line = e1inp_line_find(0); + if (!line) + line = e1inp_line_create(0, "ipa"); + if (!line) + return NULL; + e1inp_line_bind_ops(line, &line_ops); + + /* This will open the OML connection now */ + if (e1inp_line_update(line) < 0) + return NULL; + + return line; +} diff --git a/src/common/amr.c b/src/common/amr.c new file mode 100644 index 0000000..05d1aaa --- /dev/null +++ b/src/common/amr.c @@ -0,0 +1,170 @@ +#include +#include + +#include + +#include +#include + +void amr_log_mr_conf(int ss, int logl, const char *pfx, + struct amr_multirate_conf *amr_mrc) +{ + int i; + + LOGP(ss, logl, "%s AMR MR Conf: num_modes=%u", + pfx, amr_mrc->num_modes); + + for (i = 0; i < amr_mrc->num_modes; i++) + LOGPC(ss, logl, ", mode[%u] = %u/%u/%u", + i, amr_mrc->bts_mode[i].mode, + amr_mrc->bts_mode[i].threshold, + amr_mrc->bts_mode[i].hysteresis); + LOGPC(ss, logl, "\n"); +} + +static inline int get_amr_mode_idx(const struct amr_multirate_conf *amr_mrc, + uint8_t cmi) +{ + unsigned int i; + for (i = 0; i < amr_mrc->num_modes; i++) { + if (amr_mrc->bts_mode[i].mode == cmi) + return i; + } + return -EINVAL; +} + +static inline uint8_t set_cmr_mode_idx(const struct amr_multirate_conf *amr_mrc, + uint8_t cmr) +{ + int rc; + + /* Codec Mode Request is in upper 4 bits of RTP payload header, + * and we simply copy the CMR into the CMC */ + if (cmr == 0xF) { + /* FIXME: we need some state about the last codec mode */ + return 0; + } + + rc = get_amr_mode_idx(amr_mrc, cmr); + if (rc < 0) { + /* FIXME: we need some state about the last codec mode */ + LOGP(DRTP, LOGL_INFO, "RTP->L1: overriding CMR %u\n", cmr); + return 0; + } + return rc; +} + +static inline uint8_t set_cmi_mode_idx(const struct amr_multirate_conf *amr_mrc, + uint8_t cmi) +{ + int rc = get_amr_mode_idx(amr_mrc, cmi); + if (rc < 0) { + LOGP(DRTP, LOGL_ERROR, "AMR CMI %u not part of AMR MR set\n", + cmi); + return 0; + } + return rc; +} + +void amr_set_mode_pref(uint8_t *data, const struct amr_multirate_conf *amr_mrc, + uint8_t cmi, uint8_t cmr) +{ + data[0] = set_cmi_mode_idx(amr_mrc, cmi); + data[1] = set_cmr_mode_idx(amr_mrc, cmr); +} + +/* parse a GSM 04.08 MultiRate Config IE (10.5.2.21aa) in a more + * comfortable internal data structure */ +int amr_parse_mr_conf(struct amr_multirate_conf *amr_mrc, + const uint8_t *mr_conf, unsigned int len) +{ + uint8_t mr_version = mr_conf[0] >> 5; + uint8_t num_codecs = 0; + int i, j = 0; + + if (mr_version != 1) { + LOGP(DRSL, LOGL_ERROR, "AMR Multirate Version %u unknown\n", + mr_version); + goto ret_einval; + } + + /* check number of active codecs */ + for (i = 0; i < 8; i++) { + if (mr_conf[1] & (1 << i)) + num_codecs++; + } + + /* check for minimum length */ + if (num_codecs == 0 || + (num_codecs == 1 && len < 2) || + (num_codecs == 2 && len < 4) || + (num_codecs == 3 && len < 5) || + (num_codecs == 4 && len < 6) || + (num_codecs > 4)) { + LOGP(DRSL, LOGL_ERROR, "AMR Multirate with %u modes len=%u " + "not possible\n", num_codecs, len); + goto ret_einval; + } + + /* copy the first two octets of the IE */ + amr_mrc->gsm48_ie[0] = mr_conf[0]; + amr_mrc->gsm48_ie[1] = mr_conf[1]; + + amr_mrc->num_modes = num_codecs; + + for (i = 0; i < 8; i++) { + if (mr_conf[1] & (1 << i)) { + amr_mrc->bts_mode[j++].mode = i; + } + } + + if (num_codecs >= 2) { + amr_mrc->bts_mode[0].threshold = mr_conf[1] & 0x3F; + amr_mrc->bts_mode[0].hysteresis = mr_conf[2] >> 4; + } + if (num_codecs >= 3) { + amr_mrc->bts_mode[1].threshold = + ((mr_conf[2] & 0xF) << 2) | (mr_conf[3] >> 6); + amr_mrc->bts_mode[1].hysteresis = (mr_conf[3] >> 2) & 0xF; + } + if (num_codecs >= 4) { + amr_mrc->bts_mode[2].threshold = + ((mr_conf[3] & 0x3) << 4) | (mr_conf[4] >> 4); + amr_mrc->bts_mode[2].hysteresis = mr_conf[4] & 0xF; + } + + return num_codecs; + +ret_einval: + return -EINVAL; +} + + +/*! \brief determine AMR initial codec mode for given logical channel + * \returns integer between 0..3 for AMR codce mode 1..4 */ +unsigned int amr_get_initial_mode(struct gsm_lchan *lchan) +{ + struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr; + struct gsm48_multi_rate_conf *mr_conf = + (struct gsm48_multi_rate_conf *) amr_mrc->gsm48_ie; + + if (mr_conf->icmi) { + /* initial mode given, coding in TS 05.09 3.4.1 */ + return mr_conf->smod; + } else { + /* implicit rule according to TS 05.09 Chapter 3.4.3 */ + switch (amr_mrc->num_modes) { + case 2: + case 3: + /* return the most robust */ + return 0; + case 4: + /* return the second-most robust */ + return 1; + case 1: + default: + /* return the only mode we have */ + return 0; + } + } +} diff --git a/src/common/bts.c b/src/common/bts.c new file mode 100644 index 0000000..74630cc --- /dev/null +++ b/src/common/bts.c @@ -0,0 +1,740 @@ +/* BTS support code common to all supported BTS models */ + +/* (C) 2011 by Andreas Eversberg + * (C) 2011 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MIN_QUAL_RACH 5.0f /* at least 5 dB C/I */ +#define MIN_QUAL_NORM -0.5f /* at least -1 dB C/I */ + +static void bts_update_agch_max_queue_length(struct gsm_bts *bts); + +struct gsm_network bts_gsmnet = { + .bts_list = { &bts_gsmnet.bts_list, &bts_gsmnet.bts_list }, + .num_bts = 0, +}; + +void *tall_bts_ctx; + +/* Table 3.1 TS 04.08: Values of parameter S */ +static const uint8_t tx_integer[] = { + 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 20, 25, 32, 50, +}; + +static const uint8_t s_values[][2] = { + { 55, 41 }, { 76, 52 }, { 109, 58 }, { 163, 86 }, { 217, 115 }, +}; + +static int bts_signal_cbfn(unsigned int subsys, unsigned int signal, + void *hdlr_data, void *signal_data) +{ + if (subsys == SS_GLOBAL && signal == S_NEW_SYSINFO) { + struct gsm_bts *bts = signal_data; + + bts_update_agch_max_queue_length(bts); + } + return 0; +} + +static const struct rate_ctr_desc bts_ctr_desc[] = { + [BTS_CTR_PAGING_RCVD] = {"paging:rcvd", "Received paging requests (Abis)"}, + [BTS_CTR_PAGING_DROP] = {"paging:drop", "Dropped paging requests (Abis)"}, + [BTS_CTR_PAGING_SENT] = {"paging:sent", "Sent paging requests (Um)"}, + + [BTS_CTR_RACH_RCVD] = {"rach:rcvd", "Received RACH requests (Um)"}, + [BTS_CTR_RACH_DROP] = {"rach:drop", "Dropped RACH requests (Um)"}, + [BTS_CTR_RACH_HO] = {"rach:handover", "Received RACH requests (Handover)"}, + [BTS_CTR_RACH_CS] = {"rach:cs", "Received RACH requests (CS/Abis)"}, + [BTS_CTR_RACH_PS] = {"rach:ps", "Received RACH requests (PS/PCU)"}, + + [BTS_CTR_AGCH_RCVD] = {"agch:rcvd", "Received AGCH requests (Abis)"}, + [BTS_CTR_AGCH_SENT] = {"agch:sent", "Sent AGCH requests (Abis)"}, + [BTS_CTR_AGCH_DELETED] = {"agch:delete", "Sent AGCH DELETE IND (Abis)"}, +}; +static const struct rate_ctr_group_desc bts_ctrg_desc = { + "bts", + "base transceiver station", + OSMO_STATS_CLASS_GLOBAL, + ARRAY_SIZE(bts_ctr_desc), + bts_ctr_desc +}; + +/* Initialize the BTS (and TRX) data structures, called before config + * file reading */ +int bts_init(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + int rc, i; + static int initialized = 0; + void *tall_rtp_ctx; + + /* add to list of BTSs */ + llist_add_tail(&bts->list, &bts_gsmnet.bts_list); + + bts->band = GSM_BAND_1800; + + INIT_LLIST_HEAD(&bts->agch_queue.queue); + bts->agch_queue.length = 0; + + bts->ctrs = rate_ctr_group_alloc(bts, &bts_ctrg_desc, bts->nr); + + /* enable management with default levels, + * raise threshold to GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DISABLE to + * disable this feature. + */ + bts->agch_queue.low_level = GSM_BTS_AGCH_QUEUE_LOW_LEVEL_DEFAULT; + bts->agch_queue.high_level = GSM_BTS_AGCH_QUEUE_HIGH_LEVEL_DEFAULT; + bts->agch_queue.thresh_level = GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DEFAULT; + + /* configurable via VTY */ + bts->paging_state = paging_init(bts, 200, 0); + bts->ul_power_target = -75; /* dBm default */ + bts->rtp_jitter_adaptive = false; + + /* configurable via OML */ + bts->load.ccch.load_ind_period = 112; + load_timer_start(bts); + bts->rtp_jitter_buf_ms = 100; + bts->max_ta = 63; + bts->ny1 = 4; + bts->t3105_ms = 300; + bts->min_qual_rach = MIN_QUAL_RACH; + bts->min_qual_norm = MIN_QUAL_NORM; + bts->max_ber10k_rach = 1707; /* 7 of 41 bits is Eb/N0 of 0 dB = 0.1707 */ + bts->pcu.sock_path = talloc_strdup(bts, PCU_SOCK_DEFAULT); + for (i = 0; i < ARRAY_SIZE(bts->t200_ms); i++) + bts->t200_ms[i] = oml_default_t200_ms[i]; + + /* default RADIO_LINK_TIMEOUT */ + bts->radio_link_timeout = 32; + + /* Start with the site manager */ + oml_mo_state_init(&bts->site_mgr.mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); + + /* set BTS to dependency */ + oml_mo_state_init(&bts->mo, -1, NM_AVSTATE_DEPENDENCY); + oml_mo_state_init(&bts->gprs.nse.mo, -1, NM_AVSTATE_DEPENDENCY); + oml_mo_state_init(&bts->gprs.cell.mo, -1, NM_AVSTATE_DEPENDENCY); + oml_mo_state_init(&bts->gprs.nsvc[0].mo, -1, NM_AVSTATE_DEPENDENCY); + oml_mo_state_init(&bts->gprs.nsvc[1].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE); + + /* initialize bts data structure */ + llist_for_each_entry(trx, &bts->trx_list, list) { + struct trx_power_params *tpp = &trx->power_params; + int i; + + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + int k; + + for (k = 0; k < ARRAY_SIZE(ts->lchan); k++) { + struct gsm_lchan *lchan = &ts->lchan[k]; + INIT_LLIST_HEAD(&lchan->dl_tch_queue); + } + } + /* Default values for the power adjustments */ + tpp->ramp.max_initial_pout_mdBm = to_mdB(0); + tpp->ramp.step_size_mdB = to_mdB(2); + tpp->ramp.step_interval_sec = 1; + } + + /* allocate a talloc pool for ORTP to ensure it doesn't have to go back + * to the libc malloc all the time */ + tall_rtp_ctx = talloc_pool(tall_bts_ctx, 262144); + osmo_rtp_init(tall_rtp_ctx); + + rc = bts_model_init(bts); + if (rc < 0) { + llist_del(&bts->list); + return rc; + } + + bts_gsmnet.num_bts++; + + if (!initialized) { + osmo_signal_register_handler(SS_GLOBAL, bts_signal_cbfn, NULL); + initialized = 1; + } + + INIT_LLIST_HEAD(&bts->smscb_state.queue); + INIT_LLIST_HEAD(&bts->oml_queue); + + /* register DTX DL FSM */ + rc = osmo_fsm_register(&dtx_dl_amr_fsm); + OSMO_ASSERT(rc == 0); + + return rc; +} + +static void shutdown_timer_cb(void *data) +{ + fprintf(stderr, "Shutdown timer expired\n"); + exit(42); +} + +static struct osmo_timer_list shutdown_timer = { + .cb = &shutdown_timer_cb, +}; + +void bts_shutdown(struct gsm_bts *bts, const char *reason) +{ + struct gsm_bts_trx *trx; + + if (osmo_timer_pending(&shutdown_timer)) { + LOGP(DOML, LOGL_NOTICE, + "BTS is already being shutdown.\n"); + return; + } + + LOGP(DOML, LOGL_NOTICE, "Shutting down BTS %u, Reason %s\n", + bts->nr, reason); + + llist_for_each_entry_reverse(trx, &bts->trx_list, list) { + bts_model_trx_deact_rf(trx); + bts_model_trx_close(trx); + } + + /* shedule a timer to make sure select loop logic can run again + * to dispatch any pending primitives */ + osmo_timer_schedule(&shutdown_timer, 3, 0); +} + +/* main link is established, send status report */ +int bts_link_estab(struct gsm_bts *bts) +{ + int i, j; + + LOGP(DSUM, LOGL_INFO, "Main link established, sending Status'.\n"); + + /* BTS and SITE MGR are EANBLED, BTS is DEPENDENCY */ + oml_tx_state_changed(&bts->site_mgr.mo); + oml_tx_state_changed(&bts->mo); + + /* those should all be in DEPENDENCY */ + oml_tx_state_changed(&bts->gprs.nse.mo); + oml_tx_state_changed(&bts->gprs.cell.mo); + oml_tx_state_changed(&bts->gprs.nsvc[0].mo); + oml_tx_state_changed(&bts->gprs.nsvc[1].mo); + + /* All other objects start off-line until the BTS Model code says otherwise */ + for (i = 0; i < bts->num_trx; i++) { + struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, i); + + oml_tx_state_changed(&trx->mo); + oml_tx_state_changed(&trx->bb_transc.mo); + + for (j = 0; j < ARRAY_SIZE(trx->ts); j++) { + struct gsm_bts_trx_ts *ts = &trx->ts[j]; + + oml_tx_state_changed(&ts->mo); + } + } + + return bts_model_oml_estab(bts); +} + +/* RSL link is established, send status report */ +int trx_link_estab(struct gsm_bts_trx *trx) +{ + struct e1inp_sign_link *link = trx->rsl_link; + uint8_t radio_state = link ? NM_OPSTATE_ENABLED : NM_OPSTATE_DISABLED; + int rc; + + LOGP(DSUM, LOGL_INFO, "RSL link (TRX %02x) state changed to %s, sending Status'.\n", + trx->nr, link ? "up" : "down"); + + oml_mo_state_chg(&trx->mo, radio_state, NM_AVSTATE_OK); + + if (link) + rc = rsl_tx_rf_res(trx); + else + rc = bts_model_trx_deact_rf(trx); + if (rc < 0) + oml_fail_rep(OSMO_EVT_MAJ_RSL_FAIL, + link ? "Failed to establish RSL link (%d)" : + "Failed to deactivate RF (%d)", rc); + return 0; +} + +/* set the availability of the TRX (used by PHY driver) */ +int trx_set_available(struct gsm_bts_trx *trx, int avail) +{ + int tn; + + LOGP(DSUM, LOGL_INFO, "TRX(%d): Setting available = %d\n", + trx->nr, avail); + if (avail) { + /* FIXME: This needs to be sorted out */ +#if 0 + oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE); + oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OFF_LINE); + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) + oml_mo_state_chg(&trx->ts[tn].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY); +#endif + } else { + oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED); + oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_NOT_INSTALLED); + + for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++) + oml_mo_state_chg(&trx->ts[tn].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_NOT_INSTALLED); + } + return 0; +} + +int lchan_init_lapdm(struct gsm_lchan *lchan) +{ + struct lapdm_channel *lc = &lchan->lapdm_ch; + + lapdm_channel_init(lc, LAPDM_MODE_BTS); + lapdm_channel_set_flags(lc, LAPDM_ENT_F_POLLING_ONLY); + lapdm_channel_set_l1(lc, NULL, lchan); + lapdm_channel_set_l3(lc, lapdm_rll_tx_cb, lchan); + oml_set_lchan_t200(lchan); + + return 0; +} + +#define CCCH_RACH_RATIO_COMBINED256 (256*1/9) +#define CCCH_RACH_RATIO_SEPARATE256 (256*10/55) + +int bts_agch_max_queue_length(int T, int bcch_conf) +{ + int S, ccch_rach_ratio256, i; + int T_group = 0; + int is_ccch_comb = 0; + + if (bcch_conf == RSL_BCCH_CCCH_CONF_1_C) + is_ccch_comb = 1; + + /* + * The calculation is based on the ratio of the number RACH slots and + * CCCH blocks per time: + * Lmax = (T + 2*S) / R_RACH * R_CCCH + * where + * T3126_min = (T + 2*S) / R_RACH, as defined in GSM 04.08, 11.1.1 + * R_RACH is the RACH slot rate (e.g. RACHs per multiframe) + * R_CCCH is the CCCH block rate (same time base like R_RACH) + * S and T are defined in GSM 04.08, 3.3.1.1.2 + * The ratio is mainly influenced by the downlink only channels + * (BCCH, FCCH, SCH, CBCH) that can not be used for CCCH. + * An estimation with an error of < 10% is used: + * ~ 1/9 if CCCH is combined with SDCCH, and + * ~ 1/5.5 otherwise. + */ + ccch_rach_ratio256 = is_ccch_comb ? + CCCH_RACH_RATIO_COMBINED256 : + CCCH_RACH_RATIO_SEPARATE256; + + for (i = 0; i < ARRAY_SIZE(tx_integer); i++) { + if (tx_integer[i] == T) { + T_group = i % 5; + break; + } + } + S = s_values[T_group][is_ccch_comb]; + + return (T + 2 * S) * ccch_rach_ratio256 / 256; +} + +static void bts_update_agch_max_queue_length(struct gsm_bts *bts) +{ + struct gsm48_system_information_type_3 *si3; + int old_max_length = bts->agch_queue.max_length; + + if (!(bts->si_valid & (1<agch_queue.max_length = + bts_agch_max_queue_length(si3->rach_control.tx_integer, + si3->control_channel_desc.ccch_conf); + + if (bts->agch_queue.max_length != old_max_length) + LOGP(DRSL, LOGL_INFO, "Updated AGCH max queue length to %d\n", + bts->agch_queue.max_length); +} + +#define REQ_REFS_PER_IMM_ASS_REJ 4 +static int store_imm_ass_rej_refs(struct gsm48_imm_ass_rej *rej, + struct gsm48_req_ref *req_refs, + uint8_t *wait_inds, + int count) +{ + switch (count) { + case 0: + /* TODO: Warning ? */ + return 0; + default: + count = 4; + rej->req_ref4 = req_refs[3]; + rej->wait_ind4 = wait_inds[3]; + /* fall through */ + case 3: + rej->req_ref3 = req_refs[2]; + rej->wait_ind3 = wait_inds[2]; + /* fall through */ + case 2: + rej->req_ref2 = req_refs[1]; + rej->wait_ind2 = wait_inds[1]; + /* fall through */ + case 1: + rej->req_ref1 = req_refs[0]; + rej->wait_ind1 = wait_inds[0]; + break; + } + + switch (count) { + case 1: + rej->req_ref2 = req_refs[0]; + rej->wait_ind2 = wait_inds[0]; + /* fall through */ + case 2: + rej->req_ref3 = req_refs[0]; + rej->wait_ind3 = wait_inds[0]; + /* fall through */ + case 3: + rej->req_ref4 = req_refs[0]; + rej->wait_ind4 = wait_inds[0]; + /* fall through */ + default: + break; + } + + return count; +} + +static int extract_imm_ass_rej_refs(struct gsm48_imm_ass_rej *rej, + struct gsm48_req_ref *req_refs, + uint8_t *wait_inds) +{ + int count = 0; + req_refs[count] = rej->req_ref1; + wait_inds[count] = rej->wait_ind1; + count++; + + if (memcmp(&rej->req_ref1, &rej->req_ref2, sizeof(rej->req_ref2))) { + req_refs[count] = rej->req_ref2; + wait_inds[count] = rej->wait_ind2; + count++; + } + + if (memcmp(&rej->req_ref1, &rej->req_ref3, sizeof(rej->req_ref3)) && + memcmp(&rej->req_ref2, &rej->req_ref3, sizeof(rej->req_ref3))) { + req_refs[count] = rej->req_ref3; + wait_inds[count] = rej->wait_ind3; + count++; + } + + if (memcmp(&rej->req_ref1, &rej->req_ref4, sizeof(rej->req_ref4)) && + memcmp(&rej->req_ref2, &rej->req_ref4, sizeof(rej->req_ref4)) && + memcmp(&rej->req_ref3, &rej->req_ref4, sizeof(rej->req_ref4))) { + req_refs[count] = rej->req_ref4; + wait_inds[count] = rej->wait_ind4; + count++; + } + + return count; +} + +static int try_merge_imm_ass_rej(struct gsm48_imm_ass_rej *old_rej, + struct gsm48_imm_ass_rej *new_rej) +{ + struct gsm48_req_ref req_refs[2 * REQ_REFS_PER_IMM_ASS_REJ]; + uint8_t wait_inds[2 * REQ_REFS_PER_IMM_ASS_REJ]; + int count = 0; + int stored = 0; + + if (new_rej->msg_type != GSM48_MT_RR_IMM_ASS_REJ) + return 0; + if (old_rej->msg_type != GSM48_MT_RR_IMM_ASS_REJ) + return 0; + + /* GSM 08.58, 5.7 + * -> The BTS may combine serveral IMM.ASS.REJ messages + * -> Identical request refs in one message may be squeezed + * + * GSM 04.08, 9.1.20.2 + * -> Request ref and wait ind are duplicated to fill the message + */ + + /* Extract all entries */ + count = extract_imm_ass_rej_refs(old_rej, + &req_refs[count], &wait_inds[count]); + if (count == REQ_REFS_PER_IMM_ASS_REJ) + return 0; + + count += extract_imm_ass_rej_refs(new_rej, + &req_refs[count], &wait_inds[count]); + + /* Store entries into old message */ + stored = store_imm_ass_rej_refs(old_rej, + &req_refs[stored], &wait_inds[stored], + count); + count -= stored; + if (count == 0) + return 1; + + /* Store remaining entries into new message */ + stored += store_imm_ass_rej_refs(new_rej, + &req_refs[stored], &wait_inds[stored], + count); + return 0; +} + +int bts_agch_enqueue(struct gsm_bts *bts, struct msgb *msg) +{ + int hard_limit = 1000; + struct gsm48_imm_ass_rej *imm_ass_cmd = msgb_l3(msg); + + if (bts->agch_queue.length > hard_limit) { + LOGP(DSUM, LOGL_ERROR, + "AGCH: too many messages in queue, " + "refusing message type 0x%02x, length = %d/%d\n", + ((struct gsm48_imm_ass *)msgb_l3(msg))->msg_type, + bts->agch_queue.length, bts->agch_queue.max_length); + + bts->agch_queue.rejected_msgs++; + return -ENOMEM; + } + + if (bts->agch_queue.length > 0) { + struct msgb *last_msg = + llist_entry(bts->agch_queue.queue.prev, struct msgb, list); + struct gsm48_imm_ass_rej *last_imm_ass_rej = msgb_l3(last_msg); + + if (try_merge_imm_ass_rej(last_imm_ass_rej, imm_ass_cmd)) { + bts->agch_queue.merged_msgs++; + msgb_free(msg); + return 0; + } + } + + msgb_enqueue(&bts->agch_queue.queue, msg); + bts->agch_queue.length++; + + return 0; +} + +struct msgb *bts_agch_dequeue(struct gsm_bts *bts) +{ + struct msgb *msg = msgb_dequeue(&bts->agch_queue.queue); + if (!msg) + return NULL; + + bts->agch_queue.length--; + return msg; +} + +/* + * Remove lower prio messages if the queue has grown too long. + * + * \return 0 iff the number of messages in the queue would fit into the AGCH + * reserved part of the CCCH. + */ +static void compact_agch_queue(struct gsm_bts *bts) +{ + struct msgb *msg, *msg2; + int max_len, slope, offs; + int level_low = bts->agch_queue.low_level; + int level_high = bts->agch_queue.high_level; + int level_thres = bts->agch_queue.thresh_level; + + max_len = bts->agch_queue.max_length; + + if (max_len == 0) + max_len = 1; + + if (bts->agch_queue.length < max_len * level_thres / 100) + return; + + /* p^ + * 1+ /''''' + * | / + * | / + * 0+---/--+----+--> Q length + * low high max_len + */ + + offs = max_len * level_low / 100; + if (level_high > level_low) + slope = 0x10000 * 100 / (level_high - level_low); + else + slope = 0x10000 * max_len; /* p_drop >= 1 if len > offs */ + + llist_for_each_entry_safe(msg, msg2, &bts->agch_queue.queue, list) { + struct gsm48_imm_ass *imm_ass_cmd = msgb_l3(msg); + int p_drop; + + if (imm_ass_cmd->msg_type != GSM48_MT_RR_IMM_ASS_REJ) + return; + + /* IMMEDIATE ASSIGN REJECT */ + + p_drop = (bts->agch_queue.length - offs) * slope / max_len; + + if ((random() & 0xffff) >= p_drop) + return; + + llist_del(&msg->list); + bts->agch_queue.length--; + msgb_free(msg); + + bts->agch_queue.dropped_msgs++; + } + return; +} + +int bts_ccch_copy_msg(struct gsm_bts *bts, uint8_t *out_buf, struct gsm_time *gt, + int is_ag_res) +{ + struct msgb *msg = NULL; + int rc = 0; + int is_empty = 1; + + /* Do queue house keeping. + * This needs to be done every time a CCCH message is requested, since + * the queue max length is calculated based on the CCCH block rate and + * PCH messages also reduce the drain of the AGCH queue. + */ + compact_agch_queue(bts); + + /* Check for paging messages first if this is PCH */ + if (!is_ag_res) + rc = paging_gen_msg(bts->paging_state, out_buf, gt, &is_empty); + + /* Check whether the block may be overwritten */ + if (!is_empty) + return rc; + + msg = bts_agch_dequeue(bts); + if (!msg) + return rc; + + rate_ctr_inc2(bts->ctrs, BTS_CTR_AGCH_SENT); + + /* Copy AGCH message */ + memcpy(out_buf, msgb_l3(msg), msgb_l3len(msg)); + rc = msgb_l3len(msg); + msgb_free(msg); + + if (is_ag_res) + bts->agch_queue.agch_msgs++; + else + bts->agch_queue.pch_msgs++; + + return rc; +} + +int bts_supports_cipher(struct gsm_bts *bts, int rsl_cipher) +{ + int sup; + + if (rsl_cipher < 1 || rsl_cipher > 8) + return -ENOTSUP; + + /* No encryption is always supported */ + if (rsl_cipher == 1) + return 1; + + sup = (1 << (rsl_cipher - 2)) & bts->support.ciphers; + return sup > 0; +} + +int trx_ms_pwr_ctrl_is_osmo(struct gsm_bts_trx *trx) +{ + return trx->ms_power_control == 1; +} + +struct gsm_time *get_time(struct gsm_bts *bts) +{ + return &bts->gsm_time; +} + +int bts_supports_cm(struct gsm_bts *bts, enum gsm_phys_chan_config pchan, + enum gsm48_chan_mode cm) +{ + enum gsm_bts_features feature = _NUM_BTS_FEAT; + + /* Before the requested pchan/cm combination can be checked, we need to + * convert it to a feature identifier we can check */ + if (pchan == GSM_PCHAN_TCH_F) { + switch(cm) { + case GSM48_CMODE_SPEECH_V1: + feature = BTS_FEAT_SPEECH_F_V1; + break; + case GSM48_CMODE_SPEECH_EFR: + feature = BTS_FEAT_SPEECH_F_EFR; + break; + case GSM48_CMODE_SPEECH_AMR: + feature = BTS_FEAT_SPEECH_F_AMR; + break; + default: + /* Invalid speech codec type => Not supported! */ + return 0; + } + } else if (pchan == GSM_PCHAN_TCH_H) { + switch(cm) { + case GSM48_CMODE_SPEECH_V1: + feature = BTS_FEAT_SPEECH_H_V1; + break; + case GSM48_CMODE_SPEECH_AMR: + feature = BTS_FEAT_SPEECH_H_AMR; + break; + default: + /* Invalid speech codec type => Not supported! */ + return 0; + } + } + + /* Check if the feature is supported by this BTS */ + if (gsm_bts_has_feature(bts, feature)) + return 1; + + return 0; +} diff --git a/src/common/bts_ctrl_commands.c b/src/common/bts_ctrl_commands.c new file mode 100644 index 0000000..4efb4ee --- /dev/null +++ b/src/common/bts_ctrl_commands.c @@ -0,0 +1,93 @@ +/* Control Interface for osmo-bts */ + +/* (C) 2014 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +CTRL_CMD_DEFINE(therm_att, "thermal-attenuation"); +static int get_therm_att(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts_trx *trx = cmd->node; + struct trx_power_params *tpp = &trx->power_params; + + cmd->reply = talloc_asprintf(cmd, "%d", tpp->thermal_attenuation_mdB); + + return CTRL_CMD_REPLY; +} + +static int set_therm_att(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts_trx *trx = cmd->node; + struct trx_power_params *tpp = &trx->power_params; + int val = atoi(cmd->value); + + printf("set_therm_att(trx=%p, tpp=%p)\n", trx, tpp); + + tpp->thermal_attenuation_mdB = val; + + power_ramp_start(trx, tpp->p_total_cur_mdBm, 0); + + return get_therm_att(cmd, data); +} + +static int verify_therm_att(struct ctrl_cmd *cmd, const char *value, void *data) +{ + int val = atoi(value); + + /* permit between 0 to 40 dB attenuation */ + if (val < 0 || val > to_mdB(40)) + return 1; + + return 0; +} + +CTRL_CMD_DEFINE_WO_NOVRF(oml_alert, "oml-alert"); +static int set_oml_alert(struct ctrl_cmd *cmd, void *data) +{ + /* Note: we expect signal dispatch to be synchronous */ + osmo_signal_dispatch(SS_FAIL, OSMO_EVT_EXT_ALARM, cmd->value); + + cmd->reply = "OK"; + + return CTRL_CMD_REPLY; +} + +int bts_ctrl_cmds_install(struct gsm_bts *bts) +{ + int rc = 0; + + rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_therm_att); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_oml_alert); + + return rc; +} diff --git a/src/common/bts_ctrl_lookup.c b/src/common/bts_ctrl_lookup.c new file mode 100644 index 0000000..f0157e9 --- /dev/null +++ b/src/common/bts_ctrl_lookup.c @@ -0,0 +1,115 @@ +/* Control Interface for osmo-bts */ + +/* (C) 2014 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +extern vector ctrl_node_vec; + +/*! \brief control interface lookup function for bsc/bts 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 bts_ctrl_node_lookup(void *data, vector vline, int *node_type, + void **node_data, int *i) +{ + struct gsm_bts *bts = data; + struct gsm_bts_trx *trx = NULL; + struct gsm_bts_trx_ts *ts = 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, "trx")) { + if (*node_type != CTRL_NODE_ROOT || !*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 + return 0; + + return 1; +err_missing: + return -ENODEV; +err_index: + return -ERANGE; +} + +struct ctrl_handle *bts_controlif_setup(struct gsm_bts *bts, + const char *bind_addr, uint16_t port) +{ + struct ctrl_handle *hdl; + int rc = 0; + + hdl = ctrl_interface_setup_dynip(bts, bind_addr, port, + bts_ctrl_node_lookup); + if (!hdl) + return NULL; + + rc = bts_ctrl_cmds_install(bts); + if (rc) { + /* FIXME: close control interface */ + return NULL; + } + + rc = bts_model_ctrl_cmds_install(bts); + if (rc) { + /* FIXME: cleanup generic control commands */ + /* FIXME: close control interface */ + return NULL; + } + + return hdl; +} diff --git a/src/common/cbch.c b/src/common/cbch.c new file mode 100644 index 0000000..b8f69c6 --- /dev/null +++ b/src/common/cbch.c @@ -0,0 +1,192 @@ +/* Cell Broadcast routines */ + +/* (C) 2014 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include + +#include +#include + +#include +#include +#include + +struct smscb_msg { + struct llist_head list; /* list in smscb_state.queue */ + + uint8_t msg[GSM412_MSG_LEN]; /* message buffer */ + uint8_t next_seg; /* next segment number */ + uint8_t num_segs; /* total number of segments */ +}; + +static int get_smscb_null_block(uint8_t *out) +{ + struct gsm412_block_type *block_type = (struct gsm412_block_type *) out; + + block_type->spare = 0; + block_type->lpd = 1; + block_type->seq_nr = GSM412_SEQ_NULL_MSG; + block_type->lb = 0; + memset(out+1, GSM_MACBLOCK_PADDING, GSM412_BLOCK_LEN); + + return 0; +} + +/* get the next block of the current CB message */ +static int get_smscb_block(struct gsm_bts *bts, uint8_t *out) +{ + int to_copy; + struct gsm412_block_type *block_type; + struct smscb_msg *msg = bts->smscb_state.cur_msg; + + if (!msg) { + /* No message: Send NULL mesage */ + return get_smscb_null_block(out); + } + + block_type = (struct gsm412_block_type *) out++; + + /* LPD is always 01 */ + block_type->spare = 0; + block_type->lpd = 1; + + /* determine how much data to copy */ + to_copy = GSM412_MSG_LEN - (msg->next_seg * GSM412_BLOCK_LEN); + if (to_copy > GSM412_BLOCK_LEN) + to_copy = GSM412_BLOCK_LEN; + + /* copy data and increment index */ + memcpy(out, &msg->msg[msg->next_seg * GSM412_BLOCK_LEN], to_copy); + + /* set + increment sequence number */ + block_type->seq_nr = msg->next_seg++; + + /* determine if this is the last block */ + if (block_type->seq_nr + 1 == msg->num_segs) + block_type->lb = 1; + else + block_type->lb = 0; + + if (block_type->lb == 1) { + /* remove/release the message memory */ + talloc_free(bts->smscb_state.cur_msg); + bts->smscb_state.cur_msg = NULL; + } + + return block_type->lb; +} + +static const uint8_t last_block_rsl2um[4] = { + [RSL_CB_CMD_LASTBLOCK_4] = 4, + [RSL_CB_CMD_LASTBLOCK_1] = 1, + [RSL_CB_CMD_LASTBLOCK_2] = 2, + [RSL_CB_CMD_LASTBLOCK_3] = 3, +}; + + +/* incoming SMS broadcast command from RSL */ +int bts_process_smscb_cmd(struct gsm_bts *bts, + struct rsl_ie_cb_cmd_type cmd_type, + uint8_t msg_len, const uint8_t *msg) +{ + struct smscb_msg *scm; + + if (msg_len > sizeof(scm->msg)) { + LOGP(DLSMS, LOGL_ERROR, + "Cannot process SMSCB of %u bytes (max %zu)\n", + msg_len, sizeof(scm->msg)); + return -EINVAL; + } + + scm = talloc_zero_size(bts, sizeof(*scm)); + + /* initialize entire message with default padding */ + memset(scm->msg, GSM_MACBLOCK_PADDING, sizeof(scm->msg)); + /* next segment is first segment */ + scm->next_seg = 0; + + switch (cmd_type.command) { + case RSL_CB_CMD_TYPE_NORMAL: + case RSL_CB_CMD_TYPE_SCHEDULE: + case RSL_CB_CMD_TYPE_NULL: + scm->num_segs = last_block_rsl2um[cmd_type.last_block&3]; + memcpy(scm->msg, msg, msg_len); + /* def_bcast is ignored */ + break; + case RSL_CB_CMD_TYPE_DEFAULT: + /* use def_bcast, ignore command */ + /* def_bcast == 0: normal mess */ + break; + } + + llist_add_tail(&scm->list, &bts->smscb_state.queue); + + return 0; +} + +static struct smscb_msg *select_next_smscb(struct gsm_bts *bts) +{ + struct smscb_msg *msg; + + if (llist_empty(&bts->smscb_state.queue)) + return NULL; + + msg = llist_entry(bts->smscb_state.queue.next, + struct smscb_msg, list); + + llist_del(&msg->list); + + return msg; +} + +/* call-back from bts model specific code when it wants to obtain a CBCH + * block for a given gsm_time. outbuf must have 23 bytes of space. */ +int bts_cbch_get(struct gsm_bts *bts, uint8_t *outbuf, struct gsm_time *g_time) +{ + uint32_t fn = gsm_gsmtime2fn(g_time); + /* According to 05.02 Section 6.5.4 */ + uint32_t tb = (fn / 51) % 8; + int rc = 0; + + /* The multiframes used for the basic cell broadcast channel + * shall be those in * which TB = 0,1,2 and 3. The multiframes + * used for the extended cell broadcast channel shall be those + * in which TB = 4, 5, 6 and 7 */ + + /* The SMSCB header shall be sent in the multiframe in which TB + * = 0 for the basic, and TB = 4 for the extended cell + * broadcast channel. */ + + switch (tb) { + case 0: + /* select a new SMSCB message */ + bts->smscb_state.cur_msg = select_next_smscb(bts); + rc = get_smscb_block(bts, outbuf); + break; + case 1: case 2: case 3: + rc = get_smscb_block(bts, outbuf); + break; + case 4: case 5: case 6: case 7: + /* always send NULL frame in extended CBCH for now */ + rc = get_smscb_null_block(outbuf); + break; + } + + return rc; +} diff --git a/src/common/dtx_dl_amr_fsm.c b/src/common/dtx_dl_amr_fsm.c new file mode 100644 index 0000000..38e22c9 --- /dev/null +++ b/src/common/dtx_dl_amr_fsm.c @@ -0,0 +1,477 @@ +/* DTX DL AMR FSM */ + +/* (C) 2016 by sysmocom s.f.m.c. GmbH + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +void dtx_fsm_voice(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_VOICE: + case E_FACCH: + break; + case E_SID_F: + osmo_fsm_inst_state_chg(fi, ST_SID_F1, 0, 0); + break; + case E_SID_U: + osmo_fsm_inst_state_chg(fi, ST_U_NOINH, 0, 0); + break; + case E_INHIB: + osmo_fsm_inst_state_chg(fi, ST_F1_INH_V, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Inexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_sid_f1(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_SID_F: +/* FIXME: what shall we do if we get SID-FIRST _again_ (twice in a row)? + Was observed during testing, let's just ignore it for now */ + break; + case E_SID_U: + osmo_fsm_inst_state_chg(fi, ST_U_NOINH, 0, 0); + break; + case E_FACCH: + osmo_fsm_inst_state_chg(fi, ST_F1_INH_F, 0, 0); + break; + case E_FIRST: + osmo_fsm_inst_state_chg(fi, ST_SID_F2, 0, 0); + break; + case E_ONSET: + osmo_fsm_inst_state_chg(fi, ST_ONSET_V, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_sid_f2(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_U_NOINH, 0, 0); + break; + case E_FACCH: + osmo_fsm_inst_state_chg(fi, ST_ONSET_F, 0, 0); + break; + case E_ONSET: + osmo_fsm_inst_state_chg(fi, ST_ONSET_V, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_f1_inh_v(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_F1_INH_V_REC, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_f1_inh_f(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_F1_INH_F_REC, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_u_inh_v(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_U_INH_V_REC, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_u_inh_f(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_U_INH_F_REC, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_f1_inh_v_rec(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_VOICE: + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_f1_inh_f_rec(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_FACCH: + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_FACCH, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_u_inh_v_rec(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_VOICE: + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_u_inh_f_rec(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_FACCH: + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_FACCH, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_u_noinh(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_FACCH: + osmo_fsm_inst_state_chg(fi, ST_ONSET_F, 0, 0); + break; + case E_VOICE: + osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0); + break; + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_SID_U, 0, 0); + break; + case E_SID_U: + case E_SID_F: +/* FIXME: what shall we do if we get SID-FIRST _after_ sending SID-UPDATE? + Was observed during testing, let's just ignore it for now */ + break; + case E_ONSET: + osmo_fsm_inst_state_chg(fi, ST_ONSET_V, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_sid_upd(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_FACCH: + osmo_fsm_inst_state_chg(fi, ST_U_INH_F, 0, 0); + break; + case E_VOICE: + osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0); + break; + case E_INHIB: + osmo_fsm_inst_state_chg(fi, ST_U_INH_V, 0, 0); + break; + case E_SID_U: + case E_SID_F: + osmo_fsm_inst_state_chg(fi, ST_U_NOINH, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_onset_v(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_ONSET_V_REC, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_onset_f(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_ONSET_F_REC, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_onset_v_rec(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_onset_f_rec(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_FACCH, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +void dtx_fsm_facch(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case E_SID_U: + case E_SID_F: + case E_FACCH: + break; + case E_VOICE: + osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0); + break; + case E_COMPL: + osmo_fsm_inst_state_chg(fi, ST_SID_F1, 0, 0); + break; + default: + LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event); + OSMO_ASSERT(0); + break; + } +} + +static struct osmo_fsm_state dtx_dl_amr_fsm_states[] = { + /* default state for non-DTX and DTX when SPEECH is in progress */ + [ST_VOICE] = { + .in_event_mask = X(E_SID_F) | X(E_SID_U) | X(E_VOICE) | X(E_FACCH) | X(E_INHIB), + .out_state_mask = X(ST_SID_F1) | X(ST_U_NOINH) | X(ST_F1_INH_V), + .name = "Voice", + .action = dtx_fsm_voice, + }, + /* SID-FIRST or SID-FIRST-P1 in case of AMR HR: + start of silence period (might be interrupted in case of AMR HR) */ + [ST_SID_F1]= { + .in_event_mask = X(E_SID_F) | X(E_SID_U) | X(E_FACCH) | X(E_FIRST) | X(E_ONSET), + .out_state_mask = X(ST_U_NOINH) | X(ST_ONSET_F) | X(ST_SID_F2) | X(ST_ONSET_V), + .name = "SID-FIRST (P1)", + .action = dtx_fsm_sid_f1, + }, + /* SID-FIRST P2 (only for AMR HR): + actual start of silence period in case of AMR HR */ + [ST_SID_F2]= { + .in_event_mask = X(E_COMPL) | X(E_FACCH) | X(E_ONSET), + .out_state_mask = X(ST_U_NOINH) | X(ST_ONSET_F) | X(ST_ONSET_V), + .name = "SID-FIRST (P2)", + .action = dtx_fsm_sid_f2, + }, + /* SID-FIRST Inhibited: incoming SPEECH (only for AMR HR) */ + [ST_F1_INH_V]= { + .in_event_mask = X(E_COMPL), + .out_state_mask = X(ST_F1_INH_V_REC), + .name = "SID-FIRST (Inh, SPEECH)", + .action = dtx_fsm_f1_inh_v, + }, + /* SID-FIRST Inhibited: incoming FACCH frame (only for AMR HR) */ + [ST_F1_INH_F]= { + .in_event_mask = X(E_COMPL), + .out_state_mask = X(ST_F1_INH_F_REC), + .name = "SID-FIRST (Inh, FACCH)", + .action = dtx_fsm_f1_inh_f, + }, + /* SID-UPDATE Inhibited: incoming SPEECH (only for AMR HR) */ + [ST_U_INH_V]= { + .in_event_mask = X(E_COMPL), + .out_state_mask = X(ST_U_INH_V_REC), + .name = "SID-UPDATE (Inh, SPEECH)", + .action = dtx_fsm_u_inh_v, + }, + /* SID-UPDATE Inhibited: incoming FACCH frame (only for AMR HR) */ + [ST_U_INH_F]= { + .in_event_mask = X(E_COMPL), + .out_state_mask = X(ST_U_INH_F_REC), + .name = "SID-UPDATE (Inh, FACCH)", + .action = dtx_fsm_u_inh_f, + }, + /* SID-UPDATE: Inhibited not allowed (only for AMR HR) */ + [ST_U_NOINH]= { + .in_event_mask = X(E_FACCH) | X(E_VOICE) | X(E_COMPL) | X(E_SID_U) | X(E_SID_F) | X(E_ONSET), + .out_state_mask = X(ST_ONSET_F) | X(ST_VOICE) | X(ST_SID_U) | X(ST_ONSET_V), + .name = "SID-UPDATE (NoInh)", + .action = dtx_fsm_u_noinh, + }, + /* SID-FIRST Inhibition recursion in progress: + Inhibit itself was already sent, now have to send the voice that caused it */ + [ST_F1_INH_V_REC]= { + .in_event_mask = X(E_COMPL) | X(E_VOICE), + .out_state_mask = X(ST_VOICE), + .name = "SID-FIRST (Inh, SPEECH, Rec)", + .action = dtx_fsm_f1_inh_v_rec, + }, + /* SID-FIRST Inhibition recursion in progress: + Inhibit itself was already sent, now have to send the data that caused it */ + [ST_F1_INH_F_REC]= { + .in_event_mask = X(E_COMPL) | X(E_FACCH), + .out_state_mask = X(ST_FACCH), + .name = "SID-FIRST (Inh, FACCH, Rec)", + .action = dtx_fsm_f1_inh_f_rec, + }, + /* SID-UPDATE Inhibition recursion in progress: + Inhibit itself was already sent, now have to send the voice that caused it */ + [ST_U_INH_V_REC]= { + .in_event_mask = X(E_COMPL) | X(E_VOICE), + .out_state_mask = X(ST_VOICE), + .name = "SID-UPDATE (Inh, SPEECH, Rec)", + .action = dtx_fsm_u_inh_v_rec, + }, + /* SID-UPDATE Inhibition recursion in progress: + Inhibit itself was already sent, now have to send the data that caused it */ + [ST_U_INH_F_REC]= { + .in_event_mask = X(E_COMPL) | X(E_FACCH), + .out_state_mask = X(ST_FACCH), + .name = "SID-UPDATE (Inh, FACCH, Rec)", + .action = dtx_fsm_u_inh_f_rec, + }, + /* Silence period with periodic comfort noise data updates */ + [ST_SID_U]= { + .in_event_mask = X(E_FACCH) | X(E_VOICE) | X(E_INHIB) | X(E_SID_U) | X(E_SID_F), + .out_state_mask = X(ST_ONSET_F) | X(ST_VOICE) | X(ST_U_INH_V) | X(ST_U_INH_F) | X(ST_U_NOINH), + .name = "SID-UPDATE (AMR/HR)", + .action = dtx_fsm_sid_upd, + }, + /* ONSET - end of silent period due to incoming SPEECH frame */ + [ST_ONSET_V]= { + .in_event_mask = X(E_COMPL), + .out_state_mask = X(ST_ONSET_V_REC), + .name = "ONSET (SPEECH)", + .action = dtx_fsm_onset_v, + }, + /* ONSET - end of silent period due to incoming FACCH frame */ + [ST_ONSET_F]= { + .in_event_mask = X(E_COMPL), + .out_state_mask = X(ST_ONSET_F_REC), + .name = "ONSET (FACCH)", + .action = dtx_fsm_onset_f, + }, + /* ONSET recursion in progress: + ONSET itself was already sent, now have to send the voice that caused it */ + [ST_ONSET_V_REC]= { + .in_event_mask = X(E_COMPL), + .out_state_mask = X(ST_VOICE), + .name = "ONSET (SPEECH, Rec)", + .action = dtx_fsm_onset_v_rec, + }, + /* ONSET recursion in progress: + ONSET itself was already sent, now have to send the data that caused it */ + [ST_ONSET_F_REC]= { + .in_event_mask = X(E_COMPL), + .out_state_mask = X(ST_FACCH), + .name = "ONSET (FACCH, Rec)", + .action = dtx_fsm_onset_f_rec, + }, + /* FACCH sending state */ + [ST_FACCH]= { + .in_event_mask = X(E_FACCH) | X(E_VOICE) | X(E_COMPL) | X(E_SID_U) | X(E_SID_F), + .out_state_mask = X(ST_VOICE) | X(ST_SID_F1), + .name = "FACCH", + .action = dtx_fsm_facch, + }, +}; + +const struct value_string dtx_dl_amr_fsm_event_names[] = { + { E_VOICE, "Voice" }, + { E_ONSET, "ONSET" }, + { E_FACCH, "FACCH" }, + { E_COMPL, "Complete" }, + { E_FIRST, "FIRST P1->P2" }, + { E_INHIB, "Inhibit" }, + { E_SID_F, "SID-FIRST" }, + { E_SID_U, "SID-UPDATE" }, + { 0, NULL } +}; + +struct osmo_fsm dtx_dl_amr_fsm = { + .name = "DTX_DL_AMR_FSM", + .states = dtx_dl_amr_fsm_states, + .num_states = ARRAY_SIZE(dtx_dl_amr_fsm_states), + .event_names = dtx_dl_amr_fsm_event_names, + .log_subsys = DL1C, +}; diff --git a/src/common/gsm_data_shared.c b/src/common/gsm_data_shared.c new file mode 100644 index 0000000..2a1f9c8 --- /dev/null +++ b/src/common/gsm_data_shared.c @@ -0,0 +1,829 @@ +/* (C) 2008-2010 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +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" }, + { BTS_OSMO_VIRTUAL, "omso-bts-virtual" }, + { BTS_OSMO_OMLDUMMY, "omso-bts-omldummy" }, + { 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 gsm_bts_features_descs[] = { + { BTS_FEAT_HSCSD, "HSCSD" }, + { BTS_FEAT_GPRS, "GPRS" }, + { BTS_FEAT_EGPRS, "EGPRS" }, + { BTS_FEAT_ECSD, "ECSD" }, + { BTS_FEAT_HOPPING, "Frequency Hopping" }, + { BTS_FEAT_MULTI_TSC, "Multi-TSC" }, + { BTS_FEAT_OML_ALERTS, "OML Alerts" }, + { BTS_FEAT_AGCH_PCH_PROP, "AGCH/PCH proportional allocation" }, + { BTS_FEAT_CBCH, "CBCH" }, + { BTS_FEAT_SPEECH_F_V1, "Fullrate speech V1" }, + { BTS_FEAT_SPEECH_H_V1, "Halfrate speech V1" }, + { BTS_FEAT_SPEECH_F_EFR, "Fullrate speech EFR" }, + { BTS_FEAT_SPEECH_F_AMR, "Fullrate speech AMR" }, + { BTS_FEAT_SPEECH_H_AMR, "Halfrate speech AMR" }, + { 0, NULL } +}; + +const struct value_string gsm_chreq_descs[] = { + { GSM_CHREQ_REASON_EMERG, "emergency call" }, + { GSM_CHREQ_REASON_PAG, "answer to paging" }, + { GSM_CHREQ_REASON_CALL, "call re-establishment" }, + { GSM_CHREQ_REASON_LOCATION_UPD,"Location updating" }, + { GSM_CHREQ_REASON_PDCH, "one phase packet access" }, + { GSM_CHREQ_REASON_OTHER, "other" }, + { 0, NULL } +}; + +const struct value_string gsm_pchant_names[13] = { + { GSM_PCHAN_NONE, "NONE" }, + { GSM_PCHAN_CCCH, "CCCH" }, + { GSM_PCHAN_CCCH_SDCCH4,"CCCH+SDCCH4" }, + { GSM_PCHAN_TCH_F, "TCH/F" }, + { GSM_PCHAN_TCH_H, "TCH/H" }, + { GSM_PCHAN_SDCCH8_SACCH8C, "SDCCH8" }, + { GSM_PCHAN_PDCH, "PDCH" }, + { GSM_PCHAN_TCH_F_PDCH, "TCH/F_PDCH" }, + { GSM_PCHAN_UNKNOWN, "UNKNOWN" }, + { GSM_PCHAN_CCCH_SDCCH4_CBCH, "CCCH+SDCCH4+CBCH" }, + { GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "SDCCH8+CBCH" }, + { GSM_PCHAN_TCH_F_TCH_H_PDCH, "TCH/F_TCH/H_PDCH" }, + { 0, NULL } +}; + +const struct value_string gsm_pchant_descs[13] = { + { GSM_PCHAN_NONE, "Physical Channel not configured" }, + { GSM_PCHAN_CCCH, "FCCH + SCH + BCCH + CCCH (Comb. IV)" }, + { GSM_PCHAN_CCCH_SDCCH4, + "FCCH + SCH + BCCH + CCCH + 4 SDCCH + 2 SACCH (Comb. V)" }, + { GSM_PCHAN_TCH_F, "TCH/F + FACCH/F + SACCH (Comb. I)" }, + { GSM_PCHAN_TCH_H, "2 TCH/H + 2 FACCH/H + 2 SACCH (Comb. II)" }, + { GSM_PCHAN_SDCCH8_SACCH8C, "8 SDCCH + 4 SACCH (Comb. VII)" }, + { GSM_PCHAN_PDCH, "Packet Data Channel for GPRS/EDGE" }, + { GSM_PCHAN_TCH_F_PDCH, "Dynamic TCH/F or GPRS PDCH" }, + { GSM_PCHAN_UNKNOWN, "Unknown / Unsupported channel combination" }, + { GSM_PCHAN_CCCH_SDCCH4_CBCH, "FCCH + SCH + BCCH + CCCH + CBCH + 3 SDCCH + 2 SACCH (Comb. V)" }, + { GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "7 SDCCH + 4 SACCH + CBCH (Comb. VII)" }, + { GSM_PCHAN_TCH_F_TCH_H_PDCH, "Dynamic TCH/F or TCH/H or GPRS PDCH" }, + { 0, NULL } +}; + +const char *gsm_pchan_name(enum gsm_phys_chan_config c) +{ + return get_value_string(gsm_pchant_names, c); +} + +enum gsm_phys_chan_config gsm_pchan_parse(const char *name) +{ + return get_string_value(gsm_pchant_names, name); +} + +/* TODO: move to libosmocore, next to gsm_chan_t_names? */ +const char *gsm_lchant_name(enum gsm_chan_t c) +{ + return get_value_string(gsm_chan_t_names, c); +} + +static const struct value_string lchan_s_names[] = { + { LCHAN_S_NONE, "NONE" }, + { LCHAN_S_ACT_REQ, "ACTIVATION REQUESTED" }, + { LCHAN_S_ACTIVE, "ACTIVE" }, + { LCHAN_S_INACTIVE, "INACTIVE" }, + { LCHAN_S_REL_REQ, "RELEASE REQUESTED" }, + { LCHAN_S_REL_ERR, "RELEASE DUE ERROR" }, + { LCHAN_S_BROKEN, "BROKEN UNUSABLE" }, + { 0, NULL } +}; + +const char *gsm_lchans_name(enum gsm_lchan_state s) +{ + return get_value_string(lchan_s_names, s); +} + +static const struct value_string chreq_names[] = { + { GSM_CHREQ_REASON_EMERG, "EMERGENCY" }, + { GSM_CHREQ_REASON_PAG, "PAGING" }, + { GSM_CHREQ_REASON_CALL, "CALL" }, + { GSM_CHREQ_REASON_LOCATION_UPD,"LOCATION_UPDATE" }, + { GSM_CHREQ_REASON_OTHER, "OTHER" }, + { 0, NULL } +}; + +const char *gsm_chreq_name(enum gsm_chreq_reason_t c) +{ + return get_value_string(chreq_names, c); +} + +struct gsm_bts *gsm_bts_num(struct gsm_network *net, int num) +{ + struct gsm_bts *bts; + + if (num >= net->num_bts) + return NULL; + + llist_for_each_entry(bts, &net->bts_list, list) { + if (bts->nr == num) + return bts; + } + + return NULL; +} + +struct gsm_bts_trx *gsm_bts_trx_alloc(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx = talloc_zero(bts, struct gsm_bts_trx); + int k; + + if (!trx) + return NULL; + + trx->bts = bts; + trx->nr = bts->num_trx++; + trx->mo.nm_state.administrative = NM_STATE_UNLOCKED; + + gsm_mo_init(&trx->mo, bts, NM_OC_RADIO_CARRIER, + bts->nr, trx->nr, 0xff); + gsm_mo_init(&trx->bb_transc.mo, bts, NM_OC_BASEB_TRANSC, + bts->nr, trx->nr, 0xff); + + for (k = 0; k < TRX_NR_TS; k++) { + struct gsm_bts_trx_ts *ts = &trx->ts[k]; + int l; + + ts->trx = trx; + ts->nr = k; + ts->pchan = GSM_PCHAN_NONE; + ts->dyn.pchan_is = GSM_PCHAN_NONE; + ts->dyn.pchan_want = GSM_PCHAN_NONE; + ts->tsc = -1; + + gsm_mo_init(&ts->mo, bts, NM_OC_CHANNEL, + bts->nr, trx->nr, ts->nr); + + ts->hopping.arfcns.data_len = sizeof(ts->hopping.arfcns_data); + ts->hopping.arfcns.data = ts->hopping.arfcns_data; + ts->hopping.ma.data_len = sizeof(ts->hopping.ma_data); + ts->hopping.ma.data = ts->hopping.ma_data; + + for (l = 0; l < TS_MAX_LCHAN; l++) { + struct gsm_lchan *lchan; + char *name; + lchan = &ts->lchan[l]; + + lchan->ts = ts; + lchan->nr = l; + lchan->type = GSM_LCHAN_NONE; + + name = gsm_lchan_name_compute(lchan); + lchan->name = talloc_strdup(trx, name); + INIT_LLIST_HEAD(&lchan->sapi_cmds); + } + } + + if (trx->nr != 0) + trx->nominal_power = bts->c0->nominal_power; + + llist_add_tail(&trx->list, &bts->trx_list); + + return trx; +} + + +static const uint8_t bts_nse_timer_default[] = { 3, 3, 3, 3, 30, 3, 10 }; +static const uint8_t bts_cell_timer_default[] = + { 3, 3, 3, 3, 3, 10, 3, 10, 3, 10, 3 }; +static const struct gprs_rlc_cfg rlc_cfg_default = { + .parameter = { + [RLC_T3142] = 20, + [RLC_T3169] = 5, + [RLC_T3191] = 5, + [RLC_T3193] = 160, /* 10ms */ + [RLC_T3195] = 5, + [RLC_N3101] = 10, + [RLC_N3103] = 4, + [RLC_N3105] = 8, + [CV_COUNTDOWN] = 15, + [T_DL_TBF_EXT] = 250 * 10, /* ms */ + [T_UL_TBF_EXT] = 250 * 10, /* ms */ + }, + .paging = { + .repeat_time = 5 * 50, /* ms */ + .repeat_count = 3, + }, + .cs_mask = 0x1fff, + .initial_cs = 2, + .initial_mcs = 6, +}; + +struct gsm_bts *gsm_bts_alloc(void *ctx, uint8_t bts_num) +{ + struct gsm_bts *bts = talloc_zero(ctx, struct gsm_bts); + int i; + + if (!bts) + return NULL; + + bts->nr = bts_num; + bts->num_trx = 0; + INIT_LLIST_HEAD(&bts->trx_list); + 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)); + + /* create our primary TRX */ + bts->c0 = gsm_bts_trx_alloc(bts); + if (!bts->c0) { + talloc_free(bts); + return NULL; + } + bts->c0->ts[0].pchan = GSM_PCHAN_CCCH_SDCCH4; + + bts->rach_b_thresh = -1; + bts->rach_ldavg_slots = -1; + bts->features.data = &bts->_features_data[0]; + bts->features.data_len = sizeof(bts->_features_data); + + /* si handling */ + bts->bcch_change_mark = 1; + + return bts; +} + +/* reset the state of all MO in the BTS */ +void gsm_bts_mo_reset(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + unsigned int i; + + gsm_abis_mo_reset(&bts->mo); + gsm_abis_mo_reset(&bts->site_mgr.mo); + for (i = 0; i < ARRAY_SIZE(bts->gprs.nsvc); i++) + gsm_abis_mo_reset(&bts->gprs.nsvc[i].mo); + gsm_abis_mo_reset(&bts->gprs.nse.mo); + gsm_abis_mo_reset(&bts->gprs.cell.mo); + + llist_for_each_entry(trx, &bts->trx_list, list) { + gsm_abis_mo_reset(&trx->mo); + gsm_abis_mo_reset(&trx->bb_transc.mo); + + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct gsm_bts_trx_ts *ts = &trx->ts[i]; + gsm_abis_mo_reset(&ts->mo); + } + } +} + +struct gsm_bts_trx *gsm_bts_trx_num(const struct gsm_bts *bts, int num) +{ + struct gsm_bts_trx *trx; + + if (num >= bts->num_trx) + return NULL; + + llist_for_each_entry(trx, &bts->trx_list, list) { + if (trx->nr == num) + return trx; + } + + return NULL; +} + +static char ts2str[255]; + +char *gsm_trx_name(const struct gsm_bts_trx *trx) +{ + if (!trx) + snprintf(ts2str, sizeof(ts2str), "(trx=NULL)"); + else + snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d)", + trx->bts->nr, trx->nr); + + return ts2str; +} + + +char *gsm_ts_name(const struct gsm_bts_trx_ts *ts) +{ + snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d)", + ts->trx->bts->nr, ts->trx->nr, ts->nr); + + return ts2str; +} + +/*! Log timeslot number with full pchan information */ +char *gsm_ts_and_pchan_name(const struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + if (ts->dyn.pchan_is == ts->dyn.pchan_want) + snprintf(ts2str, sizeof(ts2str), + "(bts=%d,trx=%d,ts=%d,pchan=%s as %s)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan), + gsm_pchan_name(ts->dyn.pchan_is)); + else + snprintf(ts2str, sizeof(ts2str), + "(bts=%d,trx=%d,ts=%d,pchan=%s" + " switching %s -> %s)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan), + gsm_pchan_name(ts->dyn.pchan_is), + gsm_pchan_name(ts->dyn.pchan_want)); + break; + case GSM_PCHAN_TCH_F_PDCH: + if ((ts->flags & TS_F_PDCH_PENDING_MASK) == 0) + snprintf(ts2str, sizeof(ts2str), + "(bts=%d,trx=%d,ts=%d,pchan=%s as %s)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan), + (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH" + : "TCH/F"); + else + snprintf(ts2str, sizeof(ts2str), + "(bts=%d,trx=%d,ts=%d,pchan=%s" + " switching %s -> %s)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan), + (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH" + : "TCH/F", + (ts->flags & TS_F_PDCH_ACT_PENDING)? "PDCH" + : "TCH/F"); + break; + default: + snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d,pchan=%s)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan)); + break; + } + + return ts2str; +} + +char *gsm_lchan_name_compute(const struct gsm_lchan *lchan) +{ + struct gsm_bts_trx_ts *ts = lchan->ts; + + snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d,ss=%d)", + ts->trx->bts->nr, ts->trx->nr, ts->nr, lchan->nr); + + return ts2str; +} + +/* obtain the MO structure for a given object instance */ +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_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; + + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH); + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH); + + switch (pchan) { + case GSM_PCHAN_TCH_F: + 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; + case GSM_PCHAN_CCCH: + default: + /* OSMO_ASSERT(lchan_nr == 0); + * FIXME: On octphy and litecell, we hit above assertion (see + * Max's comment at https://gerrit.osmocom.org/589 ); disabled + * for BTS until this is clarified; remove the #ifdef when it + * is fixed. Tracked in OS#2906. + */ +#pragma message "fix caller that passes 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) +{ + switch (lchan->ts->pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + /* Return chan_nr reflecting the current TS pchan, either a standard TCH kind, or the + * nonstandard value reflecting PDCH for Osmocom style dyn TS. */ + return gsm_lchan_as_pchan2chan_nr(lchan, + lchan->ts->dyn.pchan_is); + case GSM_PCHAN_TCH_F_PDCH: + /* For ip.access style dyn TS, we always want to use the chan_nr as if it was TCH/F. + * We're using custom PDCH ACT and DEACT messages that use the usual chan_nr values. */ + return gsm_lchan_as_pchan2chan_nr(lchan, GSM_PCHAN_TCH_F); + default: + return gsm_pchan2chan_nr(lchan->ts->pchan, lchan->ts->nr, lchan->nr); + } +} + +uint8_t gsm_lchan_as_pchan2chan_nr(const struct gsm_lchan *lchan, + enum gsm_phys_chan_config as_pchan) +{ + if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH + && as_pchan == GSM_PCHAN_PDCH) + return RSL_CHAN_OSMO_PDCH | (lchan->ts->nr & ~RSL_CHAN_NR_MASK); + return gsm_pchan2chan_nr(as_pchan, lchan->ts->nr, lchan->nr); +} + +/* return the gsm_lchan for the CBCH (if it exists at all) */ +struct gsm_lchan *gsm_bts_get_cbch(struct gsm_bts *bts) +{ + struct gsm_lchan *lchan = NULL; + struct gsm_bts_trx *trx = bts->c0; + + if (trx->ts[0].pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH) + lchan = &trx->ts[0].lchan[2]; + else { + int i; + for (i = 0; i < 8; i++) { + if (trx->ts[i].pchan == GSM_PCHAN_SDCCH8_SACCH8C_CBCH) { + lchan = &trx->ts[i].lchan[2]; + break; + } + } + } + + return lchan; +} + +/* determine logical channel based on TRX and channel number IE */ +struct gsm_lchan *rsl_lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr, + int *rc) +{ + uint8_t ts_nr = chan_nr & 0x07; + uint8_t cbits = chan_nr >> 3; + uint8_t lch_idx; + struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; + bool ok = true; + + if (rc) + *rc = -EINVAL; + + if (cbits == 0x01) { + lch_idx = 0; /* TCH/F */ + if (ts->pchan != GSM_PCHAN_TCH_F && + ts->pchan != GSM_PCHAN_PDCH && + ts->pchan != GSM_PCHAN_TCH_F_PDCH && + ts->pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH) + ok = false; + } else if ((cbits & 0x1e) == 0x02) { + lch_idx = cbits & 0x1; /* TCH/H */ + if (ts->pchan != GSM_PCHAN_TCH_H && + ts->pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH) + ok = false; + } else if ((cbits & 0x1c) == 0x04) { + lch_idx = cbits & 0x3; /* SDCCH/4 */ + if (ts->pchan != GSM_PCHAN_CCCH_SDCCH4 && + ts->pchan != GSM_PCHAN_CCCH_SDCCH4_CBCH) + ok = false; + } else if ((cbits & 0x18) == 0x08) { + lch_idx = cbits & 0x7; /* SDCCH/8 */ + if (ts->pchan != GSM_PCHAN_SDCCH8_SACCH8C && + ts->pchan != GSM_PCHAN_SDCCH8_SACCH8C_CBCH) + ok = false; + } else if (cbits == 0x10 || cbits == 0x11 || cbits == 0x12) { + lch_idx = 0; + if (ts->pchan != GSM_PCHAN_CCCH && + ts->pchan != GSM_PCHAN_CCCH_SDCCH4 && + ts->pchan != GSM_PCHAN_CCCH_SDCCH4_CBCH) + ok = false; + /* FIXME: we should not return first sdcch4 !!! */ + } else if ((chan_nr & RSL_CHAN_NR_MASK) == RSL_CHAN_OSMO_PDCH) { + lch_idx = 0; + if (ts->pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH) + ok = false; + } else + return NULL; + + if (rc && ok) + *rc = 0; + + return &ts->lchan[lch_idx]; +} + +static const uint8_t subslots_per_pchan[] = { + [GSM_PCHAN_NONE] = 0, + [GSM_PCHAN_CCCH] = 0, + [GSM_PCHAN_PDCH] = 0, + [GSM_PCHAN_CCCH_SDCCH4] = 4, + [GSM_PCHAN_TCH_F] = 1, + [GSM_PCHAN_TCH_H] = 2, + [GSM_PCHAN_SDCCH8_SACCH8C] = 8, + [GSM_PCHAN_CCCH_SDCCH4_CBCH] = 4, + [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = 8, + /* + * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_TCH_F_TCH_H_PDCH should not be + * part of this, those TS are handled according to their dynamic state. + */ +}; + +/*! Return the actual pchan type, also heeding dynamic TS. */ +enum gsm_phys_chan_config ts_pchan(struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + return ts->dyn.pchan_is; + case GSM_PCHAN_TCH_F_PDCH: + if (ts->flags & TS_F_PDCH_ACTIVE) + return GSM_PCHAN_PDCH; + else + return GSM_PCHAN_TCH_F; + default: + return ts->pchan; + } +} + +/*! According to ts->pchan and possibly ts->dyn_pchan, return the number of + * logical channels available in the timeslot. */ +uint8_t ts_subslots(struct gsm_bts_trx_ts *ts) +{ + return subslots_per_pchan[ts_pchan(ts)]; +} + +static bool pchan_is_tch(enum gsm_phys_chan_config pchan) +{ + switch (pchan) { + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_H: + return true; + default: + return false; + } +} + +bool ts_is_tch(struct gsm_bts_trx_ts *ts) +{ + return pchan_is_tch(ts_pchan(ts)); +} + +const char *gsm_trx_unit_id(struct gsm_bts_trx *trx) +{ + static char buf[23]; + + snprintf(buf, sizeof(buf), "%u/%u/%u", trx->bts->ip_access.site_id, + trx->bts->ip_access.bts_id, trx->nr); + return buf; +} + +const struct value_string lchan_ciph_state_names[] = { + { LCHAN_CIPH_NONE, "NONE" }, + { LCHAN_CIPH_RX_REQ, "RX_REQ" }, + { LCHAN_CIPH_RX_CONF, "RX_CONF" }, + { LCHAN_CIPH_RXTX_REQ, "RXTX_REQ" }, + { LCHAN_CIPH_RX_CONF_TX_REQ, "RX_CONF_TX_REQ" }, + { LCHAN_CIPH_RXTX_CONF, "RXTX_CONF" }, + { 0, NULL } +}; diff --git a/src/common/handover.c b/src/common/handover.c new file mode 100644 index 0000000..54b131f --- /dev/null +++ b/src/common/handover.c @@ -0,0 +1,164 @@ +/* Paging message encoding + queue management */ + +/* (C) 2012-2013 by Harald Welte + * Andreas Eversberg + * (C) 2014 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +/* Transmit a handover related PHYS INFO on given lchan */ +static int ho_tx_phys_info(struct gsm_lchan *lchan) +{ + struct msgb *msg = msgb_alloc_headroom(1024, 128, "PHYS INFO"); + struct gsm48_hdr *gh; + + if (!msg) + return -ENOMEM; + + LOGP(DHO, LOGL_INFO, + "%s Sending PHYSICAL INFORMATION to MS.\n", + gsm_lchan_name(lchan)); + + /* Build RSL UNITDATA REQUEST message with 04.08 PHYS INFO */ + msg->l3h = msg->data; + gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh)); + gh->proto_discr = GSM48_PDISC_RR; + gh->msg_type = GSM48_MT_RR_HANDO_INFO; + msgb_put_u8(msg, lchan->rqd_ta); + + rsl_rll_push_l3(msg, RSL_MT_UNIT_DATA_REQ, gsm_lchan2chan_nr(lchan), + 0x00, 0); + + lapdm_rslms_recvmsg(msg, &lchan->lapdm_ch); + return 0; +} + +/* timer call-back for T3105 (handover PHYS INFO re-transmit) */ +static void ho_t3105_cb(void *data) +{ + struct gsm_lchan *lchan = data; + struct gsm_bts *bts = lchan->ts->trx->bts; + + LOGP(DHO, LOGL_INFO, "%s T3105 timeout (%d resends left)\n", + gsm_lchan_name(lchan), bts->ny1 - lchan->ho.phys_info_count); + + if (lchan->state != LCHAN_S_ACTIVE) { + LOGP(DHO, LOGL_NOTICE, + "%s is in not active. It is in state %s. Ignoring\n", + gsm_lchan_name(lchan), gsm_lchans_name(lchan->state)); + return; + } + + if (lchan->ho.phys_info_count >= bts->ny1) { + /* HO Abort */ + LOGP(DHO, LOGL_NOTICE, "%s NY1 reached, sending CONNection " + "FAILure to BSC.\n", gsm_lchan_name(lchan)); + rsl_tx_conn_fail(lchan, RSL_ERR_HANDOVER_ACC_FAIL); + return; + } + + ho_tx_phys_info(lchan); + lchan->ho.phys_info_count++; + osmo_timer_schedule(&lchan->ho.t3105, 0, bts->t3105_ms * 1000); +} + +/* received random access on dedicated channel */ +void handover_rach(struct gsm_lchan *lchan, uint8_t ra, uint8_t acc_delay) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + + /* Ignore invalid handover ref */ + if (lchan->ho.ref != ra) { + LOGP(DHO, LOGL_INFO, "%s RACH on dedicated channel received, but " + "ra=0x%02x != expected ref=0x%02x. (This is no bug)\n", + gsm_lchan_name(lchan), ra, lchan->ho.ref); + return; + } + + /* Ignore handover on channels other than DCCH and SACCH */ + if (lchan->type != GSM_LCHAN_SDCCH && lchan->type != GSM_LCHAN_TCH_H && + lchan->type != GSM_LCHAN_TCH_F) { + LOGP(DHO, LOGL_ERROR, "%s handover RACH received on %s?!\n", + gsm_lchan_name(lchan), gsm_lchant_name(lchan->type)); + return; + } + + LOGP(DHO, LOGL_NOTICE, + "%s RACH on dedicated channel type %s received with TA=%u, ref=%u\n", + gsm_lchan_name(lchan), gsm_lchant_name(lchan->type), acc_delay, ra); + + /* Set timing advance */ + lchan->rqd_ta = acc_delay; + + /* Stop handover detection, wait for valid frame */ + lchan->ho.active = HANDOVER_WAIT_FRAME; + if (l1sap_chan_modify(lchan->ts->trx, gsm_lchan2chan_nr(lchan)) != 0) { + LOGP(DHO, LOGL_ERROR, + "%s failed to modify channel after handover\n", + gsm_lchan_name(lchan)); + rsl_tx_conn_fail(lchan, RSL_ERR_HANDOVER_ACC_FAIL); + return; + } + + /* Send HANDover DETect to BSC */ + rsl_tx_hando_det(lchan, &lchan->rqd_ta); + + /* Send PHYS INFO */ + lchan->ho.phys_info_count = 1; + ho_tx_phys_info(lchan); + + /* Start T3105 */ + LOGP(DHO, LOGL_DEBUG, + "%s Starting T3105 with %u ms\n", + gsm_lchan_name(lchan), bts->t3105_ms); + lchan->ho.t3105.cb = ho_t3105_cb; + lchan->ho.t3105.data = lchan; + osmo_timer_schedule(&lchan->ho.t3105, 0, bts->t3105_ms * 1000); +} + +/* received frist valid data frame on dedicated channel */ +void handover_frame(struct gsm_lchan *lchan) +{ + LOGP(DHO, LOGL_INFO, + "%s First valid frame detected\n", gsm_lchan_name(lchan)); + handover_reset(lchan); +} + +/* release handover state */ +void handover_reset(struct gsm_lchan *lchan) +{ + /* Stop T3105 */ + osmo_timer_del(&lchan->ho.t3105); + + /* Handover process is done */ + lchan->ho.active = HANDOVER_NONE; +} diff --git a/src/common/l1sap.c b/src/common/l1sap.c new file mode 100644 index 0000000..e7cef4e --- /dev/null +++ b/src/common/l1sap.c @@ -0,0 +1,1484 @@ +/* L1 SAP primitives */ + +/* (C) 2011 by Harald Welte + * (C) 2013 by Andreas Eversberg + * + * 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 . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct gsm_lchan *get_lchan_by_chan_nr(struct gsm_bts_trx *trx, + unsigned int chan_nr) +{ + return &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)]; +} + +static struct gsm_lchan * +get_active_lchan_by_chan_nr(struct gsm_bts_trx *trx, unsigned int chan_nr) +{ + struct gsm_lchan *lchan = get_lchan_by_chan_nr(trx, chan_nr); + + if (lchan && lchan->state != LCHAN_S_ACTIVE) { + LOGP(DL1P, LOGL_NOTICE, "%s: assuming active lchan, but " + "state is %s\n", gsm_lchan_name(lchan), + gsm_lchans_name(lchan->state)); + return NULL; + } + return lchan; +} + +static int l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap); + +static uint32_t fn_ms_adj(uint32_t fn, const struct gsm_lchan *lchan) +{ + uint32_t samples_passed, r; + + if (lchan->tch.last_fn != LCHAN_FN_DUMMY) { + /* 12/13 frames usable for audio in TCH, + 160 samples per RTP packet, + 1 RTP packet per 4 frames */ + samples_passed = (fn - lchan->tch.last_fn) * 12 * 160 / (13 * 4); + /* round number of samples to the nearest multiple of + GSM_RTP_DURATION */ + r = samples_passed + GSM_RTP_DURATION / 2; + r -= r % GSM_RTP_DURATION; + + if (r != GSM_RTP_DURATION) + LOGP(DRTP, LOGL_ERROR, "RTP clock out of sync with lower layer:" + " %"PRIu32" vs %d (%"PRIu32"->%"PRIu32")\n", + r, GSM_RTP_DURATION, lchan->tch.last_fn, fn); + } + return GSM_RTP_DURATION; +} + +/*! limit number of queue entries to %u; drops any surplus messages */ +static void queue_limit_to(const char *prefix, struct llist_head *queue, unsigned int limit) +{ + int count = llist_count(queue); + + if (count > limit) + LOGP(DL1P, LOGL_NOTICE, "%s: freeing %d queued frames\n", prefix, count-limit); + while (count > limit) { + struct msgb *tmp = msgb_dequeue(queue); + msgb_free(tmp); + count--; + } +} + +/* allocate a msgb containing a osmo_phsap_prim + optional l2 data + * in order to wrap femtobts header arround l2 data, there must be enough space + * in front and behind data pointer */ +struct msgb *l1sap_msgb_alloc(unsigned int l2_len) +{ + int headroom = 128; + int size = headroom + sizeof(struct osmo_phsap_prim) + l2_len; + struct msgb *msg = msgb_alloc_headroom(size, headroom, "l1sap_prim"); + + if (!msg) + return NULL; + + msg->l1h = msgb_put(msg, sizeof(struct osmo_phsap_prim)); + + return msg; +} + +int add_l1sap_header(struct gsm_bts_trx *trx, struct msgb *rmsg, + struct gsm_lchan *lchan, uint8_t chan_nr, uint32_t fn, + uint16_t ber10k, int16_t lqual_cb) +{ + struct osmo_phsap_prim *l1sap; + + LOGP(DL1P, LOGL_DEBUG, "%s Rx -> RTP: %s\n", + gsm_lchan_name(lchan), osmo_hexdump(rmsg->data, rmsg->len)); + + rmsg->l2h = rmsg->data; + msgb_push(rmsg, sizeof(*l1sap)); + rmsg->l1h = rmsg->data; + l1sap = msgb_l1sap_prim(rmsg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH, PRIM_OP_INDICATION, + rmsg); + l1sap->u.tch.chan_nr = chan_nr; + l1sap->u.tch.fn = fn; + l1sap->u.tch.ber10k = ber10k; + l1sap->u.tch.lqual_cb = lqual_cb; + + return l1sap_up(trx, l1sap); +} + +static int l1sap_tx_ciph_req(struct gsm_bts_trx *trx, uint8_t chan_nr, + uint8_t downlink, uint8_t uplink) +{ + struct osmo_phsap_prim l1sap_ciph; + + osmo_prim_init(&l1sap_ciph.oph, SAP_GSM_PH, PRIM_MPH_INFO, + PRIM_OP_REQUEST, NULL); + l1sap_ciph.u.info.type = PRIM_INFO_ACT_CIPH; + l1sap_ciph.u.info.u.ciph_req.chan_nr = chan_nr; + l1sap_ciph.u.info.u.ciph_req.downlink = downlink; + l1sap_ciph.u.info.u.ciph_req.uplink = uplink; + + return l1sap_down(trx, &l1sap_ciph); +} + + +/* check if the message is a GSM48_MT_RR_CIPH_M_CMD, and if yes, enable + * uni-directional de-cryption on the uplink. We need this ugly layering + * violation as we have no way of passing down L3 metadata (RSL CIPHERING CMD) + * to this point in L1 */ +static int check_for_ciph_cmd(struct msgb *msg, struct gsm_lchan *lchan, + uint8_t chan_nr) +{ + uint8_t n_s; + + /* only do this if we are in the right state */ + switch (lchan->ciph_state) { + case LCHAN_CIPH_NONE: + case LCHAN_CIPH_RX_REQ: + break; + default: + return 0; + } + + /* First byte (Address Field) of LAPDm header) */ + if (msg->data[0] != 0x03) + return 0; + /* First byte (protocol discriminator) of RR */ + if ((msg->data[3] & 0xF) != GSM48_PDISC_RR) + return 0; + /* 2nd byte (msg type) of RR */ + if ((msg->data[4] & 0x3F) != GSM48_MT_RR_CIPH_M_CMD) + return 0; + + /* Remember N(S) + 1 to find the first ciphered frame */ + n_s = (msg->data[1] >> 1) & 0x7; + lchan->ciph_ns = (n_s + 1) % 8; + + l1sap_tx_ciph_req(lchan->ts->trx, chan_nr, 0, 1); + + return 1; +} + +/* public helpers for the test */ +int bts_check_for_ciph_cmd(struct msgb *msg, struct gsm_lchan *lchan, + uint8_t chan_nr) +{ + return check_for_ciph_cmd(msg, lchan, chan_nr); +} + +struct gsmtap_inst *gsmtap = NULL; +uint32_t gsmtap_sapi_mask = 0; +uint8_t gsmtap_sapi_acch = 0; + +const struct value_string gsmtap_sapi_names[] = { + { GSMTAP_CHANNEL_BCCH, "BCCH" }, + { GSMTAP_CHANNEL_CCCH, "CCCH" }, + { GSMTAP_CHANNEL_RACH, "RACH" }, + { GSMTAP_CHANNEL_AGCH, "AGCH" }, + { GSMTAP_CHANNEL_PCH, "PCH" }, + { GSMTAP_CHANNEL_SDCCH, "SDCCH" }, + { GSMTAP_CHANNEL_TCH_F, "TCH/F" }, + { GSMTAP_CHANNEL_TCH_H, "TCH/H" }, + { GSMTAP_CHANNEL_PACCH, "PACCH" }, + { GSMTAP_CHANNEL_PDCH, "PDTCH" }, + { GSMTAP_CHANNEL_PTCCH, "PTCCH" }, + { GSMTAP_CHANNEL_CBCH51,"CBCH" }, + { GSMTAP_CHANNEL_ACCH, "SACCH" }, + { 0, NULL } +}; + +/* send primitive as gsmtap */ +static int gsmtap_ph_data(struct osmo_phsap_prim *l1sap, uint8_t *chan_type, + uint8_t *ss, uint32_t fn, uint8_t **data, unsigned int *len, + uint8_t num_agch) +{ + struct msgb *msg = l1sap->oph.msg; + uint8_t chan_nr, link_id; + + *data = msgb_l2(msg); + *len = msgb_l2len(msg); + chan_nr = l1sap->u.data.chan_nr; + link_id = l1sap->u.data.link_id; + + if (L1SAP_IS_CHAN_TCHF(chan_nr)) { + *chan_type = GSMTAP_CHANNEL_TCH_F; + } else if (L1SAP_IS_CHAN_TCHH(chan_nr)) { + *ss = L1SAP_CHAN2SS_TCHH(chan_nr); + *chan_type = GSMTAP_CHANNEL_TCH_H; + } else if (L1SAP_IS_CHAN_SDCCH4(chan_nr)) { + *ss = L1SAP_CHAN2SS_SDCCH4(chan_nr); + *chan_type = GSMTAP_CHANNEL_SDCCH; + } else if (L1SAP_IS_CHAN_SDCCH8(chan_nr)) { + *ss = L1SAP_CHAN2SS_SDCCH8(chan_nr); + *chan_type = GSMTAP_CHANNEL_SDCCH; + } else if (L1SAP_IS_CHAN_BCCH(chan_nr)) { + *chan_type = GSMTAP_CHANNEL_BCCH; + } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) { + /* The sapi depends on DSP configuration, not + * on the actual SYSTEM INFORMATION 3. */ + if (L1SAP_FN2CCCHBLOCK(fn) >= num_agch) + *chan_type = GSMTAP_CHANNEL_PCH; + else + *chan_type = GSMTAP_CHANNEL_AGCH; + } else if (L1SAP_IS_CHAN_PDCH(chan_nr)) { + *chan_type = GSMTAP_CHANNEL_PDTCH; + } + if (L1SAP_IS_LINK_SACCH(link_id)) + *chan_type |= GSMTAP_CHANNEL_ACCH; + + return 0; +} + +static int gsmtap_pdch(struct osmo_phsap_prim *l1sap, uint8_t *chan_type, + uint8_t *ss, uint32_t fn, uint8_t **data, unsigned int *len) +{ + struct msgb *msg = l1sap->oph.msg; + + *data = msgb_l2(msg); + *len = msgb_l2len(msg); + + if (L1SAP_IS_PTCCH(fn)) { + *chan_type = GSMTAP_CHANNEL_PTCCH; + *ss = L1SAP_FN2PTCCHBLOCK(fn); + if (l1sap->oph.primitive == PRIM_OP_INDICATION) { + OSMO_ASSERT(len > 0); + if ((*data[0]) == 7) + return -EINVAL; + (*data)++; + (*len)--; + } + } else + *chan_type = GSMTAP_CHANNEL_PACCH; + + return 0; +} + +static int gsmtap_ph_rach(struct osmo_phsap_prim *l1sap, uint8_t *chan_type, + uint8_t *tn, uint8_t *ss, uint32_t *fn, uint8_t **data, unsigned int *len) +{ + uint8_t chan_nr; + + *chan_type = GSMTAP_CHANNEL_RACH; + *fn = l1sap->u.rach_ind.fn; + *tn = L1SAP_CHAN2TS(l1sap->u.rach_ind.chan_nr); + chan_nr = l1sap->u.rach_ind.chan_nr; + if (L1SAP_IS_CHAN_TCHH(chan_nr)) + *ss = L1SAP_CHAN2SS_TCHH(chan_nr); + else if (L1SAP_IS_CHAN_SDCCH4(chan_nr)) + *ss = L1SAP_CHAN2SS_SDCCH4(chan_nr); + else if (L1SAP_IS_CHAN_SDCCH8(chan_nr)) + *ss = L1SAP_CHAN2SS_SDCCH8(chan_nr); + *data = (uint8_t *)&l1sap->u.rach_ind.ra; + *len = 1; + + return 0; +} + +/* Paging Request 1 with "no identity" content, i.e. empty/dummy paging */ +static const uint8_t paging_fill[GSM_MACBLOCK_LEN] = { + 0x15, 0x06, 0x21, 0x00, 0x01, 0xf0, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b }; + +static bool is_fill_frame(uint8_t chan_type, const uint8_t *data, unsigned int len) +{ + switch (chan_type) { + case GSMTAP_CHANNEL_AGCH: + if (!memcmp(data, fill_frame, GSM_MACBLOCK_LEN)) + return true; + break; + case GSMTAP_CHANNEL_PCH: + if (!memcmp(data, paging_fill, GSM_MACBLOCK_LEN)) + return true; + break; + /* don't use 'default' case here as the above only conditionally return true */ + } + return false; +} + +static int to_gsmtap(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) +{ + uint8_t *data; + unsigned int len; + uint8_t chan_type = 0, tn = 0, ss = 0; + uint32_t fn; + uint16_t uplink = GSMTAP_ARFCN_F_UPLINK; + int rc; + + if (!gsmtap) + return 0; + + switch (OSMO_PRIM_HDR(&l1sap->oph)) { + case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST): + uplink = 0; + /* fall through */ + case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_INDICATION): + fn = l1sap->u.data.fn; + tn = L1SAP_CHAN2TS(l1sap->u.data.chan_nr); + if (ts_is_pdch(&trx->ts[tn])) + rc = gsmtap_pdch(l1sap, &chan_type, &ss, fn, &data, + &len); + else + rc = gsmtap_ph_data(l1sap, &chan_type, &ss, fn, &data, + &len, num_agch(trx, "GSMTAP")); + break; + case OSMO_PRIM(PRIM_PH_RACH, PRIM_OP_INDICATION): + rc = gsmtap_ph_rach(l1sap, &chan_type, &tn, &ss, &fn, &data, + &len); + break; + default: + rc = -ENOTSUP; + } + + if (rc) + return rc; + + if (len == 0) + return 0; + if ((chan_type & GSMTAP_CHANNEL_ACCH)) { + if (!gsmtap_sapi_acch) + return 0; + } else { + if (!((1 << (chan_type & 31)) & gsmtap_sapi_mask)) + return 0; + } + + /* don't log fill frames via GSMTAP; they serve no purpose other than + * to clog up your logs */ + if (is_fill_frame(chan_type, data, len)) + return 0; + + gsmtap_send(gsmtap, trx->arfcn | uplink, tn, chan_type, ss, fn, 0, 0, + data, len); + + return 0; +} + +/* Calculate the number of RACH slots that expire in a certain GSM frame + * See also 3GPP TS 05.02 Clause 7 Table 5 of 9 */ +static unsigned int calc_exprd_rach_frames(struct gsm_bts *bts, uint32_t fn) +{ + int rach_frames_expired = 0; + uint8_t ccch_conf; + struct gsm48_system_information_type_3 *si3; + unsigned int blockno; + + si3 = GSM_BTS_SI(bts, SYSINFO_TYPE_3); + ccch_conf = si3->control_channel_desc.ccch_conf; + + if (ccch_conf == RSL_BCCH_CCCH_CONF_1_C) { + /* It is possible to combine a CCCH with an SDCCH4, in this + * case the CCCH will have to share the available frames with + * the other channel, this results in a limited number of + * available rach slots */ + blockno = fn % 51; + if (blockno == 4 || blockno == 5 + || (blockno >= 15 && blockno <= 36) || blockno == 45 + || blockno == 46) + rach_frames_expired = 1; + } else { + /* It is possible to have multiple CCCH channels on + * different physical channels (large cells), this + * also multiplies the available/expired RACH channels. + * See also TS 04.08, Chapter 10.5.2.11, table 10.29 */ + if (ccch_conf == RSL_BCCH_CCCH_CONF_2_NC) + rach_frames_expired = 2; + else if (ccch_conf == RSL_BCCH_CCCH_CONF_3_NC) + rach_frames_expired = 3; + else if (ccch_conf == RSL_BCCH_CCCH_CONF_4_NC) + rach_frames_expired = 4; + else + rach_frames_expired = 1; + } + + /* Each Frame has room for 4 RACH slots, since RACH + * slots are short enough to fit into a single radio + * burst, so we need to multiply the final result by 4 */ + return rach_frames_expired * 4; +} + +/* time information received from bts model */ +static int l1sap_info_time_ind(struct gsm_bts *bts, + struct osmo_phsap_prim *l1sap, + struct info_time_ind_param *info_time_ind) +{ + int frames_expired; + + DEBUGPFN(DL1P, info_time_ind->fn, "Rx MPH_INFO time ind\n"); + + /* Calculate and check frame difference */ + frames_expired = info_time_ind->fn - bts->gsm_time.fn; + if (frames_expired > 1) { + if (bts->gsm_time.fn) + LOGPFN(DL1P, LOGL_ERROR, info_time_ind->fn, + "Invalid condition detected: Frame difference is %"PRIu32"-%"PRIu32"=%d > 1!\n", + info_time_ind->fn, bts->gsm_time.fn, frames_expired); + } + + /* Update our data structures with the current GSM time */ + gsm_fn2gsmtime(&bts->gsm_time, info_time_ind->fn); + + /* Update time on PCU interface */ + pcu_tx_time_ind(info_time_ind->fn); + + /* increment number of RACH slots that have passed by since the + * last time indication */ + bts->load.rach.total += + calc_exprd_rach_frames(bts, info_time_ind->fn) * frames_expired; + + return 0; +} + +static inline void set_ms_to_data(struct gsm_lchan *lchan, int16_t data, bool set_ms_to) +{ + if (!lchan) + return; + + if (data + 63 > 255) { /* According to 3GPP TS 48.058 §9.3.37 Timing Offset field cannot exceed 255 */ + LOGP(DL1P, LOGL_ERROR, "Attempting to set invalid Timing Offset value %d (MS TO = %u)!\n", + data, set_ms_to); + return; + } + + if (set_ms_to) { + lchan->ms_t_offs = data + 63; + lchan->p_offs = -1; + } else { + lchan->p_offs = data + 63; + lchan->ms_t_offs = -1; + } +} + +/* measurement information received from bts model */ +static int l1sap_info_meas_ind(struct gsm_bts_trx *trx, + struct osmo_phsap_prim *l1sap, + struct info_meas_ind_param *info_meas_ind) +{ + struct bts_ul_meas ulm; + struct gsm_lchan *lchan; + + lchan = get_active_lchan_by_chan_nr(trx, info_meas_ind->chan_nr); + if (!lchan) { + LOGPFN(DL1P, LOGL_ERROR, info_meas_ind->fn, + "No lchan for MPH INFO MEAS IND (chan_nr=%u)\n", info_meas_ind->chan_nr); + return 0; + } + + DEBUGPFN(DL1P, info_meas_ind->fn, + "%s MPH_INFO meas ind, ta_offs_256bits=%d, ber10k=%d, inv_rssi=%u\n", + gsm_lchan_name(lchan), info_meas_ind->ta_offs_256bits, + info_meas_ind->ber10k, info_meas_ind->inv_rssi); + + /* in the GPRS case we are not interested in measurement + * processing. The PCU will take care of it */ + if (lchan->type == GSM_LCHAN_PDTCH) + return 0; + + memset(&ulm, 0, sizeof(ulm)); + ulm.ta_offs_256bits = info_meas_ind->ta_offs_256bits; + ulm.ber10k = info_meas_ind->ber10k; + ulm.inv_rssi = info_meas_ind->inv_rssi; + ulm.is_sub = info_meas_ind->is_sub; + + /* we assume that symbol period is 1 bit: */ + set_ms_to_data(lchan, info_meas_ind->ta_offs_256bits / 256, true); + + lchan_new_ul_meas(lchan, &ulm, info_meas_ind->fn); + + /* Check measurement period end and prepare the UL measurment + * report at Meas period End*/ + lchan_meas_check_compute(lchan, info_meas_ind->fn); + + return 0; +} + +/* any L1 MPH_INFO indication prim recevied from bts model */ +static int l1sap_mph_info_ind(struct gsm_bts_trx *trx, + struct osmo_phsap_prim *l1sap, struct mph_info_param *info) +{ + int rc = 0; + + switch (info->type) { + case PRIM_INFO_TIME: + if (trx != trx->bts->c0) { + LOGPFN(DL1P, LOGL_NOTICE, info->u.time_ind.fn, + "BTS model is sending us PRIM_INFO_TIME for TRX %u, please fix it\n", + trx->nr); + rc = -1; + } else + rc = l1sap_info_time_ind(trx->bts, l1sap, + &info->u.time_ind); + break; + case PRIM_INFO_MEAS: + rc = l1sap_info_meas_ind(trx, l1sap, &info->u.meas_ind); + break; + default: + LOGP(DL1P, LOGL_NOTICE, "unknown MPH_INFO ind type %d\n", + info->type); + break; + } + + return rc; +} + +/* activation confirm received from bts model */ +static int l1sap_info_act_cnf(struct gsm_bts_trx *trx, + struct osmo_phsap_prim *l1sap, + struct info_act_cnf_param *info_act_cnf) +{ + struct gsm_lchan *lchan; + + LOGP(DL1C, LOGL_INFO, "activate confirm chan_nr=0x%02x trx=%d\n", + info_act_cnf->chan_nr, trx->nr); + + lchan = get_lchan_by_chan_nr(trx, info_act_cnf->chan_nr); + + rsl_tx_chan_act_acknack(lchan, info_act_cnf->cause); + + /* During PDCH ACT, this is where we know that the PCU is done + * activating a PDCH, and PDCH switchover is complete. See + * rsl_rx_dyn_pdch() */ + if (lchan->ts->pchan == GSM_PCHAN_TCH_F_PDCH + && (lchan->ts->flags & TS_F_PDCH_ACT_PENDING)) + ipacc_dyn_pdch_complete(lchan->ts, + info_act_cnf->cause? -EIO : 0); + + return 0; +} + +/* activation confirm received from bts model */ +static int l1sap_info_rel_cnf(struct gsm_bts_trx *trx, + struct osmo_phsap_prim *l1sap, + struct info_act_cnf_param *info_act_cnf) +{ + struct gsm_lchan *lchan; + + LOGP(DL1C, LOGL_INFO, "deactivate confirm chan_nr=0x%02x trx=%d\n", + info_act_cnf->chan_nr, trx->nr); + + lchan = get_lchan_by_chan_nr(trx, info_act_cnf->chan_nr); + + rsl_tx_rf_rel_ack(lchan); + + /* During PDCH DEACT, this marks the deactivation of the PDTCH as + * requested by the PCU. Next up, we disconnect the TS completely and + * call back to cb_ts_disconnected(). See rsl_rx_dyn_pdch(). */ + if (lchan->ts->pchan == GSM_PCHAN_TCH_F_PDCH + && (lchan->ts->flags & TS_F_PDCH_DEACT_PENDING)) + bts_model_ts_disconnect(lchan->ts); + + return 0; +} + +/* any L1 MPH_INFO confirm prim recevied from bts model */ +static int l1sap_mph_info_cnf(struct gsm_bts_trx *trx, + struct osmo_phsap_prim *l1sap, struct mph_info_param *info) +{ + int rc = 0; + + switch (info->type) { + case PRIM_INFO_ACTIVATE: + rc = l1sap_info_act_cnf(trx, l1sap, &info->u.act_cnf); + break; + case PRIM_INFO_DEACTIVATE: + rc = l1sap_info_rel_cnf(trx, l1sap, &info->u.act_cnf); + break; + default: + LOGP(DL1C, LOGL_NOTICE, "unknown MPH_INFO cnf type %d\n", + info->type); + break; + } + + return rc; +} + +/*! handling for PDTCH loopback mode, used for BER testing + * \param[in] lchan logical channel on which we operate + * \param[in] rts_ind PH-RTS.ind from PHY which we process + * \param[out] msg Message buffer to which we write data + * + * The function will fill \a msg, from which the caller can then + * subsequently build a PH-DATA.req */ +static int lchan_pdtch_ph_rts_ind_loop(struct gsm_lchan *lchan, + const struct ph_data_param *rts_ind, + struct msgb *msg, const struct gsm_time *tm) +{ + struct msgb *loop_msg; + uint8_t *p; + + /* de-queue response message (loopback) */ + loop_msg = msgb_dequeue(&lchan->dl_tch_queue); + if (!loop_msg) { + LOGPGT(DL1P, LOGL_NOTICE, tm, "%s: no looped PDTCH message, sending empty\n", + gsm_lchan_name(lchan)); + /* empty downlink message */ + p = msgb_put(msg, GSM_MACBLOCK_LEN); + memset(p, 0, GSM_MACBLOCK_LEN); + } else { + LOGPGT(DL1P, LOGL_NOTICE, tm, "%s: looped PDTCH message of %u bytes\n", + gsm_lchan_name(lchan), msgb_l2len(loop_msg)); + /* copy over data from queued response message */ + p = msgb_put(msg, msgb_l2len(loop_msg)); + memcpy(p, msgb_l2(loop_msg), msgb_l2len(loop_msg)); + msgb_free(loop_msg); + } + return 0; +} + +/* PH-RTS-IND prim received from bts model */ +static int l1sap_ph_rts_ind(struct gsm_bts_trx *trx, + struct osmo_phsap_prim *l1sap, struct ph_data_param *rts_ind) +{ + struct msgb *msg = l1sap->oph.msg; + struct gsm_time g_time; + struct gsm_lchan *lchan; + uint8_t chan_nr, link_id; + uint8_t tn; + uint32_t fn; + uint8_t *p, *si; + struct lapdm_entity *le; + struct osmo_phsap_prim pp; + bool dtxd_facch = false; + int rc; + + chan_nr = rts_ind->chan_nr; + link_id = rts_ind->link_id; + fn = rts_ind->fn; + tn = L1SAP_CHAN2TS(chan_nr); + + gsm_fn2gsmtime(&g_time, fn); + + DEBUGPGT(DL1P, &g_time, "Rx PH-RTS.ind chan_nr=0x%02x link_id=0x%02xd\n", chan_nr, link_id); + + /* reuse PH-RTS.ind for PH-DATA.req */ + if (!msg) { + LOGPGT(DL1P, LOGL_FATAL, &g_time, "RTS without msg to be reused. Please fix!\n"); + abort(); + } + msgb_trim(msg, sizeof(*l1sap)); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA, PRIM_OP_REQUEST, + msg); + msg->l2h = msg->l1h + sizeof(*l1sap); + + if (ts_is_pdch(&trx->ts[tn])) { + lchan = get_active_lchan_by_chan_nr(trx, chan_nr); + if (lchan && lchan->loopback) { + if (!L1SAP_IS_PTCCH(rts_ind->fn)) + lchan_pdtch_ph_rts_ind_loop(lchan, rts_ind, msg, &g_time); + /* continue below like for SACCH/FACCH/... */ + } else { + /* forward RTS.ind to PCU */ + if (L1SAP_IS_PTCCH(rts_ind->fn)) { + pcu_tx_rts_req(&trx->ts[tn], 1, fn, 1 /* ARFCN */, + L1SAP_FN2PTCCHBLOCK(fn)); + } else { + pcu_tx_rts_req(&trx->ts[tn], 0, fn, 0 /* ARFCN */, + L1SAP_FN2MACBLOCK(fn)); + } + /* return early, PCU takes care of rest */ + return 0; + } + } else if (L1SAP_IS_CHAN_BCCH(chan_nr)) { + p = msgb_put(msg, GSM_MACBLOCK_LEN); + /* get them from bts->si_buf[] */ + si = bts_sysinfo_get(trx->bts, &g_time); + if (si) + memcpy(p, si, GSM_MACBLOCK_LEN); + else + memcpy(p, fill_frame, GSM_MACBLOCK_LEN); + } else if (!(chan_nr & 0x80)) { /* only TCH/F, TCH/H, SDCCH/4 and SDCCH/8 have C5 bit cleared */ + lchan = get_active_lchan_by_chan_nr(trx, chan_nr); + if (!lchan) { + LOGPGT(DL1P, LOGL_ERROR, &g_time, "No lchan for PH-RTS.ind (chan_nr=%u)\n", chan_nr); + return 0; + } + if (L1SAP_IS_LINK_SACCH(link_id)) { + p = msgb_put(msg, GSM_MACBLOCK_LEN); + /* L1-header, if not set/modified by layer 1 */ + p[0] = lchan->ms_power_ctrl.current; + p[1] = lchan->rqd_ta; + le = &lchan->lapdm_ch.lapdm_acch; + } else { + if (lchan->ts->trx->bts->dtxd) + dtxd_facch = true; + le = &lchan->lapdm_ch.lapdm_dcch; + } + rc = lapdm_phsap_dequeue_prim(le, &pp); + if (rc < 0) { + if (L1SAP_IS_LINK_SACCH(link_id)) { + /* No SACCH data from LAPDM pending, send SACCH filling */ + uint8_t *si = lchan_sacch_get(lchan); + if (si) { + /* The +2 is empty space where the DSP inserts the L1 hdr */ + memcpy(p + 2, si, GSM_MACBLOCK_LEN - 2); + } else + memcpy(p + 2, fill_frame, GSM_MACBLOCK_LEN - 2); + } else if ((!L1SAP_IS_CHAN_TCHF(chan_nr) && !L1SAP_IS_CHAN_TCHH(chan_nr)) + || lchan->rsl_cmode == RSL_CMOD_SPD_SIGN) { + /* send fill frame only, if not TCH/x != Signalling, otherwise send empty frame */ + p = msgb_put(msg, GSM_MACBLOCK_LEN); + memcpy(p, fill_frame, GSM_MACBLOCK_LEN); + } /* else the message remains empty, so TCH frames are sent */ + } else { + /* The +2 is empty space where the DSP inserts the L1 hdr */ + if (L1SAP_IS_LINK_SACCH(link_id)) + memcpy(p + 2, pp.oph.msg->data + 2, GSM_MACBLOCK_LEN - 2); + else { + p = msgb_put(msg, GSM_MACBLOCK_LEN); + memcpy(p, pp.oph.msg->data, GSM_MACBLOCK_LEN); + /* check if it is a RR CIPH MODE CMD. if yes, enable RX ciphering */ + check_for_ciph_cmd(pp.oph.msg, lchan, chan_nr); + if (dtxd_facch) + dtx_dispatch(lchan, E_FACCH); + } + msgb_free(pp.oph.msg); + } + } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) { + p = msgb_put(msg, GSM_MACBLOCK_LEN); + rc = bts_ccch_copy_msg(trx->bts, p, &g_time, + (L1SAP_FN2CCCHBLOCK(fn) < + num_agch(trx, "PH-RTS-IND"))); + if (rc <= 0) + memcpy(p, fill_frame, GSM_MACBLOCK_LEN); + } + + DEBUGPGT(DL1P, &g_time, "Tx PH-DATA.req chan_nr=0x%02x link_id=0x%02x\n", chan_nr, link_id); + + l1sap_down(trx, l1sap); + + /* don't free, because we forwarded data */ + return 1; +} + +static bool rtppayload_is_octet_aligned(const uint8_t *rtp_pl, uint8_t payload_len) +{ + /* + * Logic: If 1st bit padding is not zero, packet is either: + * - bandwidth-efficient AMR payload. + * - malformed packet. + * However, Bandwidth-efficient AMR 4,75 frame last in payload(F=0, FT=0) + * with 4th,5ht,6th AMR payload to 0 matches padding==0. + * Furthermore, both AMR 4,75 bw-efficient and octet alignment are 14 bytes long (AMR 4,75 encodes 95b): + * bw-efficient: 95b, + 4b hdr + 6b ToC = 105b, + padding = 112b = 14B. + * octet-aligned: 1B hdr + 1B ToC + 95b = 111b, + padding = 112b = 14B. + * We cannot use other fields to match since they are inside the AMR + * payload bits which are unknown. + * As a result, this function may return false positive (true) for some AMR + * 4,75 AMR frames, but given the length, CMR and FT read is the same as a + * consequence, the damage in here is harmless other than being unable to + * decode the audio at the other side. + */ + #define AMR_PADDING1(rtp_pl) (rtp_pl[0] & 0x0f) + #define AMR_PADDING2(rtp_pl) (rtp_pl[1] & 0x03) + + if(payload_len < 2 || AMR_PADDING1(rtp_pl) || AMR_PADDING2(rtp_pl)) + return false; + + return true; +} + +static bool rtppayload_is_valid(struct gsm_lchan *lchan, struct msgb *resp_msg) +{ + /* Avoid sending bw-efficient AMR to lower layers, most bts models + * don't support it. */ + if(lchan->tch_mode == GSM48_CMODE_SPEECH_AMR && + !rtppayload_is_octet_aligned(resp_msg->data, resp_msg->len)) { + LOGP(DL1P, LOGL_NOTICE, + "%s RTP->L1: Dropping unexpected AMR encoding (bw-efficient?) %s\n", + gsm_lchan_name(lchan), osmo_hexdump(resp_msg->data, resp_msg->len)); + return false; + } + return true; +} + +/* TCH-RTS-IND prim recevied from bts model */ +static int l1sap_tch_rts_ind(struct gsm_bts_trx *trx, + struct osmo_phsap_prim *l1sap, struct ph_tch_param *rts_ind) +{ + struct msgb *resp_msg; + struct osmo_phsap_prim *resp_l1sap, empty_l1sap; + struct gsm_time g_time; + struct gsm_lchan *lchan; + uint8_t chan_nr, marker = 0; + uint32_t fn; + int rc; + + chan_nr = rts_ind->chan_nr; + fn = rts_ind->fn; + + gsm_fn2gsmtime(&g_time, fn); + + DEBUGPGT(DL1P, &g_time, "Rx TCH-RTS.ind chan_nr=0x%02x\n", chan_nr); + + lchan = get_active_lchan_by_chan_nr(trx, chan_nr); + if (!lchan) { + LOGPGT(DL1P, LOGL_ERROR, &g_time, "No lchan for PH-RTS.ind (chan_nr=%u)\n", chan_nr); + return 0; + } + + if (!lchan->loopback && lchan->abis_ip.rtp_socket) { + osmo_rtp_socket_poll(lchan->abis_ip.rtp_socket); + /* FIXME: we _assume_ that we never miss TDMA + * frames and that we always get to this point + * for every to-be-transmitted voice frame. A + * better solution would be to compute + * rx_user_ts based on how many TDMA frames have + * elapsed since the last call */ + lchan->abis_ip.rtp_socket->rx_user_ts += GSM_RTP_DURATION; + } + /* get a msgb from the dl_tx_queue */ + resp_msg = msgb_dequeue(&lchan->dl_tch_queue); + if (!resp_msg) { + DEBUGPGT(DL1P, &g_time, "%s DL TCH Tx queue underrun\n", gsm_lchan_name(lchan)); + resp_l1sap = &empty_l1sap; + } else if(!rtppayload_is_valid(lchan, resp_msg)) { + msgb_free(resp_msg); + resp_msg = NULL; + resp_l1sap = &empty_l1sap; + } else { + /* Obtain RTP header Marker bit from control buffer */ + marker = rtpmsg_marker_bit(resp_msg); + + resp_msg->l2h = resp_msg->data; + msgb_push(resp_msg, sizeof(*resp_l1sap)); + resp_msg->l1h = resp_msg->data; + resp_l1sap = msgb_l1sap_prim(resp_msg); + } + + /* check for pending REL_IND */ + if (lchan->pending_rel_ind_msg) { + LOGPGT(DRSL, LOGL_INFO, &g_time, "%s Forward REL_IND to L3\n", gsm_lchan_name(lchan)); + /* Forward it to L3 */ + rc = abis_bts_rsl_sendmsg(lchan->pending_rel_ind_msg); + lchan->pending_rel_ind_msg = NULL; + if (rc < 0) + return rc; + } + + memset(resp_l1sap, 0, sizeof(*resp_l1sap)); + osmo_prim_init(&resp_l1sap->oph, SAP_GSM_PH, PRIM_TCH, PRIM_OP_REQUEST, + resp_msg); + resp_l1sap->u.tch.chan_nr = chan_nr; + resp_l1sap->u.tch.fn = fn; + resp_l1sap->u.tch.marker = marker; + + DEBUGPGT(DL1P, &g_time, "Tx TCH.req chan_nr=0x%02x\n", chan_nr); + + l1sap_down(trx, resp_l1sap); + + return 0; +} + +/* process radio link timeout counter S. Follows TS 05.08 Section 5.2 + * "MS Procedure" as the "BSS Procedure [...] shall be determined by the + * network operator." */ +static void radio_link_timeout(struct gsm_lchan *lchan, int bad_frame) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + + /* Bypass radio link timeout if set to -1 */ + if (bts->radio_link_timeout < 0) + return; + + /* if link loss criterion already reached */ + if (lchan->s == 0) { + DEBUGP(DMEAS, "%s radio link counter S already 0.\n", + gsm_lchan_name(lchan)); + return; + } + + if (bad_frame) { + /* count down radio link counter S */ + lchan->s--; + DEBUGP(DMEAS, "%s counting down radio link counter S=%d\n", + gsm_lchan_name(lchan), lchan->s); + if (lchan->s == 0) + rsl_tx_conn_fail(lchan, RSL_ERR_RADIO_LINK_FAIL); + return; + } + + if (lchan->s < bts->radio_link_timeout) { + /* count up radio link counter S */ + lchan->s += 2; + if (lchan->s > bts->radio_link_timeout) + lchan->s = bts->radio_link_timeout; + DEBUGP(DMEAS, "%s counting up radio link counter S=%d\n", + gsm_lchan_name(lchan), lchan->s); + } +} + +static inline int check_for_first_ciphrd(struct gsm_lchan *lchan, + uint8_t *data, int len) +{ + uint8_t n_s; + + /* if this is the first valid message after enabling Rx + * decryption, we have to enable Tx encryption */ + if (lchan->ciph_state != LCHAN_CIPH_RX_CONF) + return 0; + + /* HACK: check if it's an I frame, in order to + * ignore some still buffered/queued UI frames received + * before decryption was enabled */ + if (data[0] != 0x01) + return 0; + + if ((data[1] & 0x01) != 0) + return 0; + + n_s = data[1] >> 5; + if (lchan->ciph_ns != n_s) + return 0; + + return 1; +} + +/* public helper for the test */ +int bts_check_for_first_ciphrd(struct gsm_lchan *lchan, + uint8_t *data, int len) +{ + return check_for_first_ciphrd(lchan, data, len); +} + +/* DATA received from bts model */ +static int l1sap_ph_data_ind(struct gsm_bts_trx *trx, + struct osmo_phsap_prim *l1sap, struct ph_data_param *data_ind) +{ + struct msgb *msg = l1sap->oph.msg; + struct gsm_time g_time; + struct gsm_lchan *lchan; + struct lapdm_entity *le; + uint8_t *data = msg->l2h; + int len = msgb_l2len(msg); + uint8_t chan_nr, link_id; + uint8_t tn; + uint32_t fn; + int8_t rssi; + enum osmo_ph_pres_info_type pr_info = data_ind->pdch_presence_info; + + rssi = data_ind->rssi; + chan_nr = data_ind->chan_nr; + link_id = data_ind->link_id; + fn = data_ind->fn; + tn = L1SAP_CHAN2TS(chan_nr); + + gsm_fn2gsmtime(&g_time, fn); + + DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind chan_nr=0x%02x link_id=0x%02x len=%d\n", + chan_nr, link_id, len); + + if (ts_is_pdch(&trx->ts[tn])) { + lchan = get_lchan_by_chan_nr(trx, chan_nr); + if (!lchan) + LOGPGT(DL1P, LOGL_ERROR, &g_time, "No lchan for chan_nr=0x%02x\n", chan_nr); + if (lchan && lchan->loopback && !L1SAP_IS_PTCCH(fn)) { + /* we are in loopback mode (for BER testing) + * mode and need to enqeue the frame to be + * returned in downlink */ + queue_limit_to(gsm_lchan_name(lchan), &lchan->dl_tch_queue, 1); + msgb_enqueue(&lchan->dl_tch_queue, msg); + + /* Return 1 to signal that we're still using msg + * and it should not be freed */ + return 1; + } + + /* don't send bad frames to PCU */ + if (len == 0) + return -EINVAL; + if (L1SAP_IS_PTCCH(fn)) { + pcu_tx_data_ind(&trx->ts[tn], PCU_IF_SAPI_PTCCH, fn, + 0 /* ARFCN */, L1SAP_FN2PTCCHBLOCK(fn), + data, len, rssi, data_ind->ber10k, + data_ind->ta_offs_256bits/64, + data_ind->lqual_cb); + } else { + /* drop incomplete UL block */ + if (pr_info != PRES_INFO_BOTH) + return 0; + /* PDTCH / PACCH frame handling */ + pcu_tx_data_ind(&trx->ts[tn], PCU_IF_SAPI_PDTCH, fn, 0 /* ARFCN */, + L1SAP_FN2MACBLOCK(fn), data, len, rssi, data_ind->ber10k, + data_ind->ta_offs_256bits/64, data_ind->lqual_cb); + } + return 0; + } + + lchan = get_active_lchan_by_chan_nr(trx, chan_nr); + if (!lchan) { + LOGPGT(DL1P, LOGL_ERROR, &g_time, "No lchan for chan_nr=%d\n", chan_nr); + return 0; + } + + /* bad frame */ + if (len == 0) { + if (L1SAP_IS_LINK_SACCH(link_id)) + radio_link_timeout(lchan, 1); + return -EINVAL; + } + + /* report first valid received frame to handover process */ + if (lchan->ho.active == HANDOVER_WAIT_FRAME) + handover_frame(lchan); + + if (L1SAP_IS_LINK_SACCH(link_id)) { + radio_link_timeout(lchan, 0); + le = &lchan->lapdm_ch.lapdm_acch; + /* save the SACCH L1 header in the lchan struct for RSL MEAS RES */ + if (len < 2) { + LOGPGT(DL1P, LOGL_NOTICE, &g_time, "SACCH with size %u<2 !?!\n", len); + return -EINVAL; + } + /* Some brilliant engineer decided that the ordering of + * fields on the Um interface is different from the + * order of fields in RLS. See TS 04.04 (Chapter 7.2) + * vs. TS 08.58 (Chapter 9.3.10). */ + lchan->meas.l1_info[0] = data[0] << 3; + lchan->meas.l1_info[0] |= ((data[0] >> 5) & 1) << 2; + lchan->meas.l1_info[1] = data[1]; + lchan->meas.flags |= LC_UL_M_F_L1_VALID; + + lchan_ms_pwr_ctrl(lchan, data[0] & 0x1f, data_ind->rssi); + } else + le = &lchan->lapdm_ch.lapdm_dcch; + + if (check_for_first_ciphrd(lchan, data, len)) + l1sap_tx_ciph_req(lchan->ts->trx, chan_nr, 1, 0); + + /* SDCCH, SACCH and FACCH all go to LAPDm */ + msgb_pull(msg, (msg->l2h - msg->data)); + msg->l1h = NULL; + lapdm_phsap_up(&l1sap->oph, le); + + /* don't free, because we forwarded data */ + return 1; +} + +/* TCH received from bts model */ +static int l1sap_tch_ind(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap, + struct ph_tch_param *tch_ind) +{ + struct gsm_bts *bts = trx->bts; + struct msgb *msg = l1sap->oph.msg; + struct gsm_time g_time; + struct gsm_lchan *lchan; + uint8_t chan_nr; + uint32_t fn; + + chan_nr = tch_ind->chan_nr; + fn = tch_ind->fn; + + gsm_fn2gsmtime(&g_time, fn); + + LOGPGT(DL1P, LOGL_INFO, &g_time, "Rx TCH.ind chan_nr=0x%02x\n", chan_nr); + + lchan = get_active_lchan_by_chan_nr(trx, chan_nr); + if (!lchan) { + LOGPGT(DL1P, LOGL_ERROR, &g_time, "No lchan for TCH.ind (chan_nr=%u)\n", chan_nr); + return 0; + } + + msgb_pull(msg, sizeof(*l1sap)); + + /* Low level layers always call us when TCH content is expected, even if + * the content is not available due to decoding issues. Content not + * available is expected as empty payload. We also check if quality is + * good enough. */ + if (msg->len && tch_ind->lqual_cb / 10 >= bts->min_qual_norm) { + /* hand msg to RTP code for transmission */ + if (lchan->abis_ip.rtp_socket) + osmo_rtp_send_frame_ext(lchan->abis_ip.rtp_socket, + msg->data, msg->len, fn_ms_adj(fn, lchan), lchan->rtp_tx_marker); + /* if loopback is enabled, also queue received RTP data */ + if (lchan->loopback) { + /* make sure the queue doesn't get too long */ + queue_limit_to(gsm_lchan_name(lchan), &lchan->dl_tch_queue, 1); + /* add new frame to queue */ + msgb_enqueue(&lchan->dl_tch_queue, msg); + /* Return 1 to signal that we're still using msg and it should not be freed */ + return 1; + } + /* Only clear the marker bit once we have sent a RTP packet with it */ + lchan->rtp_tx_marker = false; + } else { + DEBUGPGT(DRTP, &g_time, "Skipping RTP frame with lost payload (chan_nr=0x%02x)\n", + chan_nr); + if (lchan->abis_ip.rtp_socket) + osmo_rtp_skipped_frame(lchan->abis_ip.rtp_socket, fn_ms_adj(fn, lchan)); + lchan->rtp_tx_marker = true; + } + + lchan->tch.last_fn = fn; + return 0; +} + +#define RACH_MIN_TOA256 -2 * 256 + +static bool rach_pass_filter(struct ph_rach_ind_param *rach_ind, struct gsm_bts *bts) +{ + int16_t toa256 = rach_ind->acc_delay_256bits; + + /* Check for RACH exceeding BER threshold (ghost RACH) */ + if (rach_ind->ber10k > bts->max_ber10k_rach) { + LOGPFN(DL1C, LOGL_INFO, rach_ind->fn, "Ignoring RACH request: " + "BER10k(%u) > BER10k_MAX(%u)\n", + rach_ind->ber10k, bts->max_ber10k_rach); + return false; + } + + /** + * Make sure that ToA (Timing of Arrival) is acceptable. + * We allow early arrival up to 2 symbols, and delay + * according to maximal allowed Timing Advance value. + */ + if (toa256 < RACH_MIN_TOA256 || toa256 > bts->max_ta * 256) { + LOGPFN(DL1C, LOGL_INFO, rach_ind->fn, "Ignoring RACH request: " + "ToA(%d) exceeds the allowed range (%d..%d)\n", + toa256, RACH_MIN_TOA256, bts->max_ta * 256); + return false; + } + + return true; +} + +/* Special case where handover RACH is detected */ +static int l1sap_handover_rach(struct gsm_bts_trx *trx, + struct osmo_phsap_prim *l1sap, struct ph_rach_ind_param *rach_ind) +{ + /* Filter out noise / interference / ghosts */ + if (!rach_pass_filter(rach_ind, trx->bts)) { + rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_DROP); + return 0; + } + + handover_rach(get_lchan_by_chan_nr(trx, rach_ind->chan_nr), + rach_ind->ra, rach_ind->acc_delay); + + /* must return 0, so in case of msg at l1sap, it will be freed */ + return 0; +} + +/* RACH received from bts model */ +static int l1sap_ph_rach_ind(struct gsm_bts_trx *trx, + struct osmo_phsap_prim *l1sap, struct ph_rach_ind_param *rach_ind) +{ + struct gsm_bts *bts = trx->bts; + struct lapdm_channel *lc; + + DEBUGPFN(DL1P, rach_ind->fn, "Rx PH-RA.ind"); + + /* check for handover access burst on dedicated channels */ + if (!L1SAP_IS_CHAN_RACH(rach_ind->chan_nr)) { + rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_HO); + return l1sap_handover_rach(trx, l1sap, rach_ind); + } + + rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_RCVD); + + /* increment number of busy RACH slots, if required */ + if (rach_ind->rssi >= bts->load.rach.busy_thresh) + bts->load.rach.busy++; + + /* Filter out noise / interference / ghosts */ + if (!rach_pass_filter(rach_ind, bts)) { + rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_DROP); + return 0; + } + + /* increment number of RACH slots with valid non-handover RACH burst */ + bts->load.rach.access++; + + lc = &trx->ts[0].lchan[CCCH_LCHAN].lapdm_ch; + + /* According to 3GPP TS 48.058 § 9.3.17 Access Delay is expressed same way as TA (number of symbols) */ + set_ms_to_data(get_lchan_by_chan_nr(trx, rach_ind->chan_nr), + rach_ind->acc_delay, false); + + /* check for packet access */ + if ((trx == bts->c0 && L1SAP_IS_PACKET_RACH(rach_ind->ra)) || + (trx == bts->c0 && rach_ind->is_11bit)) { + rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_PS); + + LOGPFN(DL1P, LOGL_INFO, rach_ind->fn, "RACH for packet access (toa=%d, ra=%d)\n", + rach_ind->acc_delay, rach_ind->ra); + + pcu_tx_rach_ind(bts, rach_ind->acc_delay << 2, + rach_ind->ra, rach_ind->fn, + rach_ind->is_11bit, rach_ind->burst_type); + return 0; + } + + LOGPFN(DL1P, LOGL_INFO, rach_ind->fn, "RACH for RR access (toa=%d, ra=%d)\n", + rach_ind->acc_delay, rach_ind->ra); + rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_RACH_CS); + lapdm_phsap_up(&l1sap->oph, &lc->lapdm_dcch); + + return 0; +} + +/* Process any L1 prim received from bts model. + * + * This function takes ownership of the msgb. + * If l1sap contains a msgb, it assumes that msgb->l2h was set by lower layer. + */ +int l1sap_up(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) +{ + struct msgb *msg = l1sap->oph.msg; + int rc = 0; + + switch (OSMO_PRIM_HDR(&l1sap->oph)) { + case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_INDICATION): + rc = l1sap_mph_info_ind(trx, l1sap, &l1sap->u.info); + break; + case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_CONFIRM): + rc = l1sap_mph_info_cnf(trx, l1sap, &l1sap->u.info); + break; + case OSMO_PRIM(PRIM_PH_RTS, PRIM_OP_INDICATION): + rc = l1sap_ph_rts_ind(trx, l1sap, &l1sap->u.data); + break; + case OSMO_PRIM(PRIM_TCH_RTS, PRIM_OP_INDICATION): + rc = l1sap_tch_rts_ind(trx, l1sap, &l1sap->u.tch); + break; + case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_INDICATION): + to_gsmtap(trx, l1sap); + rc = l1sap_ph_data_ind(trx, l1sap, &l1sap->u.data); + break; + case OSMO_PRIM(PRIM_TCH, PRIM_OP_INDICATION): + rc = l1sap_tch_ind(trx, l1sap, &l1sap->u.tch); + break; + case OSMO_PRIM(PRIM_PH_RACH, PRIM_OP_INDICATION): + to_gsmtap(trx, l1sap); + rc = l1sap_ph_rach_ind(trx, l1sap, &l1sap->u.rach_ind); + break; + default: + LOGP(DL1P, LOGL_NOTICE, "unknown prim %d op %d\n", + l1sap->oph.primitive, l1sap->oph.operation); + oml_fail_rep(OSMO_EVT_MAJ_UKWN_MSG, "unknown prim %d op %d", + l1sap->oph.primitive, l1sap->oph.operation); + break; + } + + /* Special return value '1' means: do not free */ + if (rc != 1) + msgb_free(msg); + + return rc; +} + +/* any L1 prim sent to bts model */ +static int l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) +{ + if (OSMO_PRIM_HDR(&l1sap->oph) == + OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST)) + to_gsmtap(trx, l1sap); + + return bts_model_l1sap_down(trx, l1sap); +} + +/* pcu (socket interface) sends us a data request primitive */ +int l1sap_pdch_req(struct gsm_bts_trx_ts *ts, int is_ptcch, uint32_t fn, + uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len) +{ + struct msgb *msg; + struct osmo_phsap_prim *l1sap; + struct gsm_time g_time; + + gsm_fn2gsmtime(&g_time, fn); + + DEBUGP(DL1P, "TX packet data %s is_ptcch=%d trx=%d ts=%d " + "block_nr=%d, arfcn=%d, len=%d\n", osmo_dump_gsmtime(&g_time), + is_ptcch, ts->trx->nr, ts->nr, block_nr, arfcn, len); + + msg = l1sap_msgb_alloc(len); + l1sap = msgb_l1sap_prim(msg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA, PRIM_OP_REQUEST, + msg); + l1sap->u.data.chan_nr = RSL_CHAN_OSMO_PDCH | ts->nr; + l1sap->u.data.link_id = 0x00; + l1sap->u.data.fn = fn; + msg->l2h = msgb_put(msg, len); + memcpy(msg->l2h, data, len); + + return l1sap_down(ts->trx, l1sap); +} + +/*! \brief call-back function for incoming RTP */ +void l1sap_rtp_rx_cb(struct osmo_rtp_socket *rs, const uint8_t *rtp_pl, + unsigned int rtp_pl_len, uint16_t seq_number, + uint32_t timestamp, bool marker) +{ + struct gsm_lchan *lchan = rs->priv; + struct msgb *msg; + struct osmo_phsap_prim *l1sap; + + /* if we're in loopback mode, we don't accept frames from the + * RTP socket anymore */ + if (lchan->loopback) + return; + + msg = l1sap_msgb_alloc(rtp_pl_len); + if (!msg) + return; + memcpy(msgb_put(msg, rtp_pl_len), rtp_pl, rtp_pl_len); + msgb_pull(msg, sizeof(*l1sap)); + + /* Store RTP header Marker bit in control buffer */ + rtpmsg_marker_bit(msg) = marker; + /* Store RTP header Sequence Number in control buffer */ + rtpmsg_seq(msg) = seq_number; + /* Store RTP header Timestamp in control buffer */ + rtpmsg_ts(msg) = timestamp; + + /* make sure the queue doesn't get too long */ + queue_limit_to(gsm_lchan_name(lchan), &lchan->dl_tch_queue, 1); + + msgb_enqueue(&lchan->dl_tch_queue, msg); +} + +static int l1sap_chan_act_dact_modify(struct gsm_bts_trx *trx, uint8_t chan_nr, + enum osmo_mph_info_type type, uint8_t sacch_only) +{ + struct osmo_phsap_prim l1sap; + + memset(&l1sap, 0, sizeof(l1sap)); + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_REQUEST, + NULL); + l1sap.u.info.type = type; + l1sap.u.info.u.act_req.chan_nr = chan_nr; + l1sap.u.info.u.act_req.sacch_only = sacch_only; + + return l1sap_down(trx, &l1sap); +} + +int l1sap_chan_act(struct gsm_bts_trx *trx, uint8_t chan_nr, struct tlv_parsed *tp) +{ + struct gsm_lchan *lchan = get_lchan_by_chan_nr(trx, chan_nr); + struct gsm48_chan_desc *cd; + int rc; + + LOGP(DL1C, LOGL_INFO, "activating channel chan_nr=0x%02x trx=%d\n", + chan_nr, trx->nr); + + /* osmo-pcu calls this without a valid 'tp' parameter, so we + * need to make sure ew don't crash here */ + if (tp && TLVP_PRESENT(tp, GSM48_IE_CHANDESC_2) && + TLVP_LEN(tp, GSM48_IE_CHANDESC_2) >= sizeof(*cd)) { + cd = (struct gsm48_chan_desc *) + TLVP_VAL(tp, GSM48_IE_CHANDESC_2); + + /* our L1 only supports one global TSC for all channels + * one one TRX, so we need to make sure not to activate + * channels with a different TSC!! */ + if (cd->h0.tsc != (lchan->ts->trx->bts->bsic & 7)) { + LOGP(DL1C, LOGL_ERROR, "lchan TSC %u != BSIC-TSC %u\n", + cd->h0.tsc, lchan->ts->trx->bts->bsic & 7); + return -RSL_ERR_SERV_OPT_UNIMPL; + } + } + + lchan->sacch_deact = 0; + lchan->s = lchan->ts->trx->bts->radio_link_timeout; + + rc = l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_ACTIVATE, 0); + if (rc) + return -RSL_ERR_EQUIPMENT_FAIL; + + /* Init DTX DL FSM if necessary */ + if (trx->bts->dtxd && lchan->type != GSM_LCHAN_SDCCH) { + char name[32]; + snprintf(name, sizeof(name), "bts%u-trx%u-ts%u-ss%u", lchan->ts->trx->bts->nr, + lchan->ts->trx->nr, lchan->ts->nr, lchan->nr); + lchan->tch.dtx.dl_amr_fsm = osmo_fsm_inst_alloc(&dtx_dl_amr_fsm, + tall_bts_ctx, + lchan, + LOGL_DEBUG, + name); + if (!lchan->tch.dtx.dl_amr_fsm) { + l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_DEACTIVATE, 0); + return -RSL_ERR_EQUIPMENT_FAIL; + } + } + return 0; +} + +int l1sap_chan_rel(struct gsm_bts_trx *trx, uint8_t chan_nr) +{ + struct gsm_lchan *lchan = get_lchan_by_chan_nr(trx, chan_nr); + LOGP(DL1C, LOGL_INFO, "deactivating channel chan_nr=0x%02x trx=%d\n", + chan_nr, trx->nr); + + if (lchan->tch.dtx.dl_amr_fsm) { + osmo_fsm_inst_free(lchan->tch.dtx.dl_amr_fsm); + lchan->tch.dtx.dl_amr_fsm = NULL; + } + + return l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_DEACTIVATE, + 0); +} + +int l1sap_chan_deact_sacch(struct gsm_bts_trx *trx, uint8_t chan_nr) +{ + struct gsm_lchan *lchan = get_lchan_by_chan_nr(trx, chan_nr); + + LOGP(DL1C, LOGL_INFO, "deactivating sacch chan_nr=0x%02x trx=%d\n", + chan_nr, trx->nr); + + lchan->sacch_deact = 1; + + return l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_DEACTIVATE, + 1); +} + +int l1sap_chan_modify(struct gsm_bts_trx *trx, uint8_t chan_nr) +{ + LOGP(DL1C, LOGL_INFO, "modifying channel chan_nr=0x%02x trx=%d\n", + chan_nr, trx->nr); + + return l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_MODIFY, 0); +} diff --git a/src/common/lchan.c b/src/common/lchan.c new file mode 100644 index 0000000..9e98166 --- /dev/null +++ b/src/common/lchan.c @@ -0,0 +1,49 @@ +/* OsmoBTS lchan interface */ + +/* (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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +void lchan_set_state(struct gsm_lchan *lchan, enum gsm_lchan_state state) +{ + DEBUGP(DL1C, "%s state %s -> %s\n", + gsm_lchan_name(lchan), + gsm_lchans_name(lchan->state), + gsm_lchans_name(state)); + lchan->state = state; +} + +bool ts_is_pdch(const struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case GSM_PCHAN_PDCH: + return true; + case GSM_PCHAN_TCH_F_PDCH: + return (ts->flags & TS_F_PDCH_ACTIVE) + && !(ts->flags & TS_F_PDCH_PENDING_MASK); + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + return ts->dyn.pchan_is == GSM_PCHAN_PDCH + && ts->dyn.pchan_want == ts->dyn.pchan_is; + default: + return false; + } +} diff --git a/src/common/load_indication.c b/src/common/load_indication.c new file mode 100644 index 0000000..e91f6d4 --- /dev/null +++ b/src/common/load_indication.c @@ -0,0 +1,94 @@ +/* Support for generating RSL Load Indication */ + +/* (C) 2011 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include + +#include +#include + +#include +#include +#include + +static void reset_load_counters(struct gsm_bts *bts) +{ + /* re-set the counters */ + bts->load.ccch.pch_used = bts->load.ccch.pch_total = 0; +} + +static void load_timer_cb(void *data) +{ + struct gsm_bts *bts = data; + unsigned int pch_percent, rach_percent; + + /* compute percentages */ + if (bts->load.ccch.pch_total == 0) + pch_percent = 0; + else + pch_percent = (bts->load.ccch.pch_used * 100) / + bts->load.ccch.pch_total; + + if (pch_percent >= bts->load.ccch.load_ind_thresh) { + /* send RSL load indication message to BSC */ + uint16_t buffer_space = paging_buffer_space(bts->paging_state); + rsl_tx_ccch_load_ind_pch(bts, buffer_space); + } else { + /* This is an extenstion of TS 08.58. We don't only + * send load indications if the load is above threshold, + * but we also explicitly indicate that we are below + * threshold by using the magic value 0xffff */ + rsl_tx_ccch_load_ind_pch(bts, 0xffff); + } + + if (bts->load.rach.total == 0) + rach_percent = 0; + else + rach_percent = (bts->load.rach.busy * 100) / + bts->load.rach.total; + + if (rach_percent >= bts->load.ccch.load_ind_thresh) { + /* send RSL load indication message to BSC */ + rsl_tx_ccch_load_ind_rach(bts, bts->load.rach.total, + bts->load.rach.busy, + bts->load.rach.access); + } + + reset_load_counters(bts); + + /* re-schedule the timer */ + osmo_timer_schedule(&bts->load.ccch.timer, + bts->load.ccch.load_ind_period, 0); +} + +void load_timer_start(struct gsm_bts *bts) +{ + if (!bts->load.ccch.timer.data) { + bts->load.ccch.timer.data = bts; + bts->load.ccch.timer.cb = load_timer_cb; + reset_load_counters(bts); + } + osmo_timer_schedule(&bts->load.ccch.timer, bts->load.ccch.load_ind_period, 0); +} + +void load_timer_stop(struct gsm_bts *bts) +{ + osmo_timer_del(&bts->load.ccch.timer); +} diff --git a/src/common/logging.c b/src/common/logging.c new file mode 100644 index 0000000..3315a01 --- /dev/null +++ b/src/common/logging.c @@ -0,0 +1,150 @@ +/* libosmocore logging support */ + +/* (C) 2011 by Andreas Eversberg + * (C) 2011 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +#include + +#include +#include +#include + +#include +#include + +static struct log_info_cat bts_log_info_cat[] = { + [DRSL] = { + .name = "DRSL", + .description = "A-bis Radio Siganlling Link (RSL)", + .color = "\033[1;35m", + .enabled = 1, .loglevel = LOGL_INFO, + }, + [DOML] = { + .name = "DOML", + .description = "A-bis Network Management / O&M (NM/OML)", + .color = "\033[1;36m", + .enabled = 1, .loglevel = LOGL_INFO, + }, + [DRLL] = { + .name = "DRLL", + .description = "A-bis Radio Link Layer (RLL)", + .color = "\033[1;31m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DRR] = { + .name = "DRR", + .description = "Layer3 Radio Resource (RR)", + .color = "\033[1;34m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DMEAS] = { + .name = "DMEAS", + .description = "Radio Measurement Processing", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DPAG] = { + .name = "DPAG", + .description = "Paging Subsystem", + .color = "\033[1;38m", + .enabled = 1, .loglevel = LOGL_INFO, + }, + [DL1C] = { + .name = "DL1C", + .description = "Layer 1 Control (MPH)", + .loglevel = LOGL_INFO, + .enabled = 1, + }, + [DL1P] = { + .name = "DL1P", + .description = "Layer 1 Primitives (PH)", + .loglevel = LOGL_INFO, + .enabled = 0, + }, + [DDSP] = { + .name = "DDSP", + .description = "DSP Trace Messages", + .loglevel = LOGL_DEBUG, + .enabled = 1, + }, + [DABIS] = { + .name = "DABIS", + .description = "A-bis Intput Subsystem", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DRTP] = { + .name = "DRTP", + .description = "Realtime Transfer Protocol", + .loglevel = LOGL_NOTICE, + .enabled = 1, + }, + [DPCU] = { + .name = "DPCU", + .description = "PCU interface", + .loglevel = LOGL_NOTICE, + .enabled = 1, + }, + [DHO] = { + .name = "DHO", + .description = "Handover", + .color = "\033[0;37m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DTRX] = { + .name = "DTRX", + .description = "TRX interface", + .color = "\033[1;33m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DLOOP] = { + .name = "DLOOP", + .description = "Control loops", + .color = "\033[0;34m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, +#if 0 + [DNS] = { + .name = "DNS", + .description = "GPRS Network Service (NS)", + .enabled = 1, .loglevel = LOGL_INFO, + }, + [DBSSGP] = { + .name = "DBSSGP", + .description = "GPRS BSS Gateway Protocol (BSSGP)", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, + [DLLC] = { + .name = "DLLC", + .description = "GPRS Logical Link Control Protocol (LLC)", + .enabled = 1, .loglevel = LOGL_DEBUG, + }, +#endif + [DSUM] = { + .name = "DSUM", + .description = "DSUM", + .loglevel = LOGL_NOTICE, + .enabled = 1, + }, +}; + +const struct log_info bts_log_info = { + .cat = bts_log_info_cat, + .num_cat = ARRAY_SIZE(bts_log_info_cat), +}; diff --git a/src/common/main.c b/src/common/main.c new file mode 100644 index 0000000..9121a2a --- /dev/null +++ b/src/common/main.c @@ -0,0 +1,368 @@ +/* Main program for Osmocom BTS */ + +/* (C) 2011-2016 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int quit = 0; +static const char *config_file = "osmo-bts.cfg"; +static int daemonize = 0; +static int rt_prio = -1; +static int trx_num = 1; +static char *gsmtap_ip = 0; +extern int g_vty_port_num; + +static void print_help() +{ + printf( "Some useful options:\n" + " -h --help this text\n" + " -d --debug MASK Enable debugging (e.g. -d DRSL:DOML:DLAPDM)\n" + " -D --daemonize For the process into a background daemon\n" + " -c --config-file Specify the filename of the config file\n" + " -s --disable-color Don't use colors in stderr log output\n" + " -T --timestamp Prefix every log line with a timestamp\n" + " -V --version Print version information and exit\n" + " -e --log-level Set a global log-level\n" + " -r --realtime PRIO Use SCHED_RR with the specified priority\n" + " -i --gsmtap-ip The destination IP used for GSMTAP.\n" + " -t --trx-num Set number of TRX (default=%d)\n", + trx_num + ); + bts_model_print_help(); +} + +/* FIXME: finally get some option parsing code into libosmocore */ +static void handle_options(int argc, char **argv) +{ + char *argv_out[argc]; + int argc_out = 0; + + argv_out[argc_out++] = argv[0]; + + /* disable generation of error messages on encountering unknown + * options */ + opterr = 0; + + while (1) { + int option_idx = 0, c; + static const struct option long_options[] = { + /* FIXME: all those are generic Osmocom app 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' }, + { "log-level", 1, 0, 'e' }, + /* FIXME: generic BTS app options */ + { "gsmtap-ip", 1, 0, 'i' }, + { "trx-num", 1, 0, 't' }, + { "realtime", 1, 0, 'r' }, + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "-hc:d:Dc:sTVe:i:t:r:", + long_options, &option_idx); + if (c == -1) + break; + + switch (c) { + case 'h': + print_help(); + exit(0); + break; + 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': + rt_prio = atoi(optarg); + break; + case 'i': + gsmtap_ip = optarg; + break; + case 't': + trx_num = atoi(optarg); + if (trx_num < 1) + trx_num = 1; + break; + case '?': + case 1: + /* prepare argv[] for bts_model */ + argv_out[argc_out++] = argv[optind-1]; + break; + default: + break; + } + } + + /* re-set opt-ind for new parsig round */ + optind = 1; + /* enable error-checking for the following getopt call */ + opterr = 1; + if (bts_model_handle_options(argc_out, argv_out)) { + print_help(); + exit(1); + } +} + +static struct gsm_bts *bts; + +static void signal_handler(int signal) +{ + fprintf(stderr, "signal %u received\n", signal); + + switch (signal) { + case SIGINT: + case SIGTERM: + if (!quit) { + oml_fail_rep(OSMO_EVT_CRIT_PROC_STOP, + "BTS: SIGINT received -> shutdown"); + bts_shutdown(bts, "SIGINT"); + } + quit++; + break; + case SIGABRT: + case SIGUSR1: + case SIGUSR2: + oml_fail_rep(OSMO_EVT_CRIT_PROC_STOP, + "BTS: signal %d (%s) received", signal, + strsignal(signal)); + talloc_report_full(tall_bts_ctx, stderr); + break; + default: + break; + } +} + +static int write_pid_file(char *procname) +{ + FILE *outf; + char tmp[PATH_MAX+1]; + + snprintf(tmp, sizeof(tmp)-1, "/var/run/%s.pid", procname); + tmp[PATH_MAX-1] = '\0'; + + outf = fopen(tmp, "w"); + if (!outf) + return -1; + + fprintf(outf, "%d\n", getpid()); + + fclose(outf); + + return 0; +} + +int bts_main(int argc, char **argv) +{ + struct gsm_bts_trx *trx; + struct e1inp_line *line; + int rc, i; + + printf("((*))\n |\n / \\ OsmoBTS\n"); + + /* Track the use of talloc NULL memory contexts */ + talloc_enable_null_tracking(); + + tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context"); + msgb_talloc_ctx_init(tall_bts_ctx, 100*1024); + bts_vty_info.tall_ctx = tall_bts_ctx; + + osmo_init_logging2(tall_bts_ctx, &bts_log_info); + vty_init(&bts_vty_info); + ctrl_vty_init(tall_bts_ctx); + rate_ctr_init(tall_bts_ctx); + + handle_options(argc, argv); + + bts = gsm_bts_alloc(tall_bts_ctx, 0); + if (!bts) { + fprintf(stderr, "Failed to create BTS structure\n"); + exit(1); + } + for (i = 1; i < trx_num; i++) { + trx = gsm_bts_trx_alloc(bts); + if (!trx) { + fprintf(stderr, "Failed to create TRX structure\n"); + exit(1); + } + } + e1inp_vty_init(); + bts_vty_init(bts, &bts_log_info); + + /* enable realtime priority for us */ + if (rt_prio != -1) { + struct sched_param param; + + memset(¶m, 0, sizeof(param)); + param.sched_priority = rt_prio; + rc = sched_setscheduler(getpid(), SCHED_RR, ¶m); + if (rc != 0) { + fprintf(stderr, "Setting SCHED_RR priority(%d) failed: %s\n", + param.sched_priority, strerror(errno)); + exit(1); + } + } + + if (gsmtap_ip) { + gsmtap = gsmtap_source_init(gsmtap_ip, GSMTAP_UDP_PORT, 1); + if (!gsmtap) { + fprintf(stderr, "Failed during gsmtap_init()\n"); + exit(1); + } + gsmtap_source_add_sink(gsmtap); + } + + if (bts_init(bts) < 0) { + fprintf(stderr, "unable to open bts\n"); + exit(1); + } + + abis_init(bts); + + rc = vty_read_config_file(config_file, NULL); + if (rc < 0) { + fprintf(stderr, "Failed to parse the config file: '%s'\n", + config_file); + exit(1); + } + + if (!phy_link_by_num(0)) { + fprintf(stderr, "You need to configure at least phy0\n"); + exit(1); + } + + llist_for_each_entry(trx, &bts->trx_list, list) { + if (!trx->role_bts.l1h) { + fprintf(stderr, "TRX %u has no associated PHY instance\n", + trx->nr); + exit(1); + } + } + + write_pid_file("osmo-bts"); + + bts_controlif_setup(bts, ctrl_vty_get_bind_addr(), OSMO_CTRL_PORT_BTS); + + rc = telnet_init_dynif(tall_bts_ctx, NULL, vty_get_bind_addr(), + g_vty_port_num); + if (rc < 0) { + fprintf(stderr, "Error initializing telnet\n"); + exit(1); + } + + if (pcu_sock_init(bts->pcu.sock_path)) { + fprintf(stderr, "PCU L1 socket failed\n"); + exit(1); + } + + 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 (!bts->bsc_oml_host) { + fprintf(stderr, "Cannot start BTS without knowing BSC OML IP\n"); + exit(1); + } + + line = abis_open(bts, bts->bsc_oml_host, "sysmoBTS"); + if (!line) { + fprintf(stderr, "unable to connect to BSC\n"); + exit(2); + } + + rc = phy_links_open(); + if (rc < 0) { + fprintf(stderr, "unable to open PHY link(s)\n"); + exit(2); + } + + if (daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + exit(1); + } + } + + while (quit < 2) { + log_reset_context(); + osmo_select_main(0); + } + + return EXIT_SUCCESS; +} diff --git a/src/common/measurement.c b/src/common/measurement.c new file mode 100644 index 0000000..ba7494a --- /dev/null +++ b/src/common/measurement.c @@ -0,0 +1,418 @@ + +#include +#include + +#include + +#include +#include +#include +#include + +/* Tables as per TS 45.008 Section 8.3 */ +static const uint8_t ts45008_83_tch_f[] = { 52, 53, 54, 55, 56, 57, 58, 59 }; +static const uint8_t ts45008_83_tch_hs0[] = { 0, 2, 4, 6, 52, 54, 56, 58 }; +static const uint8_t ts45008_83_tch_hs1[] = { 14, 16, 18, 29, 66, 68, 70, 72 }; + +/* find out if an array contains a given key as element */ +#define ARRAY_CONTAINS(arr, val) array_contains(arr, ARRAY_SIZE(arr), val) +static bool array_contains(const uint8_t *arr, unsigned int len, uint8_t val) { + int i; + for (i = 0; i < len; i++) { + if (arr[i] == val) + return true; + } + return false; +} + +/* Decide if a given frame number is part of the "-SUB" measurements (true) or not (false) */ +static bool ts45008_83_is_sub(struct gsm_lchan *lchan, uint32_t fn, bool is_amr_sid_update) +{ + uint32_t fn104 = fn % 104; + + /* See TS 45.008 Sections 8.3 and 8.4 for a detailed descriptions of the rules + * implemented here. We only implement the logic for Voice, not CSD */ + + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_EFR: + if (trx_sched_is_sacch_fn(lchan->ts, fn, true)) + return true; + if (ARRAY_CONTAINS(ts45008_83_tch_f, fn104)) + return true; + break; + case GSM48_CMODE_SPEECH_AMR: + if (trx_sched_is_sacch_fn(lchan->ts, fn, true)) + return true; + if (is_amr_sid_update) + return true; + break; + default: + LOGPFN(DMEAS, LOGL_ERROR, fn, "%s: Unsupported lchan->tch_mode %u\n", + gsm_lchan_name(lchan), lchan->tch_mode); + break; + } + break; + case GSM_LCHAN_TCH_H: + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + if (trx_sched_is_sacch_fn(lchan->ts, fn, true)) + return true; + switch (lchan->nr) { + case 0: + if (ARRAY_CONTAINS(ts45008_83_tch_hs0, fn104)) + return true; + break; + case 1: + if (ARRAY_CONTAINS(ts45008_83_tch_hs1, fn104)) + return true; + break; + default: + OSMO_ASSERT(0); + } + break; + case GSM48_CMODE_SPEECH_AMR: + if (trx_sched_is_sacch_fn(lchan->ts, fn, true)) + return true; + if (is_amr_sid_update) + return true; + break; + case GSM48_CMODE_SIGN: + /* No DTX allowed; SUB=FULL, therefore measurements at all frame numbers are + * SUB */ + return true; + default: + LOGPFN(DMEAS, LOGL_ERROR, fn, "%s: Unsupported lchan->tch_mode %u\n", + gsm_lchan_name(lchan), lchan->tch_mode); + break; + } + break; + case GSM_LCHAN_SDCCH: + /* No DTX allowed; SUB=FULL, therefore measurements at all frame numbers are SUB */ + return true; + default: + break; + } + return false; +} + +/* Measurement reporting period and mapping of SACCH message block for TCHF + * and TCHH chan As per in 3GPP TS 45.008, section 8.4.1. + * + * Timeslot number (TN) TDMA frame number (FN) modulo 104 + * Half rate, Half rate, Reporting SACCH + * Full Rate subch.0 subch.1 period Message block + * 0 0 and 1 0 to 103 12, 38, 64, 90 + * 1 0 and 1 13 to 12 25, 51, 77, 103 + * 2 2 and 3 26 to 25 38, 64, 90, 12 + * 3 2 and 3 39 to 38 51, 77, 103, 25 + * 4 4 and 5 52 to 51 64, 90, 12, 38 + * 5 4 and 5 65 to 64 77, 103, 25, 51 + * 6 6 and 7 78 to 77 90, 12, 38, 64 + * 7 6 and 7 91 to 90 103, 25, 51, 77 */ + +static const uint8_t tchf_meas_rep_fn104[] = { + [0] = 90, + [1] = 103, + [2] = 12, + [3] = 25, + [4] = 38, + [5] = 51, + [6] = 64, + [7] = 77, +}; +static const uint8_t tchh0_meas_rep_fn104[] = { + [0] = 90, + [1] = 90, + [2] = 12, + [3] = 12, + [4] = 38, + [5] = 38, + [6] = 64, + [7] = 64, +}; +static const uint8_t tchh1_meas_rep_fn104[] = { + [0] = 103, + [1] = 103, + [2] = 25, + [3] = 25, + [4] = 51, + [5] = 51, + [6] = 77, + [7] = 77, +}; + +/* Measurement reporting period for SDCCH8 and SDCCH4 chan + * As per in 3GPP TS 45.008, section 8.4.2. + * + * Logical Chan TDMA frame number + * (FN) modulo 102 + * + * SDCCH/8 12 to 11 + * SDCCH/4 37 to 36 + */ + +/* FN of the first burst whose block completes before reaching fn%102=11 */ +static const uint8_t sdcch8_meas_rep_fn102[] = { + [0] = 66, /* 15(SDCCH), 47(SACCH), 66(SDCCH) */ + [1] = 70, /* 19(SDCCH), 51(SACCH), 70(SDCCH) */ + [2] = 74, /* 23(SDCCH), 55(SACCH), 74(SDCCH) */ + [3] = 78, /* 27(SDCCH), 59(SACCH), 78(SDCCH) */ + [4] = 98, /* 31(SDCCH), 98(SACCH), 82(SDCCH) */ + [5] = 0, /* 35(SDCCH), 0(SACCH), 86(SDCCH) */ + [6] = 4, /* 39(SDCCH), 4(SACCH), 90(SDCCH) */ + [7] = 8, /* 43(SDCCH), 8(SACCH), 94(SDCCH) */ +}; + +/* FN of the first burst whose block completes before reaching fn%102=37 */ +static const uint8_t sdcch4_meas_rep_fn102[] = { + [0] = 88, /* 37(SDCCH), 57(SACCH), 88(SDCCH) */ + [1] = 92, /* 41(SDCCH), 61(SACCH), 92(SDCCH) */ + [2] = 6, /* 6(SACCH), 47(SDCCH), 98(SDCCH) */ + [3] = 10 /* 10(SACCH), 0(SDCCH), 51(SDCCH) */ +}; + +/* Note: The reporting of the measurement results is done via the SACCH channel. + * The measurement interval is not aligned with the interval in which the + * SACCH is transmitted. When we receive the measurement indication with the + * SACCH block, the corresponding measurement interval will already have ended + * and we will get the results late, but on spot with the beginning of the + * next measurement interval. + * + * For example: We get a measurement indication on FN%104=38 in TS=2. Then we + * will have to look at 3GPP TS 45.008, section 8.4.1 (or 3GPP TS 05.02 Clause 7 + * Table 1 of 9) what value we need to feed into the lookup tables in order to + * detect the measurement period ending. In this example the "real" ending + * was on FN%104=12. This is the value we have to look for in + * tchf_meas_rep_fn104 to know that a measurement period has just ended. */ + +/* See also 3GPP TS 05.02 Clause 7 Table 1 of 9: + * Mapping of logical channels onto physical channels (see subclauses 6.3, 6.4, 6.5) */ +static uint8_t translate_tch_meas_rep_fn104(uint8_t fn_mod) +{ + switch (fn_mod) { + case 25: + return 103; + case 38: + return 12; + case 51: + return 25; + case 64: + return 38; + case 77: + return 51; + case 90: + return 64; + case 103: + return 77; + case 12: + return 90; + } + + /* Invalid / not of interest */ + return 0; +} + +/* determine if a measurement period ends at the given frame number */ +static int is_meas_complete(struct gsm_lchan *lchan, uint32_t fn) +{ + unsigned int fn_mod = -1; + const uint8_t *tbl; + int rc = 0; + enum gsm_phys_chan_config pchan = ts_pchan(lchan->ts); + + if (lchan->ts->nr >= 8) + return -EINVAL; + if (pchan >= _GSM_PCHAN_MAX) + return -EINVAL; + + switch (pchan) { + case GSM_PCHAN_TCH_F: + fn_mod = translate_tch_meas_rep_fn104(fn % 104); + if (tchf_meas_rep_fn104[lchan->ts->nr] == fn_mod) + rc = 1; + break; + case GSM_PCHAN_TCH_H: + fn_mod = translate_tch_meas_rep_fn104(fn % 104); + if (lchan->nr == 0) + tbl = tchh0_meas_rep_fn104; + else + tbl = tchh1_meas_rep_fn104; + if (tbl[lchan->ts->nr] == fn_mod) + rc = 1; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + fn_mod = fn % 102; + if (sdcch8_meas_rep_fn102[lchan->nr] == fn_mod) + rc = 1; + break; + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + fn_mod = fn % 102; + if (sdcch4_meas_rep_fn102[lchan->nr] == fn_mod) + rc = 1; + break; + default: + rc = 0; + break; + } + + if (rc == 1) { + DEBUGP(DMEAS, + "%s meas period end fn:%u, fn_mod:%i, status:%d, pchan:%s\n", + gsm_lchan_name(lchan), fn, fn_mod, rc, gsm_pchan_name(pchan)); + } + + return rc; +} + +/* receive a L1 uplink measurement from L1 */ +int lchan_new_ul_meas(struct gsm_lchan *lchan, struct bts_ul_meas *ulm, uint32_t fn) +{ + if (lchan->state != LCHAN_S_ACTIVE) { + LOGPFN(DMEAS, LOGL_NOTICE, fn, + "%s measurement during state: %s, num_ul_meas=%d\n", + gsm_lchan_name(lchan), gsm_lchans_name(lchan->state), + lchan->meas.num_ul_meas); + } + + if (lchan->meas.num_ul_meas >= ARRAY_SIZE(lchan->meas.uplink)) { + LOGPFN(DMEAS, LOGL_NOTICE, fn, + "%s no space for uplink measurement, num_ul_meas=%d\n", + gsm_lchan_name(lchan), lchan->meas.num_ul_meas); + return -ENOSPC; + } + + /* We expect the lower layers to mark AMR SID_UPDATE frames already as such. + * In this function, we only deal with the comon logic as per the TS 45.008 tables */ + if (!ulm->is_sub) + ulm->is_sub = ts45008_83_is_sub(lchan, fn, false); + + DEBUGPFN(DMEAS, fn, "%s adding measurement (is_sub=%u), num_ul_meas=%d\n", + gsm_lchan_name(lchan), ulm->is_sub, lchan->meas.num_ul_meas); + + memcpy(&lchan->meas.uplink[lchan->meas.num_ul_meas++], ulm, + sizeof(*ulm)); + + return 0; +} + +/* input: BER in steps of .01%, i.e. percent/100 */ +static uint8_t ber10k_to_rxqual(uint32_t ber10k) +{ + /* Eight levels of Rx quality are defined and are mapped to the + * equivalent BER before channel decoding, as per in 3GPP TS 45.008, + * secton 8.2.4. + * + * RxQual: BER Range: + * RXQUAL_0 BER < 0,2 % Assumed value = 0,14 % + * RXQUAL_1 0,2 % < BER < 0,4 % Assumed value = 0,28 % + * RXQUAL_2 0,4 % < BER < 0,8 % Assumed value = 0,57 % + * RXQUAL_3 0,8 % < BER < 1,6 % Assumed value = 1,13 % + * RXQUAL_4 1,6 % < BER < 3,2 % Assumed value = 2,26 % + * RXQUAL_5 3,2 % < BER < 6,4 % Assumed value = 4,53 % + * RXQUAL_6 6,4 % < BER < 12,8 % Assumed value = 9,05 % + * RXQUAL_7 12,8 % < BER Assumed value = 18,10 % */ + + if (ber10k < 20) + return 0; + if (ber10k < 40) + return 1; + if (ber10k < 80) + return 2; + if (ber10k < 160) + return 3; + if (ber10k < 320) + return 4; + if (ber10k < 640) + return 5; + if (ber10k < 1280) + return 6; + return 7; +} + +int lchan_meas_check_compute(struct gsm_lchan *lchan, uint32_t fn) +{ + struct gsm_meas_rep_unidir *mru; + uint32_t ber_full_sum = 0; + uint32_t irssi_full_sum = 0; + uint32_t ber_sub_sum = 0; + uint32_t irssi_sub_sum = 0; + int32_t ta256b_sum = 0; + unsigned int num_meas_sub = 0; + int i; + + /* if measurement period is not complete, abort */ + if (!is_meas_complete(lchan, fn)) + return 0; + + /* if there are no measurements, skip computation */ + if (lchan->meas.num_ul_meas == 0) + return 0; + + /* compute the actual measurements */ + + /* step 1: add up */ + for (i = 0; i < lchan->meas.num_ul_meas; i++) { + struct bts_ul_meas *m = &lchan->meas.uplink[i]; + + ber_full_sum += m->ber10k; + irssi_full_sum += m->inv_rssi; + ta256b_sum += m->ta_offs_256bits; + + if (m->is_sub) { + num_meas_sub++; + ber_sub_sum += m->ber10k; + irssi_sub_sum += m->inv_rssi; + } + } + + /* step 2: divide */ + ber_full_sum = ber_full_sum / lchan->meas.num_ul_meas; + irssi_full_sum = irssi_full_sum / lchan->meas.num_ul_meas; + ta256b_sum = ta256b_sum / lchan->meas.num_ul_meas; + + if (num_meas_sub) { + ber_sub_sum = ber_sub_sum / num_meas_sub; + irssi_sub_sum = irssi_sub_sum / num_meas_sub; + } else { + LOGP(DMEAS, LOGL_ERROR, "%s No measurements for SUB!!!\n", gsm_lchan_name(lchan)); + /* The only situation in which this can occur is if the related uplink burst/block was + * missing, so let's set BER to 100% and level to lowest possible. */ + ber_sub_sum = 10000; /* 100% */ + irssi_sub_sum = 120; /* -120 dBm */ + } + + LOGP(DMEAS, LOGL_INFO, "%s Computed TA256(% 4d) BER-FULL(%2u.%02u%%), RSSI-FULL(-%3udBm), " + "BER-SUB(%2u.%02u%%), RSSI-SUB(-%3udBm)\n", gsm_lchan_name(lchan), + ta256b_sum, ber_full_sum/100, + ber_full_sum%100, irssi_full_sum, ber_sub_sum/100, ber_sub_sum%100, + irssi_sub_sum); + + /* store results */ + mru = &lchan->meas.ul_res; + mru->full.rx_lev = dbm2rxlev((int)irssi_full_sum * -1); + mru->sub.rx_lev = dbm2rxlev((int)irssi_sub_sum * -1); + mru->full.rx_qual = ber10k_to_rxqual(ber_full_sum); + mru->sub.rx_qual = ber10k_to_rxqual(ber_sub_sum); + lchan->meas.ms_toa256 = ta256b_sum; + + LOGP(DMEAS, LOGL_INFO, "%s UL MEAS RXLEV_FULL(%u), RXLEV_SUB(%u)," + "RXQUAL_FULL(%u), RXQUAL_SUB(%u), num_meas_sub(%u), num_ul_meas(%u) \n", + gsm_lchan_name(lchan), + mru->full.rx_lev, + mru->sub.rx_lev, + mru->full.rx_qual, + mru->sub.rx_qual, num_meas_sub, lchan->meas.num_ul_meas); + + lchan->meas.flags |= LC_UL_M_F_RES_VALID; + lchan->meas.num_ul_meas = 0; + + /* send a signal indicating computation is complete */ + + return 1; +} diff --git a/src/common/msg_utils.c b/src/common/msg_utils.c new file mode 100644 index 0000000..f936c98 --- /dev/null +++ b/src/common/msg_utils.c @@ -0,0 +1,603 @@ +/* (C) 2014 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#define STI_BIT_MASK 16 + +static int check_fom(struct abis_om_hdr *omh, size_t len) +{ + if (omh->length != len) { + LOGP(DL1C, LOGL_ERROR, "Incorrect OM hdr length value %d %zu\n", + omh->length, len); + return -1; + } + + if (len < sizeof(struct abis_om_fom_hdr)) { + LOGP(DL1C, LOGL_ERROR, "FOM header insufficient space %zu %zu\n", + len, sizeof(struct abis_om_fom_hdr)); + return -1; + } + + return 0; +} + +static int check_manuf(struct msgb *msg, struct abis_om_hdr *omh, size_t msg_size) +{ + int type; + size_t size; + + if (msg_size < 1) { + LOGP(DL1C, LOGL_ERROR, "No ManId Length Indicator %zu\n", + msg_size); + return -1; + } + + if (omh->data[0] >= msg_size - 1) { + LOGP(DL1C, LOGL_ERROR, + "Insufficient message space for this ManId Length %d %zu\n", + omh->data[0], msg_size - 1); + return -1; + } + + if (omh->data[0] == sizeof(abis_nm_ipa_magic) && + strncmp(abis_nm_ipa_magic, (const char *)omh->data + 1, + sizeof(abis_nm_ipa_magic)) == 0) { + type = OML_MSG_TYPE_IPA; + size = sizeof(abis_nm_ipa_magic) + 1; + } else if (omh->data[0] == sizeof(abis_nm_osmo_magic) && + strncmp(abis_nm_osmo_magic, (const char *) omh->data + 1, + sizeof(abis_nm_osmo_magic)) == 0) { + type = OML_MSG_TYPE_OSMO; + size = sizeof(abis_nm_osmo_magic) + 1; + } else { + LOGP(DL1C, LOGL_ERROR, "Manuf Label Unknown\n"); + return -1; + } + + /* we have verified that the vendor string fits */ + msg->l3h = omh->data + size; + if (check_fom(omh, msgb_l3len(msg)) != 0) + return -1; + return type; +} + +/* check that DTX is in the middle of silence */ +static inline bool dtx_is_update(const struct gsm_lchan *lchan) +{ + if (!dtx_dl_amr_enabled(lchan)) + return false; + if (lchan->tch.dtx.dl_amr_fsm->state == ST_SID_U || + lchan->tch.dtx.dl_amr_fsm->state == ST_U_NOINH) + return true; + return false; +} + +/* check that DTX is in the beginning of silence for AMR HR */ +bool dtx_is_first_p1(const struct gsm_lchan *lchan) +{ + if (!dtx_dl_amr_enabled(lchan)) + return false; + if ((lchan->type == GSM_LCHAN_TCH_H && + lchan->tch.dtx.dl_amr_fsm->state == ST_SID_F1)) + return true; + return false; +} + +/* update lchan SID status */ +void lchan_set_marker(bool t, struct gsm_lchan *lchan) +{ + if (t) + lchan->tch.dtx.ul_sid = true; + else if (lchan->tch.dtx.ul_sid) { + lchan->tch.dtx.ul_sid = false; + lchan->rtp_tx_marker = true; + } +} + +/*! \brief Store the last SID frame in lchan context + * \param[in] lchan Logical channel on which we check scheduling + * \param[in] l1_payload buffer with SID data + * \param[in] length length of l1_payload + * \param[in] fn Frame Number for which we check scheduling + * \param[in] update 0 if SID_FIRST, 1 if SID_UPDATE, -1 if not AMR SID + */ +void dtx_cache_payload(struct gsm_lchan *lchan, const uint8_t *l1_payload, + size_t length, uint32_t fn, int update) +{ + size_t amr = (update < 0) ? 0 : 2, + copy_len = OSMO_MIN(length, + ARRAY_SIZE(lchan->tch.dtx.cache) - amr); + + lchan->tch.dtx.len = copy_len + amr; + /* SID FIRST is special because it's both sent and cached: */ + if (update == 0) { + lchan->tch.dtx.is_update = false; /* Mark SID FIRST explicitly */ + /* for non-AMR case - always update FN for incoming SID FIRST */ + if (!amr || !dtx_is_update(lchan)) + lchan->tch.dtx.fn = fn; + /* for AMR case - do not update FN if SID FIRST arrives in a + middle of silence: this should not be happening according to + the spec */ + } + + memcpy(lchan->tch.dtx.cache + amr, l1_payload, copy_len); +} + +/*! \brief Check current state of DTX DL AMR FSM and dispatch necessary events + * \param[in] lchan Logical channel on which we check scheduling + * \param[in] rtp_pl buffer with RTP data + * \param[in] rtp_pl_len length of rtp_pl + * \param[in] fn Frame Number for which we check scheduling + * \param[in] l1_payload buffer where CMR and CMI prefix should be added + * \param[in] marker RTP Marker bit + * \param[out] len Length of expected L1 payload + * \param[out] ft_out Frame Type to be populated after decoding + * \returns 0 in case of success; negative on error + */ +int dtx_dl_amr_fsm_step(struct gsm_lchan *lchan, const uint8_t *rtp_pl, + size_t rtp_pl_len, uint32_t fn, uint8_t *l1_payload, + bool marker, uint8_t *len, uint8_t *ft_out) +{ + uint8_t cmr; + enum osmo_amr_type ft; + enum osmo_amr_quality bfi; + int8_t sti, cmi; + int rc; + + if (dtx_dl_amr_enabled(lchan)) { + if (lchan->type == GSM_LCHAN_TCH_H && !rtp_pl) { + /* we're called by gen_empty_tch_msg() to handle states + specific to AMR HR DTX */ + switch (lchan->tch.dtx.dl_amr_fsm->state) { + case ST_SID_F2: + *len = 3; /* SID-FIRST P1 -> P2 completion */ + memcpy(l1_payload, lchan->tch.dtx.cache, 2); + rc = 0; + dtx_dispatch(lchan, E_COMPL); + break; + case ST_SID_U: + rc = -EBADMSG; + dtx_dispatch(lchan, E_SID_U); + break; + default: + rc = -EBADMSG; + } + return rc; + } + } + + if (!rtp_pl_len) + return -EBADMSG; + + rc = osmo_amr_rtp_dec(rtp_pl, rtp_pl_len, &cmr, &cmi, &ft, &bfi, &sti); + if (rc < 0) { + LOGP(DRTP, LOGL_ERROR, "failed to decode AMR RTP (length %zu, " + "%p)\n", rtp_pl_len, rtp_pl); + return rc; + } + + /* only needed for old sysmo firmware: */ + *ft_out = ft; + + /* CMI in downlink tells the L1 encoder which encoding function + * it will use, so we have to use the frame type */ + if (osmo_amr_is_speech(ft)) + cmi = ft; + + /* populate L1 payload with CMR/CMI - might be ignored by caller: */ + amr_set_mode_pref(l1_payload, &lchan->tch.amr_mr, cmi, cmr); + + /* populate DTX cache with CMR/CMI - overwrite cache which will be + either updated or invalidated by caller anyway: */ + amr_set_mode_pref(lchan->tch.dtx.cache, &lchan->tch.amr_mr, cmi, cmr); + *len = 3 + rtp_pl_len; + + /* DTX DL is not enabled, move along */ + if (!lchan->ts->trx->bts->dtxd) + return 0; + + if (osmo_amr_is_speech(ft)) { + /* AMR HR - SID-FIRST_P1 Inhibition */ + if (marker && lchan->tch.dtx.dl_amr_fsm->state == ST_VOICE) + return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm, + E_INHIB, (void *)lchan); + + /* AMR HR - SID-UPDATE Inhibition */ + if (marker && lchan->type == GSM_LCHAN_TCH_H && + lchan->tch.dtx.dl_amr_fsm->state == ST_SID_U) + return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm, + E_INHIB, (void *)lchan); + + /* AMR FR & HR - generic */ + if (marker && (lchan->tch.dtx.dl_amr_fsm->state == ST_SID_F1 || + lchan->tch.dtx.dl_amr_fsm->state == ST_SID_F2 || + lchan->tch.dtx.dl_amr_fsm->state == ST_U_NOINH)) + return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm, + E_ONSET, (void *)lchan); + + if (lchan->tch.dtx.dl_amr_fsm->state != ST_VOICE) + return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm, + E_VOICE, (void *)lchan); + + return 0; + } + + if (ft == AMR_SID) { + if (lchan->tch.dtx.dl_amr_fsm->state == ST_VOICE) { + /* SID FIRST/UPDATE scheduling logic relies on SID FIRST + being sent first hence we have to force caching of SID + as FIRST regardless of actually decoded type */ + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, false); + return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm, + sti ? E_SID_U : E_SID_F, + (void *)lchan); + } else if (lchan->tch.dtx.dl_amr_fsm->state != ST_FACCH) + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, sti); + if (lchan->tch.dtx.dl_amr_fsm->state == ST_SID_F2) + return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm, + E_COMPL, (void *)lchan); + return osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm, + sti ? E_SID_U : E_SID_F, + (void *)lchan); + } + + if (ft != AMR_NO_DATA) { + LOGP(DRTP, LOGL_ERROR, "unsupported AMR FT 0x%02x\n", ft); + return -ENOTSUP; + } + + if (marker) + osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm, E_VOICE, + (void *)lchan); + *len = 0; + return 0; +} + +/* STI is located in payload byte 6, cache contains 2 byte prefix (CMR/CMI) + * STI set = SID UPDATE, STI unset = SID FIRST + */ +static inline void dtx_sti_set(struct gsm_lchan *lchan) +{ + lchan->tch.dtx.cache[6 + 2] |= STI_BIT_MASK; +} + +static inline void dtx_sti_unset(struct gsm_lchan *lchan) +{ + lchan->tch.dtx.cache[6 + 2] &= ~STI_BIT_MASK; +} + +/*! \brief Check if enough time has passed since last SID (if any) to repeat it + * \param[in] lchan Logical channel on which we check scheduling + * \param[in] fn Frame Number for which we check scheduling + * \returns true if transmission can be omitted, false otherwise + */ +static inline bool dtx_amr_sid_optional(struct gsm_lchan *lchan, uint32_t fn) +{ + if (!dtx_dl_amr_enabled(lchan)) + return true; + + /* Compute approx. time delta x26 based on Fn duration */ + uint32_t dx26 = 120 * (fn - lchan->tch.dtx.fn); + + /* We're resuming after FACCH interruption */ + if (lchan->tch.dtx.dl_amr_fsm->state == ST_FACCH) { + /* force STI bit to 0 so cache is treated as SID FIRST */ + dtx_sti_unset(lchan); + lchan->tch.dtx.is_update = false; + /* check that this FN has not been used for FACCH message + already: we rely here on the order of RTS arrival from L1 - we + expect that PH-DATA.req ALWAYS comes before PH-TCH.req for the + same FN */ + if(lchan->type == GSM_LCHAN_TCH_H) { + if (lchan->tch.dtx.fn != LCHAN_FN_DUMMY && + lchan->tch.dtx.fn != LCHAN_FN_WAIT) { + /* FACCH interruption is over */ + dtx_dispatch(lchan, E_COMPL); + return false; + } else if(lchan->tch.dtx.fn == LCHAN_FN_DUMMY) { + lchan->tch.dtx.fn = LCHAN_FN_WAIT; + } else + lchan->tch.dtx.fn = fn; + } else if(lchan->type == GSM_LCHAN_TCH_F) { + if (lchan->tch.dtx.fn != LCHAN_FN_DUMMY) { + /* FACCH interruption is over */ + dtx_dispatch(lchan, E_COMPL); + return false; + } else + lchan->tch.dtx.fn = fn; + } + /* this FN was already used for FACCH or ONSET message so we just + prepare things for next one */ + return true; + } + + if (lchan->tch.dtx.dl_amr_fsm->state == ST_VOICE) + return true; + + /* according to 3GPP TS 26.093 A.5.1.1: + (*26) to avoid float math, add 1 FN tolerance (-120) */ + if (lchan->tch.dtx.is_update) { /* SID UPDATE: every 8th RTP frame */ + if (dx26 < GSM_RTP_FRAME_DURATION_MS * 8 * 26 - 120) + return true; + return false; + } + /* 3rd frame after SID FIRST should be SID UPDATE */ + if (dx26 < GSM_RTP_FRAME_DURATION_MS * 3 * 26 - 120) + return true; + return false; +} + +static inline bool fn_chk(const uint8_t *t, uint32_t fn, uint8_t len) +{ + uint8_t i; + for (i = 0; i < len; i++) + if (fn % 104 == t[i]) + return false; + return true; +} + +/*! \brief Check if TX scheduling is optional for a given FN in case of DTX + * \param[in] lchan Logical channel on which we check scheduling + * \param[in] fn Frame Number for which we check scheduling + * \returns true if transmission can be omitted, false otherwise + */ +static inline bool dtx_sched_optional(struct gsm_lchan *lchan, uint32_t fn) +{ + /* According to 3GPP TS 45.008 § 8.3: */ + static const uint8_t f[] = { 52, 53, 54, 55, 56, 57, 58, 59 }, + h0[] = { 0, 2, 4, 6, 52, 54, 56, 58 }, + h1[] = { 14, 16, 18, 20, 66, 68, 70, 72 }; + if (lchan->tch_mode == GSM48_CMODE_SPEECH_V1) { + if (lchan->type == GSM_LCHAN_TCH_F) + return fn_chk(f, fn, ARRAY_SIZE(f)); + else + return fn_chk(lchan->nr ? h1 : h0, fn, + lchan->nr ? ARRAY_SIZE(h1) : + ARRAY_SIZE(h0)); + } + return false; +} + +/*! \brief Check if DTX DL AMR is enabled for a given lchan (it have proper type, + * FSM is allocated etc.) + * \param[in] lchan Logical channel on which we check scheduling + * \returns true if DTX DL AMR is enabled, false otherwise + */ +bool dtx_dl_amr_enabled(const struct gsm_lchan *lchan) +{ + if (lchan->ts->trx->bts->dtxd && + lchan->tch.dtx.dl_amr_fsm && + lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) + return true; + return false; +} + +/*! \brief Check if DTX DL AMR FSM state is recursive: requires secondary + * response to a single RTS request from L1. + * \param[in] lchan Logical channel on which we check scheduling + * \returns true if DTX DL AMR FSM state is recursive, false otherwise + */ +bool dtx_recursion(const struct gsm_lchan *lchan) +{ + if (!dtx_dl_amr_enabled(lchan)) + return false; + + if (lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_V || + lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F || + lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_V_REC || + lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F_REC || + lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_V || + lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F || + lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_V_REC || + lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F_REC || + lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F || + lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_V || + lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F_REC || + lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_V_REC) + return true; + + return false; +} + +/*! \brief Send signal to FSM: with proper check if DIX is enabled for this lchan + * \param[in] lchan Logical channel on which we check scheduling + * \param[in] e DTX DL AMR FSM Event + */ +void dtx_dispatch(struct gsm_lchan *lchan, enum dtx_dl_amr_fsm_events e) +{ + if (dtx_dl_amr_enabled(lchan)) + osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm, e, + (void *)lchan); +} + +/*! \brief Send internal signal to FSM: check that DTX is enabled for this chan, + * check that current FSM and lchan states are permitting such signal. + * Note: this should be the only way to dispatch E_COMPL to FSM from + * BTS code. + * \param[in] lchan Logical channel on which we check scheduling + */ +void dtx_int_signal(struct gsm_lchan *lchan) +{ + if (!dtx_dl_amr_enabled(lchan)) + return; + + if (dtx_is_first_p1(lchan) || dtx_recursion(lchan)) + dtx_dispatch(lchan, E_COMPL); +} + +/*! \brief Repeat last SID if possible in case of DTX + * \param[in] lchan Logical channel on which we check scheduling + * \param[in] dst Buffer to copy last SID into + * \returns Number of bytes copied + 1 (to accommodate for extra byte with + * payload type), 0 if there's nothing to copy + */ +uint8_t repeat_last_sid(struct gsm_lchan *lchan, uint8_t *dst, uint32_t fn) +{ + /* FIXME: add EFR support */ + if (lchan->tch_mode == GSM48_CMODE_SPEECH_EFR) + return 0; + + if (lchan->tch_mode != GSM48_CMODE_SPEECH_AMR) { + if (dtx_sched_optional(lchan, fn)) + return 0; + } else + if (dtx_amr_sid_optional(lchan, fn)) + return 0; + + if (lchan->tch.dtx.len) { + if (dtx_dl_amr_enabled(lchan)) { + if ((lchan->type == GSM_LCHAN_TCH_H && + lchan->tch.dtx.dl_amr_fsm->state == ST_SID_F2) || + (lchan->type == GSM_LCHAN_TCH_F && + lchan->tch.dtx.dl_amr_fsm->state == ST_SID_F1)) { + /* advance FSM in case we've just sent SID FIRST + to restore silence after FACCH interruption */ + osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm, + E_SID_U, (void *)lchan); + dtx_sti_unset(lchan); + } else if (dtx_is_update(lchan)) { + /* enforce SID UPDATE for next repetition: it + might have been altered by FACCH handling */ + dtx_sti_set(lchan); + if (lchan->type == GSM_LCHAN_TCH_H && + lchan->tch.dtx.dl_amr_fsm->state == + ST_U_NOINH) + osmo_fsm_inst_dispatch(lchan->tch.dtx.dl_amr_fsm, + E_COMPL, + (void *)lchan); + lchan->tch.dtx.is_update = true; + } + } + memcpy(dst, lchan->tch.dtx.cache, lchan->tch.dtx.len); + lchan->tch.dtx.fn = fn; + return lchan->tch.dtx.len + 1; + } + + LOGP(DL1C, LOGL_DEBUG, "Have to send %s frame on TCH but SID buffer " + "is empty - sent nothing\n", + get_value_string(gsm48_chan_mode_names, lchan->tch_mode)); + + return 0; +} + +/** + * Return 0 in case the IPA structure is okay and in this + * case the l2h will be set to the beginning of the data. + */ +int msg_verify_ipa_structure(struct msgb *msg) +{ + struct ipaccess_head *hh; + + if (msgb_l1len(msg) < sizeof(struct ipaccess_head)) { + LOGP(DL1C, LOGL_ERROR, + "Ipa header insufficient space %d %zu\n", + msgb_l1len(msg), sizeof(struct ipaccess_head)); + return -1; + } + + hh = (struct ipaccess_head *) msg->l1h; + + if (ntohs(hh->len) != msgb_l1len(msg) - sizeof(struct ipaccess_head)) { + LOGP(DL1C, LOGL_ERROR, + "Incorrect ipa header msg size %d %zu\n", + ntohs(hh->len), msgb_l1len(msg) - sizeof(struct ipaccess_head)); + return -1; + } + + if (hh->proto == IPAC_PROTO_OSMO) { + struct ipaccess_head_ext *hh_ext = (struct ipaccess_head_ext *) hh->data; + if (ntohs(hh->len) < sizeof(*hh_ext)) { + LOGP(DL1C, LOGL_ERROR, "IPA length shorter than OSMO header\n"); + return -1; + } + msg->l2h = hh_ext->data; + } else + msg->l2h = hh->data; + + return 0; +} + +/** + * \brief Verify the structure of the OML message and set l3h + * + * This function verifies that the data in \param in msg is a proper + * OML message. This code assumes that msg->l2h points to the + * beginning of the OML message. In the successful case the msg->l3h + * will be set and will point to the FOM header. The value is undefined + * in all other cases. + * + * \param msg The message to analyze starting from msg->l2h. + * \return In case the structure is correct a positive number will be + * returned and msg->l3h will point to the FOM. The number is a + * classification of the vendor type of the message. + */ +int msg_verify_oml_structure(struct msgb *msg) +{ + struct abis_om_hdr *omh; + + if (msgb_l2len(msg) < sizeof(*omh)) { + LOGP(DL1C, LOGL_ERROR, "Om header insufficient space %d %zu\n", + msgb_l2len(msg), sizeof(*omh)); + return -1; + } + + omh = (struct abis_om_hdr *) msg->l2h; + if (omh->mdisc != ABIS_OM_MDISC_FOM && + omh->mdisc != ABIS_OM_MDISC_MANUF) { + LOGP(DL1C, LOGL_ERROR, "Incorrect om mdisc value %x\n", + omh->mdisc); + return -1; + } + + if (omh->placement != ABIS_OM_PLACEMENT_ONLY) { + LOGP(DL1C, LOGL_ERROR, "Incorrect om placement value %x %x\n", + omh->placement, ABIS_OM_PLACEMENT_ONLY); + return -1; + } + + if (omh->sequence != 0) { + LOGP(DL1C, LOGL_ERROR, "Incorrect om sequence value %d\n", + omh->sequence); + return -1; + } + + if (omh->mdisc == ABIS_OM_MDISC_MANUF) + return check_manuf(msg, omh, msgb_l2len(msg) - sizeof(*omh)); + + msg->l3h = omh->data; + if (check_fom(omh, msgb_l3len(msg)) != 0) + return -1; + return OML_MSG_TYPE_ETSI; +} diff --git a/src/common/oml.c b/src/common/oml.c new file mode 100644 index 0000000..41debc1 --- /dev/null +++ b/src/common/oml.c @@ -0,0 +1,1495 @@ +/* GSM TS 12.21 O&M / OML, BTS side */ + +/* (C) 2011 by Andreas Eversberg + * (C) 2011-2013 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU 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 . + * + */ + +/* + * Operation and Maintenance Messages + */ + +#include "btsconfig.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static int oml_ipa_set_attr(struct gsm_bts *bts, struct msgb *msg); + +static struct tlv_definition abis_nm_att_tlvdef_ipa_local = {}; + +/* + * support + */ + +static int oml_tlv_parse(struct tlv_parsed *tp, const uint8_t *buf, int len) +{ + return tlv_parse(tp, &abis_nm_att_tlvdef_ipa_local, buf, len, 0, 0); +} + +struct msgb *oml_msgb_alloc(void) +{ + return msgb_alloc_headroom(1024, 128, "OML"); +} + +/* 3GPP TS 12.21 § 8.8.2 */ +static int oml_tx_failure_event_rep(struct gsm_abis_mo *mo, uint16_t cause_value, + const char *fmt, ...) +{ + struct msgb *nmsg; + va_list ap; + + LOGP(DOML, LOGL_NOTICE, "Sending %s to BSC: ", get_value_string(abis_mm_event_cause_names, cause_value)); + va_start(ap, fmt); + osmo_vlogp(DOML, LOGL_NOTICE, __FILE__, __LINE__, 1, fmt, ap); + nmsg = abis_nm_fail_evt_vrep(NM_EVT_PROC_FAIL, NM_SEVER_CRITICAL, + NM_PCAUSE_T_MANUF, cause_value, fmt, ap); + va_end(ap); + LOGPC(DOML, LOGL_NOTICE, "\n"); + + if (!nmsg) + return -ENOMEM; + + return oml_mo_send_msg(mo, nmsg, NM_MT_FAILURE_EVENT_REP); +} + +void oml_fail_rep(uint16_t cause_value, const char *fmt, ...) +{ + va_list ap; + char *rep; + + va_start(ap, fmt); + rep = talloc_asprintf(tall_bts_ctx, fmt, ap); + va_end(ap); + + osmo_signal_dispatch(SS_FAIL, cause_value, rep); + /* signal dispatch is synchronous so all the signal handlers are + finished already: we're free to free */ + talloc_free(rep); +} + +int oml_send_msg(struct msgb *msg, int is_manuf) +{ + struct abis_om_hdr *omh; + + if (is_manuf) { + /* length byte, string + 0 termination */ + uint8_t *manuf = msgb_push(msg, 1 + sizeof(abis_nm_ipa_magic)); + manuf[0] = strlen(abis_nm_ipa_magic)+1; + memcpy(manuf+1, abis_nm_ipa_magic, strlen(abis_nm_ipa_magic)); + } + + /* Push the main OML header and send it off */ + omh = (struct abis_om_hdr *) msgb_push(msg, sizeof(*omh)); + if (is_manuf) + omh->mdisc = ABIS_OM_MDISC_MANUF; + else + omh->mdisc = ABIS_OM_MDISC_FOM; + omh->placement = ABIS_OM_PLACEMENT_ONLY; + omh->sequence = 0; + omh->length = msgb_l3len(msg); + + msg->l2h = (uint8_t *)omh; + + return abis_oml_sendmsg(msg); +} + +int oml_mo_send_msg(struct gsm_abis_mo *mo, struct msgb *msg, uint8_t msg_type) +{ + struct abis_om_fom_hdr *foh; + + msg->l3h = msgb_push(msg, sizeof(*foh)); + foh = (struct abis_om_fom_hdr *) msg->l3h; + foh->msg_type = msg_type; + foh->obj_class = mo->obj_class; + memcpy(&foh->obj_inst, &mo->obj_inst, sizeof(foh->obj_inst)); + + /* FIXME: This assumption may not always be correct */ + msg->trx = mo->bts->c0; + + return oml_send_msg(msg, 0); +} + +/* FIXME: move to gsm_data_shared */ +static char mo_buf[128]; +char *gsm_abis_mo_name(const struct gsm_abis_mo *mo) +{ + snprintf(mo_buf, sizeof(mo_buf), "OC=%s INST=(%02x,%02x,%02x)", + get_value_string(abis_nm_obj_class_names, mo->obj_class), + mo->obj_inst.bts_nr, mo->obj_inst.trx_nr, mo->obj_inst.ts_nr); + return mo_buf; +} + +static inline void add_bts_attrs(struct msgb *msg, const struct gsm_bts *bts) +{ + abis_nm_put_sw_file(msg, "osmobts", PACKAGE_VERSION, true); + abis_nm_put_sw_file(msg, btsatttr2str(BTS_TYPE_VARIANT), btsvariant2str(bts->variant), true); + + if (strlen(bts->sub_model)) + abis_nm_put_sw_file(msg, btsatttr2str(BTS_SUB_MODEL), bts->sub_model, true); +} + +/* Add BTS features as 3GPP TS 52.021 §9.4.30 Manufacturer Id */ +static inline void add_bts_feat(struct msgb *msg, const struct gsm_bts *bts) +{ + msgb_tl16v_put(msg, NM_ATT_MANUF_ID, _NUM_BTS_FEAT/8 + 1, bts->_features_data); +} + +static inline void add_trx_attr(struct msgb *msg, struct gsm_bts_trx *trx) +{ + const struct phy_instance *pinst = trx_phy_instance(trx); + + abis_nm_put_sw_file(msg, btsatttr2str(TRX_PHY_VERSION), pinst && strlen(pinst->version) ? pinst->version : "Unknown", + true); +} + +/* The number of attributes in §9.4.26 List of Required Attributes is 2 bytes, + but the Count of not-reported attributes from §9.4.64 is 1 byte */ +static inline uint8_t pack_num_unreported_attr(uint16_t attrs) +{ + if (attrs > 255) { + LOGP(DOML, LOGL_ERROR, "O&M Get Attributes, Count of not-reported attributes is too big: %u\n", + attrs); + return 255; + } + return attrs; /* Return number of unhandled attributes */ +} + +/* copy all the attributes accumulated in msg to out and return the total length of out buffer */ +static inline int cleanup_attr_msg(uint8_t *out, int out_offset, struct msgb *msg) +{ + int len = 0; + + out[0] = pack_num_unreported_attr(out_offset - 1); + + if (msg) { + memcpy(out + out_offset, msgb_data(msg), msg->len); + len = msg->len; + msgb_free(msg); + } + + return len + out_offset + 1; +} + +static inline int handle_attrs_trx(uint8_t *out, struct gsm_bts_trx *trx, const uint8_t *attr, uint16_t attr_len) +{ + uint16_t i, attr_out_index = 1; /* byte 0 is reserved for unsupported attributes counter */ + struct msgb *attr_buf = oml_msgb_alloc(); + + if (!attr_buf) + return -NM_NACK_CANT_PERFORM; + + for (i = 0; i < attr_len; i++) { + bool processed = false; + switch (attr[i]) { + case NM_ATT_SW_CONFIG: + if (trx) { + add_trx_attr(attr_buf, trx); + processed = true; + } else + LOGP(DOML, LOGL_ERROR, "O&M Get Attributes [%u], %s is unhandled due to missing TRX.\n", + i, get_value_string(abis_nm_att_names, attr[i])); + break; + default: + LOGP(DOML, LOGL_ERROR, "O&M Get Attributes [%u], %s is unsupported by TRX.\n", i, + get_value_string(abis_nm_att_names, attr[i])); + } + /* assemble values of supported attributes and list of unsupported ones */ + if (!processed) { + out[attr_out_index] = attr[i]; + attr_out_index++; + } + } + + return cleanup_attr_msg(out, attr_out_index, attr_buf); +} + +static inline int handle_attrs_bts(uint8_t *out, const struct gsm_bts *bts, const uint8_t *attr, uint16_t attr_len) +{ + uint16_t i, attr_out_index = 1; /* byte 0 is reserved for unsupported attributes counter */ + struct msgb *attr_buf = oml_msgb_alloc(); + + if (!attr_buf) + return -NM_NACK_CANT_PERFORM; + + for (i = 0; i < attr_len; i++) { + switch (attr[i]) { + case NM_ATT_SW_CONFIG: + add_bts_attrs(attr_buf, bts); + break; + case NM_ATT_MANUF_ID: + add_bts_feat(attr_buf, bts); + break; + default: + LOGP(DOML, LOGL_ERROR, "O&M Get Attributes [%u], %s is unsupported by BTS.\n", i, + get_value_string(abis_nm_att_names, attr[i])); + out[attr_out_index] = attr[i]; /* assemble values of supported attributes and list of unsupported ones */ + attr_out_index++; + } + } + + return cleanup_attr_msg(out, attr_out_index, attr_buf); +} + +/* send 3GPP TS 52.021 §8.11.2 Get Attribute Response */ +static int oml_tx_attr_resp(struct gsm_bts *bts, const struct abis_om_fom_hdr *foh, const uint8_t *attr, + uint16_t attr_len) +{ + struct msgb *nmsg = oml_msgb_alloc(); + uint8_t resp[MAX_VERSION_LENGTH * attr_len * 2]; /* heuristic for Attribute Response Info space requirements */ + int len; + + LOGP(DOML, LOGL_INFO, "%s Tx Get Attribute Response\n", + get_value_string(abis_nm_obj_class_names, foh->obj_class)); + + if (!nmsg) + return -NM_NACK_CANT_PERFORM; + + switch (foh->obj_class) { + case NM_OC_BTS: + len = handle_attrs_bts(resp, bts, attr, attr_len); + break; + case NM_OC_BASEB_TRANSC: + len = handle_attrs_trx(resp, gsm_bts_trx_num(bts, foh->obj_inst.trx_nr), attr, attr_len); + break; + default: + LOGP(DOML, LOGL_ERROR, "Unsupported MO class %s in Get Attribute Response\n", + get_value_string(abis_nm_obj_class_names, foh->obj_class)); + len = -NM_NACK_RES_NOTIMPL; + } + + if (len < 0) { + LOGP(DOML, LOGL_ERROR, "Tx Get Attribute Response FAILED with %d\n", len); + msgb_free(nmsg); + return len; + } + + /* §9.4.64 Get Attribute Response Info */ + msgb_tl16v_put(nmsg, NM_ATT_GET_ARI, len, resp); + + len = oml_mo_send_msg(&bts->mo, nmsg, NM_MT_GET_ATTR_RESP); + return (len < 0) ? -NM_NACK_CANT_PERFORM : len; +} + +/* 8.8.1 sending State Changed Event Report */ +int oml_tx_state_changed(struct gsm_abis_mo *mo) +{ + struct msgb *nmsg; + + LOGP(DOML, LOGL_INFO, "%s Tx STATE CHG REP\n", gsm_abis_mo_name(mo)); + + nmsg = oml_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + + /* 9.4.38 Operational State */ + msgb_tv_put(nmsg, NM_ATT_OPER_STATE, mo->nm_state.operational); + + /* 9.4.7 Availability Status */ + msgb_tl16v_put(nmsg, NM_ATT_AVAIL_STATUS, 1, &mo->nm_state.availability); + + /* 9.4.4 Administrative Status -- not in spec but also sent by nanobts */ + msgb_tv_put(nmsg, NM_ATT_ADM_STATE, mo->nm_state.administrative); + + return oml_mo_send_msg(mo, nmsg, NM_MT_STATECHG_EVENT_REP); +} + +/* First initialization of MO, does _not_ generate state changes */ +void oml_mo_state_init(struct gsm_abis_mo *mo, int op_state, int avail_state) +{ + mo->nm_state.availability = avail_state; + mo->nm_state.operational = op_state; +} + +int oml_mo_state_chg(struct gsm_abis_mo *mo, int op_state, int avail_state) +{ + int rc = 0; + + if ((op_state != -1 && mo->nm_state.operational != op_state) || + (avail_state != -1 && mo->nm_state.availability != avail_state)) { + if (avail_state != -1) { + LOGP(DOML, LOGL_INFO, "%s AVAIL STATE %s -> %s\n", + gsm_abis_mo_name(mo), + abis_nm_avail_name(mo->nm_state.availability), + abis_nm_avail_name(avail_state)); + mo->nm_state.availability = avail_state; + } + if (op_state != -1) { + LOGP(DOML, LOGL_INFO, "%s OPER STATE %s -> %s\n", + gsm_abis_mo_name(mo), + abis_nm_opstate_name(mo->nm_state.operational), + abis_nm_opstate_name(op_state)); + mo->nm_state.operational = op_state; + osmo_signal_dispatch(SS_GLOBAL, S_NEW_OP_STATE, NULL); + } + + /* send state change report */ + rc = oml_tx_state_changed(mo); + } + return rc; +} + +int oml_mo_fom_ack_nack(struct gsm_abis_mo *mo, uint8_t orig_msg_type, + uint8_t cause) +{ + struct msgb *msg; + uint8_t new_msg_type; + + msg = oml_msgb_alloc(); + if (!msg) + return -ENOMEM; + + if (cause) { + new_msg_type = orig_msg_type + 2; + msgb_tv_put(msg, NM_ATT_NACK_CAUSES, cause); + } else { + new_msg_type = orig_msg_type + 1; + } + + return oml_mo_send_msg(mo, msg, new_msg_type); +} + +int oml_mo_statechg_ack(struct gsm_abis_mo *mo) +{ + struct msgb *msg; + int rc = 0; + + msg = oml_msgb_alloc(); + if (!msg) + return -ENOMEM; + + msgb_tv_put(msg, NM_ATT_ADM_STATE, mo->nm_state.administrative); + + rc = oml_mo_send_msg(mo, msg, NM_MT_CHG_ADM_STATE_ACK); + if (rc != 0) + return rc; + + /* Emulate behaviour of ipaccess nanobts: Send a 'State Changed Event Report' as well. */ + return oml_tx_state_changed(mo); +} + +int oml_mo_statechg_nack(struct gsm_abis_mo *mo, uint8_t nack_cause) +{ + return oml_mo_fom_ack_nack(mo, NM_MT_CHG_ADM_STATE, nack_cause); +} + +int oml_mo_opstart_ack(struct gsm_abis_mo *mo) +{ + return oml_mo_fom_ack_nack(mo, NM_MT_OPSTART, 0); +} + +int oml_mo_opstart_nack(struct gsm_abis_mo *mo, uint8_t nack_cause) +{ + return oml_mo_fom_ack_nack(mo, NM_MT_OPSTART, nack_cause); +} + +int oml_fom_ack_nack(struct msgb *old_msg, uint8_t cause) +{ + struct abis_om_hdr *old_oh = msgb_l2(old_msg); + struct abis_om_fom_hdr *old_foh = msgb_l3(old_msg); + struct msgb *msg; + struct abis_om_fom_hdr *foh; + int is_manuf = 0; + + msg = oml_msgb_alloc(); + if (!msg) + return -ENOMEM; + + /* make sure to respond with MANUF if request was MANUF */ + if (old_oh->mdisc == ABIS_OM_MDISC_MANUF) + is_manuf = 1; + + msg->trx = old_msg->trx; + + /* copy over old FOM-Header and later only change the msg_type */ + msg->l3h = msgb_push(msg, sizeof(*foh)); + foh = (struct abis_om_fom_hdr *) msg->l3h; + memcpy(foh, old_foh, sizeof(*foh)); + + /* alter message type */ + if (cause) { + LOGP(DOML, LOGL_NOTICE, "Sending FOM NACK with cause %s.\n", + abis_nm_nack_cause_name(cause)); + foh->msg_type += 2; /* nack */ + /* add cause */ + msgb_tv_put(msg, NM_ATT_NACK_CAUSES, cause); + } else { + LOGP(DOML, LOGL_DEBUG, "Sending FOM ACK.\n"); + foh->msg_type++; /* ack */ + } + + return oml_send_msg(msg, is_manuf); +} + +/* + * Formatted O&M messages + */ + +/* 8.3.7 sending SW Activated Report */ +int oml_mo_tx_sw_act_rep(struct gsm_abis_mo *mo) +{ + struct msgb *nmsg; + + LOGP(DOML, LOGL_INFO, "%s Tx SW ACT REP\n", gsm_abis_mo_name(mo)); + + nmsg = oml_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + + msgb_put(nmsg, sizeof(struct abis_om_fom_hdr)); + return oml_mo_send_msg(mo, nmsg, NM_MT_SW_ACTIVATED_REP); +} + +/* The defaults below correspond to the libosmocore default of 1s for + * DCCH and 2s for ACCH. The BSC should override this via OML anyway. */ +const unsigned int oml_default_t200_ms[7] = { + [T200_SDCCH] = 1000, + [T200_FACCH_F] = 1000, + [T200_FACCH_H] = 1000, + [T200_SACCH_TCH_SAPI0] = 2000, + [T200_SACCH_SDCCH] = 2000, + [T200_SDCCH_SAPI3] = 1000, + [T200_SACCH_TCH_SAPI3] = 2000, +}; + +static void dl_set_t200(struct lapdm_datalink *dl, unsigned int t200_msec) +{ + dl->dl.t200_sec = t200_msec / 1000; + dl->dl.t200_usec = (t200_msec % 1000) * 1000; +} + +/* Configure LAPDm T200 timers for this lchan according to OML */ +int oml_set_lchan_t200(struct gsm_lchan *lchan) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + struct lapdm_channel *lc = &lchan->lapdm_ch; + unsigned int t200_dcch, t200_dcch_sapi3, t200_acch, t200_acch_sapi3; + + /* set T200 for main and associated channel */ + switch (lchan->type) { + case GSM_LCHAN_SDCCH: + t200_dcch = bts->t200_ms[T200_SDCCH]; + t200_dcch_sapi3 = bts->t200_ms[T200_SDCCH_SAPI3]; + t200_acch = bts->t200_ms[T200_SACCH_SDCCH]; + t200_acch_sapi3 = bts->t200_ms[T200_SACCH_SDCCH]; + break; + case GSM_LCHAN_TCH_F: + t200_dcch = bts->t200_ms[T200_FACCH_F]; + t200_dcch_sapi3 = bts->t200_ms[T200_FACCH_F]; + t200_acch = bts->t200_ms[T200_SACCH_TCH_SAPI0]; + t200_acch_sapi3 = bts->t200_ms[T200_SACCH_TCH_SAPI3]; + break; + case GSM_LCHAN_TCH_H: + t200_dcch = bts->t200_ms[T200_FACCH_H]; + t200_dcch_sapi3 = bts->t200_ms[T200_FACCH_H]; + t200_acch = bts->t200_ms[T200_SACCH_TCH_SAPI0]; + t200_acch_sapi3 = bts->t200_ms[T200_SACCH_TCH_SAPI3]; + break; + default: + return -1; + } + + DEBUGP(DLLAPD, "%s: Setting T200 D0=%u, D3=%u, S0=%u, S3=%u" + "(all in ms)\n", gsm_lchan_name(lchan), t200_dcch, + t200_dcch_sapi3, t200_acch, t200_acch_sapi3); + + dl_set_t200(&lc->lapdm_dcch.datalink[DL_SAPI0], t200_dcch); + dl_set_t200(&lc->lapdm_dcch.datalink[DL_SAPI3], t200_dcch_sapi3); + dl_set_t200(&lc->lapdm_acch.datalink[DL_SAPI0], t200_acch); + dl_set_t200(&lc->lapdm_acch.datalink[DL_SAPI3], t200_acch_sapi3); + + return 0; +} + +/* 3GPP TS 52.021 §8.11.1 Get Attributes has been received */ +static int oml_rx_get_attr(struct gsm_bts *bts, struct msgb *msg) +{ + struct abis_om_fom_hdr *foh = msgb_l3(msg); + struct tlv_parsed tp; + int rc; + + if (!foh || !bts) + return -EINVAL; + + abis_nm_debugp_foh(DOML, foh); + DEBUGPC(DOML, "Rx GET ATTR\n"); + + rc = oml_tlv_parse(&tp, foh->data, msgb_l3len(msg) - sizeof(*foh)); + if (rc < 0) { + oml_tx_failure_event_rep(&bts->mo, OSMO_EVT_MAJ_UNSUP_ATTR, "Get Attribute parsing failure"); + return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT); + } + + if (!TLVP_PRES_LEN(&tp, NM_ATT_LIST_REQ_ATTR, 1)) { + LOGP(DOML, LOGL_ERROR, "O&M Get Attributes message without Attribute List?!\n"); + oml_tx_failure_event_rep(&bts->mo, OSMO_EVT_MAJ_UNSUP_ATTR, "Get Attribute without Attribute List"); + return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT); + } + + rc = oml_tx_attr_resp(bts, foh, TLVP_VAL(&tp, NM_ATT_LIST_REQ_ATTR), TLVP_LEN(&tp, NM_ATT_LIST_REQ_ATTR)); + if (rc < 0) { + LOGP(DOML, LOGL_ERROR, "responding to O&M Get Attributes message with NACK 0%x\n", -rc); + return oml_fom_ack_nack(msg, -rc); + } + + return 0; +} + +/* 8.6.1 Set BTS Attributes has been received */ +static int oml_rx_set_bts_attr(struct gsm_bts *bts, struct msgb *msg) +{ + struct abis_om_fom_hdr *foh = msgb_l3(msg); + struct tlv_parsed tp, *tp_merged; + int rc, i; + const uint8_t *payload; + + abis_nm_debugp_foh(DOML, foh); + DEBUGPC(DOML, "Rx SET BTS ATTR\n"); + + rc = oml_tlv_parse(&tp, foh->data, msgb_l3len(msg) - sizeof(*foh)); + if (rc < 0) { + oml_tx_failure_event_rep(&bts->mo, OSMO_EVT_MAJ_UNSUP_ATTR, + "New value for Attribute not supported"); + return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT); + } + + /* Test for globally unsupported stuff here */ + if (TLVP_PRES_LEN(&tp, NM_ATT_BCCH_ARFCN, 2)) { + uint16_t arfcn = ntohs(tlvp_val16_unal(&tp, NM_ATT_BCCH_ARFCN)); + if (arfcn > 1024) { + oml_tx_failure_event_rep(&bts->mo, OSMO_EVT_WARN_SW_WARN, + "Given ARFCN %u is not supported", + arfcn); + LOGP(DOML, LOGL_NOTICE, "Given ARFCN %d is not supported.\n", arfcn); + return oml_fom_ack_nack(msg, NM_NACK_FREQ_NOTAVAIL); + } + } + /* 9.4.52 Starting Time */ + if (TLVP_PRESENT(&tp, NM_ATT_START_TIME)) { + oml_tx_failure_event_rep(&bts->mo, OSMO_EVT_MAJ_UNSUP_ATTR, + "NM_ATT_START_TIME Attribute not " + "supported"); + return oml_fom_ack_nack(msg, NM_NACK_SPEC_IMPL_NOTSUPP); + } + + /* merge existing BTS attributes with new attributes */ + tp_merged = osmo_tlvp_copy(bts->mo.nm_attr, bts); + osmo_tlvp_merge(tp_merged, &tp); + + /* Ask BTS driver to validate new merged attributes */ + rc = bts_model_check_oml(bts, foh->msg_type, bts->mo.nm_attr, tp_merged, bts); + if (rc < 0) { + talloc_free(tp_merged); + return oml_fom_ack_nack(msg, -rc); + } + + /* Success: replace old BTS attributes with new */ + talloc_free(bts->mo.nm_attr); + bts->mo.nm_attr = tp_merged; + + /* ... and actually still parse them */ + + /* 9.4.25 Interference Level Boundaries */ + if (TLVP_PRES_LEN(&tp, NM_ATT_INTERF_BOUND, 6)) { + payload = TLVP_VAL(&tp, NM_ATT_INTERF_BOUND); + for (i = 0; i < 6; i++) { + int16_t boundary = *payload; + bts->interference.boundary[i] = -1 * boundary; + } + } + /* 9.4.24 Intave Parameter */ + if (TLVP_PRES_LEN(&tp, NM_ATT_INTAVE_PARAM, 1)) + bts->interference.intave = *TLVP_VAL(&tp, NM_ATT_INTAVE_PARAM); + + /* 9.4.14 Connection Failure Criterion */ + if (TLVP_PRES_LEN(&tp, NM_ATT_CONN_FAIL_CRIT, 1)) { + const uint8_t *val = TLVP_VAL(&tp, NM_ATT_CONN_FAIL_CRIT); + + switch (val[0]) { + case 0xFF: /* Osmocom specific Extension of TS 12.21 */ + LOGP(DOML, LOGL_NOTICE, "WARNING: Radio Link Timeout " + "explicitly disabled, only use this for lab testing!\n"); + bts->radio_link_timeout = -1; + break; + case 0x01: /* Based on uplink SACCH (radio link timeout) */ + if (TLVP_LEN(&tp, NM_ATT_CONN_FAIL_CRIT) >= 2 && + val[1] >= 4 && val[1] <= 64) { + bts->radio_link_timeout = val[1]; + break; + } + /* fall-through */ + case 0x02: /* Based on RXLEV/RXQUAL measurements */ + default: + LOGP(DOML, LOGL_NOTICE, "Given Conn. Failure Criterion " + "not supported. Please use criterion 0x01 with " + "RADIO_LINK_TIMEOUT value of 4..64\n"); + return oml_fom_ack_nack(msg, NM_NACK_PARAM_RANGE); + } + } + + /* 9.4.53 T200 */ + if (TLVP_PRES_LEN(&tp, NM_ATT_T200, ARRAY_SIZE(bts->t200_ms))) { + payload = TLVP_VAL(&tp, NM_ATT_T200); + for (i = 0; i < ARRAY_SIZE(bts->t200_ms); i++) { + uint32_t t200_ms = payload[i] * abis_nm_t200_ms[i]; +#if 0 + bts->t200_ms[i] = t200_ms; + DEBUGP(DOML, "T200[%u]: OML=%u, mult=%u => %u ms\n", + i, payload[i], abis_nm_t200_mult[i], + bts->t200_ms[i]); +#else + /* we'd rather use the 1s/2s (long) defaults by + * libosmocore, as we appear to have some bug(s) + * related to handling T200 expiration in + * libosmogsm lapd(m) code? */ + LOGP(DOML, LOGL_NOTICE, "Ignoring T200[%u] (%u ms) " + "as sent by BSC due to suspected LAPDm bug!\n", + i, t200_ms); +#endif + } + } + + /* 9.4.31 Maximum Timing Advance */ + if (TLVP_PRES_LEN(&tp, NM_ATT_MAX_TA, 1)) + bts->max_ta = *TLVP_VAL(&tp, NM_ATT_MAX_TA); + + /* 9.4.39 Overload Period */ + if (TLVP_PRES_LEN(&tp, NM_ATT_OVERL_PERIOD, 1)) + bts->load.overload_period = *TLVP_VAL(&tp, NM_ATT_OVERL_PERIOD); + + /* 9.4.12 CCCH Load Threshold */ + if (TLVP_PRES_LEN(&tp, NM_ATT_CCCH_L_T, 1)) + bts->load.ccch.load_ind_thresh = *TLVP_VAL(&tp, NM_ATT_CCCH_L_T); + + /* 9.4.11 CCCH Load Indication Period */ + if (TLVP_PRES_LEN(&tp, NM_ATT_CCCH_L_I_P, 1)) { + bts->load.ccch.load_ind_period = *TLVP_VAL(&tp, NM_ATT_CCCH_L_I_P); + load_timer_start(bts); + } + + /* 9.4.44 RACH Busy Threshold */ + if (TLVP_PRES_LEN(&tp, NM_ATT_RACH_B_THRESH, 1)) { + int16_t thresh = *TLVP_VAL(&tp, NM_ATT_RACH_B_THRESH); + bts->load.rach.busy_thresh = -1 * thresh; + } + + /* 9.4.45 RACH Load Averaging Slots */ + if (TLVP_PRES_LEN(&tp, NM_ATT_LDAVG_SLOTS, 2)) { + bts->load.rach.averaging_slots = + ntohs(tlvp_val16_unal(&tp, NM_ATT_LDAVG_SLOTS)); + } + + /* 9.4.10 BTS Air Timer */ + if (TLVP_PRES_LEN(&tp, NM_ATT_BTS_AIR_TIMER, 1)) { + uint8_t t3105 = *TLVP_VAL(&tp, NM_ATT_BTS_AIR_TIMER); + if (t3105 == 0) { + LOGP(DOML, LOGL_NOTICE, + "T3105 must have a value != 0.\n"); + return oml_fom_ack_nack(msg, NM_NACK_PARAM_RANGE); + } + bts->t3105_ms = t3105 * 10; + } + + /* 9.4.37 NY1 */ + if (TLVP_PRES_LEN(&tp, NM_ATT_NY1, 1)) + bts->ny1 = *TLVP_VAL(&tp, NM_ATT_NY1); + + /* 9.4.8 BCCH ARFCN */ + if (TLVP_PRES_LEN(&tp, NM_ATT_BCCH_ARFCN, 2)) + bts->c0->arfcn = ntohs(tlvp_val16_unal(&tp, NM_ATT_BCCH_ARFCN)); + + /* 9.4.9 BSIC */ + if (TLVP_PRES_LEN(&tp, NM_ATT_BSIC, 1)) + bts->bsic = *TLVP_VAL(&tp, NM_ATT_BSIC); + + /* call into BTS driver to apply new attributes to hardware */ + return bts_model_apply_oml(bts, msg, tp_merged, NM_OC_BTS, bts); +} + +/* 8.6.2 Set Radio Attributes has been received */ +static int oml_rx_set_radio_attr(struct gsm_bts_trx *trx, struct msgb *msg) +{ + struct abis_om_fom_hdr *foh = msgb_l3(msg); + struct tlv_parsed tp, *tp_merged; + int rc; + + abis_nm_debugp_foh(DOML, foh); + DEBUGPC(DOML, "Rx SET RADIO CARRIER ATTR\n"); + + rc = oml_tlv_parse(&tp, foh->data, msgb_l3len(msg) - sizeof(*foh)); + if (rc < 0) { + oml_tx_failure_event_rep(&trx->mo, OSMO_EVT_MAJ_UNSUP_ATTR, + "New value for Set Radio Attribute not" + " supported"); + return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT); + } + + /* merge existing BTS attributes with new attributes */ + tp_merged = osmo_tlvp_copy(trx->mo.nm_attr, trx->bts); + osmo_tlvp_merge(tp_merged, &tp); + + /* Ask BTS driver to validate new merged attributes */ + rc = bts_model_check_oml(trx->bts, foh->msg_type, trx->mo.nm_attr, tp_merged, trx); + if (rc < 0) { + talloc_free(tp_merged); + return oml_fom_ack_nack(msg, -rc); + } + + /* Success: replace old BTS attributes with new */ + talloc_free(trx->mo.nm_attr); + trx->mo.nm_attr = tp_merged; + + /* ... and actually still parse them */ + + /* 9.4.47 RF Max Power Reduction */ + if (TLVP_PRES_LEN(&tp, NM_ATT_RF_MAXPOWR_R, 1)) { + trx->max_power_red = *TLVP_VAL(&tp, NM_ATT_RF_MAXPOWR_R) * 2; + LOGP(DOML, LOGL_INFO, "Set RF Max Power Reduction = %d dBm\n", + trx->max_power_red); + } + /* 9.4.5 ARFCN List */ +#if 0 + if (TLVP_PRESENT(&tp, NM_ATT_ARFCN_LIST)) { + uint8_t *value = TLVP_VAL(&tp, NM_ATT_ARFCN_LIST); + uint16_t _value; + uint16_t length = TLVP_LEN(&tp, NM_ATT_ARFCN_LIST); + uint16_t arfcn; + int i; + for (i = 0; i < length; i++) { + memcpy(&_value, value, 2); + arfcn = ntohs(_value); + value += 2; + if (arfcn > 1024) + return oml_fom_ack_nack(msg, NM_NACK_FREQ_NOTAVAIL); + trx->arfcn_list[i] = arfcn; + LOGP(DOML, LOGL_INFO, " ARFCN list = %d\n", trx->arfcn_list[i]); + } + trx->arfcn_num = length; + } else + trx->arfcn_num = 0; +#else + if (trx != trx->bts->c0 && TLVP_PRESENT(&tp, NM_ATT_ARFCN_LIST)) { + const uint8_t *value = TLVP_VAL(&tp, NM_ATT_ARFCN_LIST); + uint16_t _value; + uint16_t length = TLVP_LEN(&tp, NM_ATT_ARFCN_LIST); + uint16_t arfcn; + if (length != 2) { + LOGP(DOML, LOGL_ERROR, "Expecting only one ARFCN, " + "because hopping not supported\n"); + return oml_fom_ack_nack(msg, NM_NACK_MSGINCONSIST_PHYSCFG); + } + memcpy(&_value, value, 2); + arfcn = ntohs(_value); + value += 2; + if (arfcn > 1024) { + oml_tx_failure_event_rep(&trx->bts->mo, + OSMO_EVT_WARN_SW_WARN, + "Given ARFCN %u is unsupported", + arfcn); + LOGP(DOML, LOGL_NOTICE, + "Given ARFCN %u is unsupported.\n", arfcn); + return oml_fom_ack_nack(msg, NM_NACK_FREQ_NOTAVAIL); + } + trx->arfcn = arfcn; + } +#endif + /* call into BTS driver to apply new attributes to hardware */ + return bts_model_apply_oml(trx->bts, msg, tp_merged, NM_OC_RADIO_CARRIER, trx); +} + +static int conf_lchans(struct gsm_bts_trx_ts *ts) +{ + enum gsm_phys_chan_config pchan = ts->pchan; + + /* RSL_MT_IPAC_PDCH_ACT style dyn PDCH */ + if (pchan == GSM_PCHAN_TCH_F_PDCH) + pchan = ts->flags & TS_F_PDCH_ACTIVE? GSM_PCHAN_PDCH + : GSM_PCHAN_TCH_F; + + /* Osmocom RSL CHAN ACT style dyn TS */ + if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + pchan = ts->dyn.pchan_is; + + /* If the dyn TS doesn't have a pchan yet, do nothing. */ + if (pchan == GSM_PCHAN_NONE) + return 0; + } + + return conf_lchans_as_pchan(ts, pchan); +} + +int conf_lchans_as_pchan(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config pchan) +{ + struct gsm_lchan *lchan; + unsigned int i; + + switch (pchan) { + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + /* fallthrough */ + case GSM_PCHAN_CCCH_SDCCH4: + for (i = 0; i < 4; i++) { + lchan = &ts->lchan[i]; + if (pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH + && i == 2) { + lchan->type = GSM_LCHAN_CBCH; + } else { + lchan->type = GSM_LCHAN_SDCCH; + } + } + /* fallthrough */ + case GSM_PCHAN_CCCH: + lchan = &ts->lchan[CCCH_LCHAN]; + lchan->type = GSM_LCHAN_CCCH; + break; + case GSM_PCHAN_TCH_F: + lchan = &ts->lchan[0]; + lchan->type = GSM_LCHAN_TCH_F; + break; + case GSM_PCHAN_TCH_H: + for (i = 0; i < 2; i++) { + lchan = &ts->lchan[i]; + lchan->type = GSM_LCHAN_TCH_H; + } + break; + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + /* fallthrough */ + case GSM_PCHAN_SDCCH8_SACCH8C: + for (i = 0; i < 8; i++) { + lchan = &ts->lchan[i]; + if (pchan == GSM_PCHAN_SDCCH8_SACCH8C_CBCH + && i == 2) { + lchan->type = GSM_LCHAN_CBCH; + } else { + lchan->type = GSM_LCHAN_SDCCH; + } + } + break; + case GSM_PCHAN_PDCH: + lchan = &ts->lchan[0]; + lchan->type = GSM_LCHAN_PDTCH; + break; + default: + LOGP(DOML, LOGL_ERROR, "Unknown/unhandled PCHAN type: %u %s\n", + ts->pchan, gsm_pchan_name(ts->pchan)); + return -NM_NACK_PARAM_RANGE; + } + return 0; +} + +/* 8.6.3 Set Channel Attributes has been received */ +static int oml_rx_set_chan_attr(struct gsm_bts_trx_ts *ts, struct msgb *msg) +{ + struct abis_om_fom_hdr *foh = msgb_l3(msg); + struct gsm_bts *bts = ts->trx->bts; + struct tlv_parsed tp, *tp_merged; + int rc; + + abis_nm_debugp_foh(DOML, foh); + DEBUGPC(DOML, "Rx SET CHAN ATTR\n"); + + rc = oml_tlv_parse(&tp, foh->data, msgb_l3len(msg) - sizeof(*foh)); + if (rc < 0) { + oml_tx_failure_event_rep(&ts->mo, OSMO_EVT_MAJ_UNSUP_ATTR, + "New value for Set Channel Attribute " + "not supported"); + return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT); + } + + /* 9.4.21 HSN... */ + /* 9.4.27 MAIO */ + if (TLVP_PRESENT(&tp, NM_ATT_HSN) || TLVP_PRESENT(&tp, NM_ATT_MAIO)) { + LOGP(DOML, LOGL_NOTICE, "SET CHAN ATTR: Frequency hopping not supported.\n"); + return oml_fom_ack_nack(msg, NM_NACK_SPEC_IMPL_NOTSUPP); + } + + /* 9.4.52 Starting Time */ + if (TLVP_PRESENT(&tp, NM_ATT_START_TIME)) { + LOGP(DOML, LOGL_NOTICE, "SET CHAN ATTR: Starting time not supported.\n"); + return oml_fom_ack_nack(msg, NM_NACK_SPEC_IMPL_NOTSUPP); + } + + /* merge existing BTS attributes with new attributes */ + tp_merged = osmo_tlvp_copy(ts->mo.nm_attr, bts); + osmo_tlvp_merge(tp_merged, &tp); + + /* Call into BTS driver to check attribute values */ + rc = bts_model_check_oml(bts, foh->msg_type, ts->mo.nm_attr, tp_merged, ts); + if (rc < 0) { + LOGP(DOML, LOGL_ERROR, "SET CHAN ATTR: invalid attribute value, rc=%d\n", rc); + talloc_free(tp_merged); + /* Send NACK */ + return oml_fom_ack_nack(msg, -rc); + } + + /* Success: replace old BTS attributes with new */ + talloc_free(ts->mo.nm_attr); + ts->mo.nm_attr = tp_merged; + + /* 9.4.13 Channel Combination */ + if (TLVP_PRES_LEN(&tp, NM_ATT_CHAN_COMB, 1)) { + uint8_t comb = *TLVP_VAL(&tp, NM_ATT_CHAN_COMB); + ts->pchan = abis_nm_pchan4chcomb(comb); + rc = conf_lchans(ts); + if (rc < 0) { + LOGP(DOML, LOGL_ERROR, "SET CHAN ATTR: invalid Chan Comb 0x%x" + " (pchan=%s, conf_lchans()->%d)\n", + comb, gsm_pchan_name(ts->pchan), rc); + talloc_free(tp_merged); + /* Send NACK */ + return oml_fom_ack_nack(msg, -rc); + } + } + + /* 9.4.5 ARFCN List */ + + /* 9.4.60 TSC */ + if (TLVP_PRES_LEN(&tp, NM_ATT_TSC, 1)) { + ts->tsc = *TLVP_VAL(&tp, NM_ATT_TSC); + } else { + /* If there is no TSC specified, use the BCC */ + ts->tsc = BSIC2BCC(bts->bsic); + } + LOGP(DOML, LOGL_INFO, "%s SET CHAN ATTR (TSC=%u pchan=%s)\n", + gsm_abis_mo_name(&ts->mo), ts->tsc, gsm_pchan_name(ts->pchan)); + + /* call into BTS driver to apply new attributes to hardware */ + return bts_model_apply_oml(bts, msg, tp_merged, NM_OC_CHANNEL, ts); +} + +/* 8.9.2 Opstart has been received */ +static int oml_rx_opstart(struct gsm_bts *bts, struct msgb *msg) +{ + struct abis_om_fom_hdr *foh = msgb_l3(msg); + struct gsm_abis_mo *mo; + void *obj; + + abis_nm_debugp_foh(DOML, foh); + DEBUGPC(DOML, "Rx OPSTART\n"); + + /* Step 1: Resolve MO by obj_class/obj_inst */ + mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst); + obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst); + if (!mo || !obj) + return oml_fom_ack_nack(msg, NM_NACK_OBJINST_UNKN); + + /* Step 2: Do some global dependency/consistency checking */ + if (mo->nm_state.operational == NM_OPSTATE_ENABLED) { + DEBUGP(DOML, "... automatic ACK, OP state already was Enabled\n"); + return oml_mo_opstart_ack(mo); + } + + /* Step 3: Ask BTS driver to apply the opstart */ + return bts_model_opstart(bts, mo, obj); +} + +static int oml_rx_chg_adm_state(struct gsm_bts *bts, struct msgb *msg) +{ + struct abis_om_fom_hdr *foh = msgb_l3(msg); + struct tlv_parsed tp; + struct gsm_abis_mo *mo; + uint8_t adm_state; + void *obj; + int rc; + + abis_nm_debugp_foh(DOML, foh); + DEBUGPC(DOML, "Rx CHG ADM STATE\n"); + + rc = oml_tlv_parse(&tp, foh->data, msgb_l3len(msg) - sizeof(*foh)); + if (rc < 0) { + LOGP(DOML, LOGL_ERROR, "Rx CHG ADM STATE: error during TLV parse\n"); + return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT); + } + + if (!TLVP_PRESENT(&tp, NM_ATT_ADM_STATE)) { + LOGP(DOML, LOGL_ERROR, "Rx CHG ADM STATE: no ADM state attribute\n"); + return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT); + } + + adm_state = *TLVP_VAL(&tp, NM_ATT_ADM_STATE); + + /* Step 1: Resolve MO by obj_class/obj_inst */ + mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst); + obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst); + if (!mo || !obj) + return oml_fom_ack_nack(msg, NM_NACK_OBJINST_UNKN); + + /* Step 2: Do some global dependency/consistency checking */ + if (mo->nm_state.administrative == adm_state) + LOGP(DOML, LOGL_NOTICE, + "ADM state already was %s\n", + get_value_string(abis_nm_adm_state_names, adm_state)); + + /* Step 3: Ask BTS driver to apply the state chg */ + return bts_model_chg_adm_state(bts, mo, obj, adm_state); +} + +/* Check and report if the BTS number received via OML is incorrect: + according to 3GPP TS 52.021 §9.3 BTS number is used to distinguish between different BTS of the same Site Manager. + As we always have only single BTS per Site Manager (in case of Abis/IP with each BTS having dedicated OML connection + to BSC), the only valid values are 0 and 0xFF (means all BTS' of a given Site Manager). */ +static inline bool report_bts_number_incorrect(struct gsm_bts *bts, const struct abis_om_fom_hdr *foh, bool is_formatted) +{ + struct gsm_bts_trx *trx; + struct gsm_abis_mo *mo = &bts->mo; + const char *form = is_formatted ? + "Unexpected BTS %d in formatted O&M %s (exp. 0 or 0xFF)" : + "Unexpected BTS %d in manufacturer O&M %s (exp. 0 or 0xFF)"; + + if (foh->obj_inst.bts_nr != 0 && foh->obj_inst.bts_nr != 0xff) { + LOGP(DOML, LOGL_ERROR, form, foh->obj_inst.bts_nr, get_value_string(abis_nm_msgtype_names, + foh->msg_type)); + LOGPC(DOML, LOGL_ERROR, "\n"); + trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr); + if (trx) { + trx->mo.obj_inst.bts_nr = 0; + trx->mo.obj_inst.trx_nr = foh->obj_inst.trx_nr; + trx->mo.obj_inst.ts_nr = 0xff; + mo = &trx->mo; + } + oml_tx_failure_event_rep(mo, OSMO_EVT_MAJ_UKWN_MSG, form, foh->obj_inst.bts_nr, + get_value_string(abis_nm_msgtype_names, foh->msg_type)); + + return true; + } + + return false; +} + +static int down_fom(struct gsm_bts *bts, struct msgb *msg) +{ + struct abis_om_fom_hdr *foh = msgb_l3(msg); + struct gsm_bts_trx *trx; + int ret; + + if (msgb_l2len(msg) < sizeof(*foh)) { + LOGP(DOML, LOGL_NOTICE, "Formatted O&M message too short\n"); + trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr); + if (trx) { + trx->mo.obj_inst.bts_nr = 0; + trx->mo.obj_inst.trx_nr = foh->obj_inst.trx_nr; + trx->mo.obj_inst.ts_nr = 0xff; + oml_tx_failure_event_rep(&trx->mo, OSMO_EVT_MAJ_UKWN_MSG, + "Formatted O&M message too short"); + } + return -EIO; + } + + if (report_bts_number_incorrect(bts, foh, true)) + return oml_fom_ack_nack(msg, NM_NACK_BTSNR_UNKN); + + switch (foh->msg_type) { + case NM_MT_SET_BTS_ATTR: + ret = oml_rx_set_bts_attr(bts, msg); + break; + case NM_MT_SET_RADIO_ATTR: + trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr); + if (!trx) + return oml_fom_ack_nack(msg, NM_NACK_TRXNR_UNKN); + ret = oml_rx_set_radio_attr(trx, msg); + break; + case NM_MT_SET_CHAN_ATTR: + trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr); + if (!trx) + return oml_fom_ack_nack(msg, NM_NACK_TRXNR_UNKN); + if (foh->obj_inst.ts_nr >= ARRAY_SIZE(trx->ts)) + return oml_fom_ack_nack(msg, NM_NACK_OBJINST_UNKN); + ret = oml_rx_set_chan_attr(&trx->ts[foh->obj_inst.ts_nr], msg); + break; + case NM_MT_OPSTART: + ret = oml_rx_opstart(bts, msg); + break; + case NM_MT_CHG_ADM_STATE: + ret = oml_rx_chg_adm_state(bts, msg); + break; + case NM_MT_IPACC_SET_ATTR: + ret = oml_ipa_set_attr(bts, msg); + break; + case NM_MT_GET_ATTR: + ret = oml_rx_get_attr(bts, msg); + break; + default: + LOGP(DOML, LOGL_INFO, "unknown Formatted O&M msg_type 0x%02x\n", + foh->msg_type); + trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr); + if (trx) { + trx->mo.obj_inst.bts_nr = 0; + trx->mo.obj_inst.trx_nr = foh->obj_inst.trx_nr; + trx->mo.obj_inst.ts_nr = 0xff; + oml_tx_failure_event_rep(&trx->mo, OSMO_EVT_MAJ_UKWN_MSG, + "unknown Formatted O&M " + "msg_type 0x%02x", + foh->msg_type); + } else + oml_tx_failure_event_rep(&bts->mo, OSMO_EVT_MAJ_UKWN_MSG, + "unknown Formatted O&M " + "msg_type 0x%02x", + foh->msg_type); + ret = oml_fom_ack_nack(msg, NM_NACK_MSGTYPE_INVAL); + } + + return ret; +} + +/* + * manufacturer related messages + */ + +static int oml_ipa_mo_set_attr_nse(void *obj, struct tlv_parsed *tp) +{ + struct gsm_bts *bts = container_of(obj, struct gsm_bts, gprs.nse); + + if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_NSEI, 2)) + bts->gprs.nse.nsei = + ntohs(tlvp_val16_unal(tp, NM_ATT_IPACC_NSEI)); + + if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_NS_CFG, 7)) { + memcpy(&bts->gprs.nse.timer, + TLVP_VAL(tp, NM_ATT_IPACC_NS_CFG), 7); + } + + if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_BSSGP_CFG, 11)) { + memcpy(&bts->gprs.cell.timer, + TLVP_VAL(tp, NM_ATT_IPACC_BSSGP_CFG), 11); + } + + osmo_signal_dispatch(SS_GLOBAL, S_NEW_NSE_ATTR, bts); + + return 0; +} + +static int oml_ipa_mo_set_attr_cell(void *obj, struct tlv_parsed *tp) +{ + struct gsm_bts *bts = container_of(obj, struct gsm_bts, gprs.cell); + struct gprs_rlc_cfg *rlcc = &bts->gprs.cell.rlc_cfg; + const uint8_t *cur; + uint16_t _cur_s; + + if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_RAC, 1)) + bts->gprs.rac = *TLVP_VAL(tp, NM_ATT_IPACC_RAC); + + if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_GPRS_PAGING_CFG, 2)) { + cur = TLVP_VAL(tp, NM_ATT_IPACC_GPRS_PAGING_CFG); + rlcc->paging.repeat_time = cur[0] * 50; + rlcc->paging.repeat_count = cur[1]; + } + + if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_BVCI, 2)) + bts->gprs.cell.bvci = + ntohs(tlvp_val16_unal(tp, NM_ATT_IPACC_BVCI)); + + if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_RLC_CFG, 9)) { + cur = TLVP_VAL(tp, NM_ATT_IPACC_RLC_CFG); + rlcc->parameter[RLC_T3142] = cur[0]; + rlcc->parameter[RLC_T3169] = cur[1]; + rlcc->parameter[RLC_T3191] = cur[2]; + rlcc->parameter[RLC_T3193] = cur[3]; + rlcc->parameter[RLC_T3195] = cur[4]; + rlcc->parameter[RLC_N3101] = cur[5]; + rlcc->parameter[RLC_N3103] = cur[6]; + rlcc->parameter[RLC_N3105] = cur[7]; + rlcc->parameter[CV_COUNTDOWN] = cur[8]; + } + + if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_CODING_SCHEMES, 2)) { + int i; + rlcc->cs_mask = 0; + cur = TLVP_VAL(tp, NM_ATT_IPACC_CODING_SCHEMES); + + for (i = 0; i < 4; i++) { + if (cur[0] & (1 << i)) + rlcc->cs_mask |= (1 << (GPRS_CS1+i)); + } + if (cur[0] & 0x80) + rlcc->cs_mask |= (1 << GPRS_MCS9); + for (i = 0; i < 8; i++) { + if (cur[1] & (1 << i)) + rlcc->cs_mask |= (1 << (GPRS_MCS1+i)); + } + } + + if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_RLC_CFG_2, 5)) { + cur = TLVP_VAL(tp, NM_ATT_IPACC_RLC_CFG_2); + memcpy(&_cur_s, cur, 2); + rlcc->parameter[T_DL_TBF_EXT] = ntohs(_cur_s) * 10; + cur += 2; + memcpy(&_cur_s, cur, 2); + rlcc->parameter[T_UL_TBF_EXT] = ntohs(_cur_s) * 10; + cur += 2; + rlcc->initial_cs = *cur; + } + + if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_RLC_CFG_3, 1)) { + rlcc->initial_mcs = *TLVP_VAL(tp, NM_ATT_IPACC_RLC_CFG_3); + } + + osmo_signal_dispatch(SS_GLOBAL, S_NEW_CELL_ATTR, bts); + + return 0; +} + +static int oml_ipa_mo_set_attr_nsvc(struct gsm_bts_gprs_nsvc *nsvc, + struct tlv_parsed *tp) +{ + if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_NSVCI, 2)) + nsvc->nsvci = ntohs(tlvp_val16_unal(tp, NM_ATT_IPACC_NSVCI)); + + if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_NS_LINK_CFG, 8)) { + const uint8_t *cur = TLVP_VAL(tp, NM_ATT_IPACC_NS_LINK_CFG); + uint16_t _cur_s; + uint32_t _cur_l; + + memcpy(&_cur_s, cur, 2); + nsvc->remote_port = ntohs(_cur_s); + cur += 2; + memcpy(&_cur_l, cur, 4); + nsvc->remote_ip = ntohl(_cur_l); + cur += 4; + memcpy(&_cur_s, cur, 2); + nsvc->local_port = ntohs(_cur_s); + } + + osmo_signal_dispatch(SS_GLOBAL, S_NEW_NSVC_ATTR, nsvc); + + return 0; +} + +static int oml_ipa_mo_set_attr(struct gsm_bts *bts, struct gsm_abis_mo *mo, + void *obj, struct tlv_parsed *tp) +{ + int rc; + + switch (mo->obj_class) { + case NM_OC_GPRS_NSE: + rc = oml_ipa_mo_set_attr_nse(obj, tp); + break; + case NM_OC_GPRS_CELL: + rc = oml_ipa_mo_set_attr_cell(obj, tp); + break; + case NM_OC_GPRS_NSVC: + rc = oml_ipa_mo_set_attr_nsvc(obj, tp); + break; + default: + rc = NM_NACK_OBJINST_UNKN; + } + + return rc; +} + +static int oml_ipa_set_attr(struct gsm_bts *bts, struct msgb *msg) +{ + struct abis_om_fom_hdr *foh = msgb_l3(msg); + struct gsm_abis_mo *mo; + struct tlv_parsed tp; + void *obj; + int rc; + + abis_nm_debugp_foh(DOML, foh); + DEBUGPC(DOML, "Rx IPA SET ATTR\n"); + + rc = oml_tlv_parse(&tp, foh->data, msgb_l3len(msg) - sizeof(*foh)); + if (rc < 0) { + mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst); + if (!mo) + return oml_fom_ack_nack(msg, NM_NACK_OBJINST_UNKN); + oml_tx_failure_event_rep(mo, OSMO_EVT_MAJ_UNSUP_ATTR, + "New value for IPAC Set Attribute not " + "supported\n"); + return oml_fom_ack_nack(msg, NM_NACK_INCORR_STRUCT); + } + + /* Resolve MO by obj_class/obj_inst */ + mo = gsm_objclass2mo(bts, foh->obj_class, &foh->obj_inst); + obj = gsm_objclass2obj(bts, foh->obj_class, &foh->obj_inst); + if (!mo || !obj) + return oml_fom_ack_nack(msg, NM_NACK_OBJINST_UNKN); + + rc = oml_ipa_mo_set_attr(bts, mo, obj, &tp); + + return oml_fom_ack_nack(msg, rc); +} + +static int rx_oml_ipa_rsl_connect(struct gsm_bts_trx *trx, struct msgb *msg, + struct tlv_parsed *tp) +{ + struct e1inp_sign_link *oml_link = trx->bts->oml_link; + uint16_t port = IPA_TCP_PORT_RSL; + uint32_t ip = get_signlink_remote_ip(oml_link); + struct in_addr in; + int rc; + + uint8_t stream_id = 0; + + if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_DST_IP, 4)) { + ip = ntohl(tlvp_val32_unal(tp, NM_ATT_IPACC_DST_IP)); + } + if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_DST_IP_PORT, 2)) { + port = ntohs(tlvp_val16_unal(tp, NM_ATT_IPACC_DST_IP_PORT)); + } + if (TLVP_PRES_LEN(tp, NM_ATT_IPACC_STREAM_ID, 1)) { + stream_id = *TLVP_VAL(tp, NM_ATT_IPACC_STREAM_ID); + } + + in.s_addr = htonl(ip); + LOGP(DOML, LOGL_INFO, "Rx IPA RSL CONNECT IP=%s PORT=%u STREAM=0x%02x\n", + inet_ntoa(in), port, stream_id); + + if (trx->bts->variant == BTS_OSMO_OMLDUMMY) { + rc = 0; + LOGP(DOML, LOGL_NOTICE, "Not connecting RSL in OML-DUMMY!\n"); + } else + rc = e1inp_ipa_bts_rsl_connect_n(oml_link->ts->line, inet_ntoa(in), port, trx->nr); + if (rc < 0) { + LOGP(DOML, LOGL_ERROR, "Error in abis_open(RSL): %d\n", rc); + return oml_fom_ack_nack(msg, NM_NACK_CANT_PERFORM); + } + + return oml_fom_ack_nack(msg, 0); +} + +static int down_mom(struct gsm_bts *bts, struct msgb *msg) +{ + struct abis_om_hdr *oh = msgb_l2(msg); + struct abis_om_fom_hdr *foh; + struct gsm_bts_trx *trx; + uint8_t idstrlen = oh->data[0]; + struct tlv_parsed tp; + int ret; + + if (msgb_l2len(msg) < sizeof(*foh)) { + LOGP(DOML, LOGL_NOTICE, "Manufacturer O&M message too short\n"); + return -EIO; + } + + if (strncmp((char *)&oh->data[1], abis_nm_ipa_magic, idstrlen)) { + LOGP(DOML, LOGL_ERROR, "Manufacturer OML message != ipaccess not supported\n"); + return -EINVAL; + } + + msg->l3h = oh->data + 1 + idstrlen; + foh = (struct abis_om_fom_hdr *) msg->l3h; + + if (report_bts_number_incorrect(bts, foh, false)) + return oml_fom_ack_nack(msg, NM_NACK_BTSNR_UNKN); + + ret = oml_tlv_parse(&tp, foh->data, oh->length - sizeof(*foh)); + if (ret < 0) { + LOGP(DOML, LOGL_ERROR, "TLV parse error %d\n", ret); + return oml_fom_ack_nack(msg, NM_NACK_BTSNR_UNKN); + } + + abis_nm_debugp_foh(DOML, foh); + DEBUGPC(DOML, "Rx IPACCESS(0x%02x): ", foh->msg_type); + + switch (foh->msg_type) { + case NM_MT_IPACC_RSL_CONNECT: + trx = gsm_bts_trx_num(bts, foh->obj_inst.trx_nr); + ret = rx_oml_ipa_rsl_connect(trx, msg, &tp); + break; + case NM_MT_IPACC_SET_ATTR: + ret = oml_ipa_set_attr(bts, msg); + break; + default: + LOGP(DOML, LOGL_INFO, "Manufacturer Formatted O&M msg_type 0x%02x\n", + foh->msg_type); + ret = oml_fom_ack_nack(msg, NM_NACK_MSGTYPE_INVAL); + } + + return ret; +} + +/* incoming OML message from BSC */ +int down_oml(struct gsm_bts *bts, struct msgb *msg) +{ + struct abis_om_hdr *oh = msgb_l2(msg); + int ret = 0; + + if (msgb_l2len(msg) < 1) { + LOGP(DOML, LOGL_NOTICE, "OML message too short\n"); + msgb_free(msg); + return -EIO; + } + msg->l3h = (unsigned char *)oh + sizeof(*oh); + + switch (oh->mdisc) { + case ABIS_OM_MDISC_FOM: + if (msgb_l2len(msg) < sizeof(*oh)) { + LOGP(DOML, LOGL_NOTICE, "Formatted O&M message too short\n"); + ret = -EIO; + break; + } + ret = down_fom(bts, msg); + break; + case ABIS_OM_MDISC_MANUF: + if (msgb_l2len(msg) < sizeof(*oh)) { + LOGP(DOML, LOGL_NOTICE, "Manufacturer O&M message too short\n"); + ret = -EIO; + break; + } + ret = down_mom(bts, msg); + break; + default: + LOGP(DOML, LOGL_NOTICE, "unknown OML msg_discr 0x%02x\n", + oh->mdisc); + ret = -EINVAL; + } + + msgb_free(msg); + + return ret; +} + +static int handle_fail_sig(unsigned int subsys, unsigned int signal, void *handle, + void *signal_data) +{ + if (signal_data) + oml_tx_failure_event_rep(handle, signal, "%s", signal_data); + else + oml_tx_failure_event_rep(handle, signal, ""); + + return 0; +} + +int oml_init(struct gsm_abis_mo *mo) +{ + DEBUGP(DOML, "Initializing OML attribute definitions\n"); + tlv_def_patch(&abis_nm_att_tlvdef_ipa_local, &abis_nm_att_tlvdef_ipa); + tlv_def_patch(&abis_nm_att_tlvdef_ipa_local, &abis_nm_att_tlvdef); + osmo_signal_register_handler(SS_FAIL, handle_fail_sig, mo); + + return 0; +} diff --git a/src/common/paging.c b/src/common/paging.c new file mode 100644 index 0000000..aa604e7 --- /dev/null +++ b/src/common/paging.c @@ -0,0 +1,630 @@ +/* Paging message encoding + queue management */ + +/* (C) 2011-2012 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +/* TODO: + * eMLPP priprity + * add P1/P2/P3 rest octets + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define MAX_PAGING_BLOCKS_CCCH 9 +#define MAX_BS_PA_MFRMS 9 + +enum paging_record_type { + PAGING_RECORD_PAGING, + PAGING_RECORD_IMM_ASS +}; + +struct paging_record { + struct llist_head list; + enum paging_record_type type; + union { + struct { + time_t expiration_time; + uint8_t chan_needed; + uint8_t identity_lv[9]; + } paging; + struct { + uint8_t msg[GSM_MACBLOCK_LEN]; + } imm_ass; + } u; +}; + +struct paging_state { + struct gsm_bts *bts; + + /* parameters taken / interpreted from BCCH/CCCH configuration */ + struct gsm48_control_channel_descr chan_desc; + + /* configured otherwise */ + unsigned int paging_lifetime; /* in seconds */ + unsigned int num_paging_max; + + /* total number of currently active paging records in queue */ + unsigned int num_paging; + struct llist_head paging_queue[MAX_PAGING_BLOCKS_CCCH*MAX_BS_PA_MFRMS]; +}; + +unsigned int paging_get_lifetime(struct paging_state *ps) +{ + return ps->paging_lifetime; +} + +unsigned int paging_get_queue_max(struct paging_state *ps) +{ + return ps->num_paging_max; +} + +void paging_set_lifetime(struct paging_state *ps, unsigned int lifetime) +{ + ps->paging_lifetime = lifetime; +} + +void paging_set_queue_max(struct paging_state *ps, unsigned int queue_max) +{ + ps->num_paging_max = queue_max; +} + +static int tmsi_mi_to_uint(uint32_t *out, const uint8_t *tmsi_lv) +{ + if (tmsi_lv[0] < 5) + return -EINVAL; + if ((tmsi_lv[1] & 7) != GSM_MI_TYPE_TMSI) + return -EINVAL; + + *out = *((uint32_t *)(tmsi_lv+2)); + + return 0; +} + +/* paging block numbers in a simple non-combined CCCH */ +static const uint8_t block_by_tdma51[51] = { + 255, 255, /* FCCH, SCH */ + 255, 255, 255, 255, /* BCCH */ + 0, 0, 0, 0, /* B0(6..9) */ + 255, 255, /* FCCH, SCH */ + 1, 1, 1, 1, /* B1(12..15) */ + 2, 2, 2, 2, /* B2(16..19) */ + 255, 255, /* FCCH, SCH */ + 3, 3, 3, 3, /* B3(22..25) */ + 4, 4, 4, 4, /* B3(26..29) */ + 255, 255, /* FCCH, SCH */ + 5, 5, 5, 5, /* B3(32..35) */ + 6, 6, 6, 6, /* B3(36..39) */ + 255, 255, /* FCCH, SCH */ + 7, 7, 7, 7, /* B3(42..45) */ + 8, 8, 8, 8, /* B3(46..49) */ + 255, /* empty */ +}; + +/* get the paging block number _within_ current 51 multiframe */ +static int get_pag_idx_n(struct paging_state *ps, struct gsm_time *gt) +{ + int blk_n = block_by_tdma51[gt->t3]; + int blk_idx; + + if (blk_n == 255) + return -EINVAL; + + blk_idx = blk_n - ps->chan_desc.bs_ag_blks_res; + if (blk_idx < 0) + return -EINVAL; + + return blk_idx; +} + +/* get paging block index over multiple 51 multiframes */ +static int get_pag_subch_nr(struct paging_state *ps, struct gsm_time *gt) +{ + int pag_idx = get_pag_idx_n(ps, gt); + unsigned int n_pag_blks_51 = gsm0502_get_n_pag_blocks(&ps->chan_desc); + unsigned int mfrm_part; + + if (pag_idx < 0) + return pag_idx; + + mfrm_part = ((gt->fn / 51) % (ps->chan_desc.bs_pa_mfrms+2)) * n_pag_blks_51; + + return pag_idx + mfrm_part; +} + +int paging_buffer_space(struct paging_state *ps) +{ + if (ps->num_paging >= ps->num_paging_max) + return 0; + else + return ps->num_paging_max - ps->num_paging; +} + +/* Add an identity to the paging queue */ +int paging_add_identity(struct paging_state *ps, uint8_t paging_group, + const uint8_t *identity_lv, uint8_t chan_needed) +{ + struct llist_head *group_q = &ps->paging_queue[paging_group]; + int blocks = gsm48_number_of_paging_subchannels(&ps->chan_desc); + struct paging_record *pr; + + rate_ctr_inc2(ps->bts->ctrs, BTS_CTR_PAGING_RCVD); + + if (paging_group >= blocks) { + LOGP(DPAG, LOGL_ERROR, "BSC Send PAGING for group %u, but number of paging " + "sub-channels is only %u\n", paging_group, blocks); + rate_ctr_inc2(ps->bts->ctrs, BTS_CTR_PAGING_DROP); + return -EINVAL; + } + + if (ps->num_paging >= ps->num_paging_max) { + LOGP(DPAG, LOGL_NOTICE, "Dropping paging, queue full (%u)\n", + ps->num_paging); + rate_ctr_inc2(ps->bts->ctrs, BTS_CTR_PAGING_DROP); + return -ENOSPC; + } + + /* Check if we already have this identity */ + llist_for_each_entry(pr, group_q, list) { + if (pr->type != PAGING_RECORD_PAGING) + continue; + if (identity_lv[0] == pr->u.paging.identity_lv[0] && + !memcmp(identity_lv+1, pr->u.paging.identity_lv+1, + identity_lv[0])) { + LOGP(DPAG, LOGL_INFO, "Ignoring duplicate paging\n"); + pr->u.paging.expiration_time = + time(NULL) + ps->paging_lifetime; + return -EEXIST; + } + } + + pr = talloc_zero(ps, struct paging_record); + if (!pr) + return -ENOMEM; + pr->type = PAGING_RECORD_PAGING; + + if (*identity_lv + 1 > sizeof(pr->u.paging.identity_lv)) { + talloc_free(pr); + return -E2BIG; + } + + LOGP(DPAG, LOGL_INFO, "Add paging to queue (group=%u, queue_len=%u)\n", + paging_group, ps->num_paging+1); + + pr->u.paging.expiration_time = time(NULL) + ps->paging_lifetime; + pr->u.paging.chan_needed = chan_needed; + memcpy(&pr->u.paging.identity_lv, identity_lv, identity_lv[0]+1); + + /* enqueue the new identity to the HEAD of the queue, + * to ensure it will be paged quickly at least once. */ + llist_add(&pr->list, group_q); + ps->num_paging++; + + return 0; +} + +/* Add an IMM.ASS message to the paging queue */ +int paging_add_imm_ass(struct paging_state *ps, const uint8_t *data, + uint8_t len) +{ + struct llist_head *group_q; + struct paging_record *pr; + uint16_t imsi, paging_group; + + if (len != GSM_MACBLOCK_LEN + 3) { + LOGP(DPAG, LOGL_ERROR, "IMM.ASS invalid length %d\n", len); + return -EINVAL; + } + len -= 3; + + imsi = 100 * ((*(data++)) - '0'); + imsi += 10 * ((*(data++)) - '0'); + imsi += (*(data++)) - '0'; + paging_group = gsm0502_calc_paging_group(&ps->chan_desc, imsi); + + group_q = &ps->paging_queue[paging_group]; + + pr = talloc_zero(ps, struct paging_record); + if (!pr) + return -ENOMEM; + pr->type = PAGING_RECORD_IMM_ASS; + + LOGP(DPAG, LOGL_INFO, "Add IMM.ASS to queue (group=%u)\n", + paging_group); + memcpy(pr->u.imm_ass.msg, data, GSM_MACBLOCK_LEN); + + /* enqueue the new message to the HEAD of the queue */ + llist_add(&pr->list, group_q); + + return 0; +} + +#define L2_PLEN(len) (((len - 1) << 2) | 0x01) + +static int fill_paging_type_1(uint8_t *out_buf, const uint8_t *identity1_lv, + uint8_t chan1, const uint8_t *identity2_lv, + uint8_t chan2) +{ + struct gsm48_paging1 *pt1 = (struct gsm48_paging1 *) out_buf; + uint8_t *cur; + + memset(out_buf, 0, sizeof(*pt1)); + + pt1->proto_discr = GSM48_PDISC_RR; + pt1->msg_type = GSM48_MT_RR_PAG_REQ_1; + pt1->pag_mode = GSM48_PM_NORMAL; + pt1->cneed1 = chan1 & 3; + pt1->cneed2 = chan2 & 3; + cur = lv_put(pt1->data, identity1_lv[0], identity1_lv+1); + if (identity2_lv) + cur = tlv_put(cur, GSM48_IE_MOBILE_ID, identity2_lv[0], identity2_lv+1); + + pt1->l2_plen = L2_PLEN(cur - out_buf); + + return cur - out_buf; +} + +static int fill_paging_type_2(uint8_t *out_buf, const uint8_t *tmsi1_lv, + uint8_t cneed1, const uint8_t *tmsi2_lv, + uint8_t cneed2, const uint8_t *identity3_lv) +{ + struct gsm48_paging2 *pt2 = (struct gsm48_paging2 *) out_buf; + uint8_t *cur; + + memset(out_buf, 0, sizeof(*pt2)); + + pt2->proto_discr = GSM48_PDISC_RR; + pt2->msg_type = GSM48_MT_RR_PAG_REQ_2; + pt2->pag_mode = GSM48_PM_NORMAL; + pt2->cneed1 = cneed1; + pt2->cneed2 = cneed2; + tmsi_mi_to_uint(&pt2->tmsi1, tmsi1_lv); + tmsi_mi_to_uint(&pt2->tmsi2, tmsi2_lv); + cur = out_buf + sizeof(*pt2); + + if (identity3_lv) + cur = tlv_put(pt2->data, GSM48_IE_MOBILE_ID, identity3_lv[0], identity3_lv+1); + + pt2->l2_plen = L2_PLEN(cur - out_buf); + + return cur - out_buf; +} + +static int fill_paging_type_3(uint8_t *out_buf, const uint8_t *tmsi1_lv, uint8_t cneed1, + const uint8_t *tmsi2_lv, uint8_t cneed2, + const uint8_t *tmsi3_lv, uint8_t cneed3, + const uint8_t *tmsi4_lv, uint8_t cneed4) +{ + struct gsm48_paging3 *pt3 = (struct gsm48_paging3 *) out_buf; + + memset(out_buf, 0, sizeof(*pt3)); + + pt3->proto_discr = GSM48_PDISC_RR; + pt3->msg_type = GSM48_MT_RR_PAG_REQ_3; + pt3->pag_mode = GSM48_PM_NORMAL; + pt3->cneed1 = cneed1; + pt3->cneed2 = cneed2; + tmsi_mi_to_uint(&pt3->tmsi1, tmsi1_lv); + tmsi_mi_to_uint(&pt3->tmsi2, tmsi2_lv); + tmsi_mi_to_uint(&pt3->tmsi3, tmsi3_lv); + tmsi_mi_to_uint(&pt3->tmsi4, tmsi4_lv); + + /* The structure definition in libosmocore is wrong. It includes as last + * byte some invalid definition of chneed3/chneed4, so we must do this by hand + * here and cannot rely on sizeof(*pt3) */ + out_buf[20] = (0x23 & ~0xf8) | 0x80 | (cneed3 & 3) << 5 | (cneed4 & 3) << 3; + + return 21; +} + +static const uint8_t empty_id_lv[] = { 0x01, 0xF0 }; + +static struct paging_record *dequeue_pr(struct llist_head *group_q) +{ + struct paging_record *pr; + + pr = llist_entry(group_q->next, struct paging_record, list); + llist_del(&pr->list); + + return pr; +} + +static int pr_is_imsi(struct paging_record *pr) +{ + if ((pr->u.paging.identity_lv[1] & 7) == GSM_MI_TYPE_IMSI) + return 1; + else + return 0; +} + +static void sort_pr_tmsi_imsi(struct paging_record *pr[], unsigned int n) +{ + int i, j; + struct paging_record *t; + + if (n < 2) + return; + + /* simple bubble sort */ + for (i = n-2; i >= 0; i--) { + for (j=0; j<=i ; j++) { + if (pr_is_imsi(pr[j]) > pr_is_imsi(pr[j+1])) { + t = pr[j]; + pr[j] = pr[j+1]; + pr[j+1] = t; + } + } + } +} + +/* generate paging message for given gsm time */ +int paging_gen_msg(struct paging_state *ps, uint8_t *out_buf, struct gsm_time *gt, + int *is_empty) +{ + struct llist_head *group_q; + int group; + int len; + + *is_empty = 0; + ps->bts->load.ccch.pch_total += 1; + + group = get_pag_subch_nr(ps, gt); + if (group < 0) { + LOGP(DPAG, LOGL_ERROR, + "Paging called for GSM wrong time: FN %d/%d/%d/%d.\n", + gt->fn, gt->t1, gt->t2, gt->t3); + return -1; + } + + group_q = &ps->paging_queue[group]; + + /* There is nobody to be paged, send Type1 with two empty ID */ + if (llist_empty(group_q)) { + //DEBUGP(DPAG, "Tx PAGING TYPE 1 (empty)\n"); + len = fill_paging_type_1(out_buf, empty_id_lv, 0, + NULL, 0); + *is_empty = 1; + } else { + struct paging_record *pr[4]; + unsigned int num_pr = 0, imm_ass = 0; + time_t now = time(NULL); + unsigned int i, num_imsi = 0; + + ps->bts->load.ccch.pch_used += 1; + + /* get (if we have) up to four paging records */ + for (i = 0; i < ARRAY_SIZE(pr); i++) { + if (llist_empty(group_q)) + break; + pr[i] = dequeue_pr(group_q); + + /* check for IMM.ASS */ + if (pr[i]->type == PAGING_RECORD_IMM_ASS) { + imm_ass = 1; + break; + } + + num_pr++; + + /* count how many IMSIs are among them */ + if (pr_is_imsi(pr[i])) + num_imsi++; + } + + /* if we have an IMMEDIATE ASSIGNMENT */ + if (imm_ass) { + /* re-add paging records */ + for (i = 0; i < num_pr; i++) + llist_add(&pr[i]->list, group_q); + + /* get message and free record */ + memcpy(out_buf, pr[num_pr]->u.imm_ass.msg, + GSM_MACBLOCK_LEN); + pcu_tx_pch_data_cnf(gt->fn, pr[num_pr]->u.imm_ass.msg, + GSM_MACBLOCK_LEN); + talloc_free(pr[num_pr]); + return GSM_MACBLOCK_LEN; + } + + /* make sure the TMSIs are ahead of the IMSIs in the array */ + sort_pr_tmsi_imsi(pr, num_pr); + + if (num_pr == 4 && num_imsi == 0) { + /* No IMSI: easy case, can use TYPE 3 */ + DEBUGP(DPAG, "Tx PAGING TYPE 3 (4 TMSI)\n"); + len = fill_paging_type_3(out_buf, + pr[0]->u.paging.identity_lv, + pr[0]->u.paging.chan_needed, + pr[1]->u.paging.identity_lv, + pr[1]->u.paging.chan_needed, + pr[2]->u.paging.identity_lv, + pr[2]->u.paging.chan_needed, + pr[3]->u.paging.identity_lv, + pr[3]->u.paging.chan_needed); + } else if (num_pr >= 3 && num_imsi <= 1) { + /* 3 or 4, of which only up to 1 is IMSI */ + DEBUGP(DPAG, "Tx PAGING TYPE 2 (2 TMSI,1 xMSI)\n"); + len = fill_paging_type_2(out_buf, + pr[0]->u.paging.identity_lv, + pr[0]->u.paging.chan_needed, + pr[1]->u.paging.identity_lv, + pr[1]->u.paging.chan_needed, + pr[2]->u.paging.identity_lv); + if (num_pr == 4) { + /* re-add #4 for next time */ + llist_add(&pr[3]->list, group_q); + pr[3] = NULL; + } + } else if (num_pr == 1) { + DEBUGP(DPAG, "Tx PAGING TYPE 1 (1 xMSI,1 empty)\n"); + len = fill_paging_type_1(out_buf, + pr[0]->u.paging.identity_lv, + pr[0]->u.paging.chan_needed, + NULL, 0); + } else { + /* 2 (any type) or + * 3 or 4, of which only 2 will be sent */ + DEBUGP(DPAG, "Tx PAGING TYPE 1 (2 xMSI)\n"); + len = fill_paging_type_1(out_buf, + pr[0]->u.paging.identity_lv, + pr[0]->u.paging.chan_needed, + pr[1]->u.paging.identity_lv, + pr[1]->u.paging.chan_needed); + if (num_pr >= 3) { + /* re-add #4 for next time */ + llist_add(&pr[2]->list, group_q); + pr[2] = NULL; + } + if (num_pr == 4) { + /* re-add #4 for next time */ + llist_add(&pr[3]->list, group_q); + pr[3] = NULL; + } + } + + for (i = 0; i < num_pr; i++) { + /* skip those that we might have re-added above */ + if (pr[i] == NULL) + continue; + rate_ctr_inc2(ps->bts->ctrs, BTS_CTR_PAGING_SENT); + /* check if we can expire the paging record, + * or if we need to re-queue it */ + if (pr[i]->u.paging.expiration_time <= now) { + talloc_free(pr[i]); + ps->num_paging--; + LOGP(DPAG, LOGL_INFO, "Removed paging record, queue_len=%u\n", + ps->num_paging); + } else + llist_add_tail(&pr[i]->list, group_q); + } + } + memset(out_buf+len, 0x2B, GSM_MACBLOCK_LEN-len); + return len; +} + +int paging_si_update(struct paging_state *ps, struct gsm48_control_channel_descr *chan_desc) +{ + LOGP(DPAG, LOGL_INFO, "Paging SI update\n"); + + ps->chan_desc = *chan_desc; + + /* FIXME: do we need to re-sort the old paging_records? */ + + return 0; +} + +static int paging_signal_cbfn(unsigned int subsys, unsigned int signal, void *hdlr_data, + void *signal_data) +{ + if (subsys == SS_GLOBAL && signal == S_NEW_SYSINFO) { + struct gsm_bts *bts = signal_data; + struct paging_state *ps = bts->paging_state; + struct gsm48_system_information_type_3 *si3 = (void *) bts->si_buf[SYSINFO_TYPE_3]; + + paging_si_update(ps, &si3->control_channel_desc); + } + return 0; +} + +static int initialized = 0; + +struct paging_state *paging_init(struct gsm_bts *bts, + unsigned int num_paging_max, + unsigned int paging_lifetime) +{ + struct paging_state *ps; + unsigned int i; + + ps = talloc_zero(bts, struct paging_state); + if (!ps) + return NULL; + + ps->bts = bts; + ps->paging_lifetime = paging_lifetime; + ps->num_paging_max = num_paging_max; + + for (i = 0; i < ARRAY_SIZE(ps->paging_queue); i++) + INIT_LLIST_HEAD(&ps->paging_queue[i]); + + if (!initialized) { + osmo_signal_register_handler(SS_GLOBAL, paging_signal_cbfn, NULL); + initialized = 1; + } + return ps; +} + +void paging_config(struct paging_state *ps, + unsigned int num_paging_max, + unsigned int paging_lifetime) +{ + ps->num_paging_max = num_paging_max; + ps->paging_lifetime = paging_lifetime; +} + +void paging_reset(struct paging_state *ps) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ps->paging_queue); i++) { + struct llist_head *queue = &ps->paging_queue[i]; + struct paging_record *pr, *pr2; + llist_for_each_entry_safe(pr, pr2, queue, list) { + llist_del(&pr->list); + talloc_free(pr); + ps->num_paging--; + } + } + + if (ps->num_paging != 0) + LOGP(DPAG, LOGL_NOTICE, "num_paging != 0 after flushing all records?!?\n"); + + ps->num_paging = 0; +} + +/** + * \brief Helper for the unit tests + */ +int paging_group_queue_empty(struct paging_state *ps, uint8_t grp) +{ + if (grp >= ARRAY_SIZE(ps->paging_queue)) + return 1; + return llist_empty(&ps->paging_queue[grp]); +} + +int paging_queue_length(struct paging_state *ps) +{ + return ps->num_paging; +} diff --git a/src/common/pcu_sock.c b/src/common/pcu_sock.c new file mode 100644 index 0000000..5f94050 --- /dev/null +++ b/src/common/pcu_sock.c @@ -0,0 +1,966 @@ +/* pcu_sock.c: Connect from PCU via unix domain socket */ + +/* (C) 2008-2010 by Harald Welte + * (C) 2009-2012 by Andreas Eversberg + * (C) 2012 by Holger Hans Peter Freyther + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx); + +extern struct gsm_network bts_gsmnet; +int pcu_direct = 0; +static int avail_lai = 0, avail_nse = 0, avail_cell = 0, avail_nsvc[2] = {0, 0}; + +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", +}; + +static int pcu_sock_send(struct gsm_network *net, struct msgb *msg); + +/* + * PCU messages + */ + +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; +} + +static bool ts_should_be_pdch(struct gsm_bts_trx_ts *ts) { + if (ts->pchan == GSM_PCHAN_PDCH) + return true; + if (ts->pchan == GSM_PCHAN_TCH_F_PDCH) { + /* When we're busy deactivating the PDCH, we first set + * DEACT_PENDING, tell the PCU about it and wait for a + * response. So DEACT_PENDING means "no PDCH" to the PCU. + * Similarly, when we're activating PDCH, we set the + * ACT_PENDING and wait for an activation response from the + * PCU, so ACT_PENDING means "is PDCH". */ + if (ts->flags & TS_F_PDCH_ACTIVE) + return !(ts->flags & TS_F_PDCH_DEACT_PENDING); + else + return (ts->flags & TS_F_PDCH_ACT_PENDING); + } + if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + /* + * When we're busy de-/activating the PDCH, we first set + * ts->dyn.pchan_want, tell the PCU about it and wait for a + * response. So only care about dyn.pchan_want here. + */ + return ts->dyn.pchan_want == GSM_PCHAN_PDCH; + } + return false; +} + +int pcu_tx_info_ind(void) +{ + struct gsm_network *net = &bts_gsmnet; + struct msgb *msg; + struct gsm_pcu_if *pcu_prim; + struct gsm_pcu_if_info_ind *info_ind; + struct gsm_bts *bts; + 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; + + LOGP(DPCU, LOGL_INFO, "Sending info\n"); + + /* FIXME: allow multiple BTS */ + bts = llist_entry(net->bts_list.next, struct gsm_bts, list); + 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; + + if (avail_lai && avail_nse && avail_cell && avail_nsvc[0]) { + info_ind->flags |= PCU_IF_FLAG_ACTIVE; + LOGP(DPCU, LOGL_INFO, "BTS is up\n"); + } else + LOGP(DPCU, LOGL_INFO, "BTS is down\n"); + + if (pcu_direct) + info_ind->flags |= PCU_IF_FLAG_SYSMO; + + /* RAI */ + info_ind->mcc = net->plmn.mcc; + info_ind->mnc = net->plmn.mnc; + info_ind->mnc_3_digits = net->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 (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 < 2; 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 < 8; i++) { + trx = gsm_bts_trx_num(bts, i); + if (!trx) + break; + info_ind->trx[i].pdch_mask = 0; + info_ind->trx[i].arfcn = trx->arfcn; + info_ind->trx[i].hlayer1 = trx_get_hlayer1(trx); + for (j = 0; j < 8; j++) { + ts = &trx->ts[j]; + if (ts->mo.nm_state.operational == NM_OPSTATE_ENABLED + && ts_should_be_pdch(ts)) { + info_ind->trx[i].pdch_mask |= (1 << j); + info_ind->trx[i].tsc[j] = gsm_ts_tsc(ts); + + 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(net, msg); +} + +static int pcu_if_signal_cb(unsigned int subsys, unsigned int signal, + void *hdlr_data, void *signal_data) +{ + struct gsm_network *net = &bts_gsmnet; + struct gsm_bts_gprs_nsvc *nsvc; + struct gsm_bts *bts; + struct gsm48_system_information_type_3 *si3; + int id; + + if (subsys != SS_GLOBAL) + return -EINVAL; + + switch(signal) { + case S_NEW_SYSINFO: + bts = signal_data; + if (!(bts->si_valid & (1 << SYSINFO_TYPE_3))) + break; + si3 = (struct gsm48_system_information_type_3 *) + bts->si_buf[SYSINFO_TYPE_3]; + osmo_plmn_from_bcd(si3->lai.digits, &net->plmn); + bts->location_area_code = ntohs(si3->lai.lac); + bts->cell_identity = si3->cell_identity; + avail_lai = 1; + break; + case S_NEW_NSE_ATTR: + bts = signal_data; + avail_nse = 1; + break; + case S_NEW_CELL_ATTR: + bts = signal_data; + avail_cell = 1; + break; + case S_NEW_NSVC_ATTR: + nsvc = signal_data; + id = nsvc->id; + if (id < 0 || id > 1) + return -EINVAL; + avail_nsvc[id] = 1; + break; + case S_NEW_OP_STATE: + break; + default: + return -EINVAL; + } + + /* If all infos have been received, of if one info is updated after + * all infos have been received, transmit info update. */ + if (avail_lai && avail_nse && avail_cell && avail_nsvc[0]) + pcu_tx_info_ind(); + return 0; +} + + +int pcu_tx_rts_req(struct gsm_bts_trx_ts *ts, uint8_t is_ptcch, uint32_t fn, + uint16_t arfcn, uint8_t block_nr) +{ + struct msgb *msg; + struct gsm_pcu_if *pcu_prim; + struct gsm_pcu_if_rts_req *rts_req; + struct gsm_bts *bts = ts->trx->bts; + + LOGP(DPCU, LOGL_DEBUG, "Sending rts request: is_ptcch=%d arfcn=%d " + "block=%d\n", is_ptcch, arfcn, block_nr); + + msg = pcu_msgb_alloc(PCU_IF_MSG_RTS_REQ, bts->nr); + if (!msg) + return -ENOMEM; + pcu_prim = (struct gsm_pcu_if *) msg->data; + rts_req = &pcu_prim->u.rts_req; + + rts_req->sapi = (is_ptcch) ? PCU_IF_SAPI_PTCCH : PCU_IF_SAPI_PDTCH; + rts_req->fn = fn; + rts_req->arfcn = arfcn; + rts_req->trx_nr = ts->trx->nr; + rts_req->ts_nr = ts->nr; + rts_req->block_nr = block_nr; + + return pcu_sock_send(&bts_gsmnet, msg); +} + +int pcu_tx_data_ind(struct gsm_bts_trx_ts *ts, uint8_t sapi, uint32_t fn, + uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len, + int8_t rssi, uint16_t ber10k, int16_t bto, int16_t lqual) +{ + struct msgb *msg; + struct gsm_pcu_if *pcu_prim; + struct gsm_pcu_if_data *data_ind; + struct gsm_bts *bts = ts->trx->bts; + + LOGP(DPCU, LOGL_DEBUG, "Sending data indication: sapi=%s arfcn=%d block=%d data=%s\n", + sapi_string[sapi], arfcn, block_nr, osmo_hexdump(data, len)); + + if (lqual / 10 < bts->min_qual_norm) { + LOGP(DPCU, LOGL_DEBUG, "Link quality %"PRId16" is below threshold %f, dropping packet\n", + lqual, bts->min_qual_norm); + return 0; + } + + msg = pcu_msgb_alloc(PCU_IF_MSG_DATA_IND, bts->nr); + if (!msg) + return -ENOMEM; + pcu_prim = (struct gsm_pcu_if *) msg->data; + data_ind = &pcu_prim->u.data_ind; + + data_ind->sapi = sapi; + data_ind->rssi = rssi; + data_ind->fn = fn; + data_ind->arfcn = arfcn; + data_ind->trx_nr = ts->trx->nr; + data_ind->ts_nr = ts->nr; + data_ind->block_nr = block_nr; + data_ind->ber10k = ber10k; + data_ind->ta_offs_qbits = bto; + data_ind->lqual_cb = lqual; + memcpy(data_ind->data, data, len); + data_ind->len = len; + + return pcu_sock_send(&bts_gsmnet, msg); +} + +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; + + 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_gsmnet, msg); +} + +int pcu_tx_time_ind(uint32_t fn) +{ + struct msgb *msg; + struct gsm_pcu_if *pcu_prim; + struct gsm_pcu_if_time_ind *time_ind; + uint8_t fn13 = fn % 13; + + /* omit frame numbers not starting at a MAC block */ + if (fn13 != 0 && fn13 != 4 && fn13 != 8) + return 0; + + msg = pcu_msgb_alloc(PCU_IF_MSG_TIME_IND, 0); + if (!msg) + return -ENOMEM; + pcu_prim = (struct gsm_pcu_if *) msg->data; + time_ind = &pcu_prim->u.time_ind; + + time_ind->fn = fn; + + return pcu_sock_send(&bts_gsmnet, msg); +} + +int pcu_tx_pag_req(const uint8_t *identity_lv, uint8_t chan_needed) +{ + struct pcu_sock_state *state = bts_gsmnet.pcu_state; + struct msgb *msg; + struct gsm_pcu_if *pcu_prim; + struct gsm_pcu_if_pag_req *pag_req; + + /* check if identity does not fit: length > sizeof(lv) - 1 */ + if (identity_lv[0] >= sizeof(pag_req->identity_lv)) { + LOGP(DPCU, LOGL_ERROR, "Paging identity too large (%d)\n", + identity_lv[0]); + return -EINVAL; + } + + /* socket not created */ + if (!state) { + LOGP(DPCU, LOGL_DEBUG, "PCU socket not created, ignoring " + "paging message\n"); + return 0; + } + + msg = pcu_msgb_alloc(PCU_IF_MSG_PAG_REQ, 0); + if (!msg) + return -ENOMEM; + pcu_prim = (struct gsm_pcu_if *) msg->data; + pag_req = &pcu_prim->u.pag_req; + + pag_req->chan_needed = chan_needed; + memcpy(pag_req->identity_lv, identity_lv, identity_lv[0] + 1); + + return pcu_sock_send(&bts_gsmnet, msg); +} + +int pcu_tx_pch_data_cnf(uint32_t fn, uint8_t *data, uint8_t len) +{ + struct gsm_network *net = &bts_gsmnet; + struct gsm_bts *bts; + struct msgb *msg; + struct gsm_pcu_if *pcu_prim; + struct gsm_pcu_if_data *data_cnf; + + /* FIXME: allow multiple BTS */ + bts = llist_entry(net->bts_list.next, struct gsm_bts, list); + + LOGP(DPCU, LOGL_INFO, "Sending PCH confirm\n"); + + msg = pcu_msgb_alloc(PCU_IF_MSG_DATA_CNF, bts->nr); + if (!msg) + return -ENOMEM; + pcu_prim = (struct gsm_pcu_if *) msg->data; + data_cnf = &pcu_prim->u.data_cnf; + + data_cnf->sapi = PCU_IF_SAPI_PCH; + data_cnf->fn = fn; + memcpy(data_cnf->data, data, len); + data_cnf->len = len; + + return pcu_sock_send(&bts_gsmnet, msg); +} + +static int pcu_rx_data_req(struct gsm_bts *bts, uint8_t msg_type, + struct gsm_pcu_if_data *data_req) +{ + uint8_t is_ptcch; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + struct msgb *msg; + 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: + if (msg_type == PCU_IF_MSG_PAG_REQ) { + /* FIXME: Add function to schedule paging request. + * This might not be required, if PCU_IF_MSG_DATA_REQ + * is used instead. */ + } else { + paging_add_imm_ass(bts->paging_state, data_req->data, data_req->len); + } + 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 (bts_agch_enqueue(bts, msg) < 0) { + msgb_free(msg); + rc = -EIO; + } + break; + case PCU_IF_SAPI_PDTCH: + case PCU_IF_SAPI_PTCCH: + trx = gsm_bts_trx_num(bts, data_req->trx_nr); + if (!trx) { + LOGP(DPCU, LOGL_ERROR, "Received PCU data request with " + "not existing TRX %d\n", data_req->trx_nr); + rc = -EINVAL; + break; + } + if (data_req->ts_nr >= ARRAY_SIZE(trx->ts)) { + LOGP(DPCU, LOGL_ERROR, "Received PCU data request with " + "not existing TS %u\n", data_req->ts_nr); + rc = -EINVAL; + break; + } + ts = &trx->ts[data_req->ts_nr]; + if (!ts_should_be_pdch(ts)) { + LOGP(DPCU, LOGL_ERROR, "%s: Received PCU DATA request for non-PDCH TS\n", + gsm_ts_name(ts)); + rc = -EINVAL; + break; + } + if (ts->lchan[0].state != LCHAN_S_ACTIVE) { + LOGP(DPCU, LOGL_ERROR, "%s: Received PCU DATA request for inactive lchan\n", + gsm_ts_name(ts)); + rc = -EINVAL; + break; + } + is_ptcch = (data_req->sapi == PCU_IF_SAPI_PTCCH); + rc = l1sap_pdch_req(ts, is_ptcch, data_req->fn, data_req->arfcn, + data_req->block_nr, data_req->data, data_req->len); + break; + default: + LOGP(DPCU, LOGL_ERROR, "Received PCU data request with " + "unsupported sapi %d\n", data_req->sapi); + rc = -EINVAL; + } + + return rc; +} + +int pcu_tx_si13(const struct gsm_bts *bts, bool enable) +{ + /* the SI is per-BTS so it doesn't matter which TRX we use */ + struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, 0); + + /* The low-level data like FN, ARFCN etc will be ignored but we have to set lqual high enough to bypass + the check at lower levels */ + int rc = pcu_tx_data_ind(&trx->ts[0], PCU_IF_SAPI_BCCH, 0, 0, 0, GSM_BTS_SI(bts, SYSINFO_TYPE_13), + enable ? GSM_MACBLOCK_LEN : 0, 0, 0, 0, INT16_MAX); + if (rc < 0) + LOGP(DPCU, LOGL_NOTICE, "Failed to send SI13 to PCU: %d\n", rc); + + return rc; +} + +static int pcu_rx_txt_ind(struct gsm_bts *bts, + struct gsm_pcu_if_txt_ind *txt) +{ + switch (txt->type) { + case PCU_VERSION: + LOGP(DPCU, LOGL_INFO, "OsmoPCU version %s connected\n", + txt->text); + osmo_signal_dispatch(SS_FAIL, OSMO_EVT_PCU_VERS, txt->text); + osmo_strlcpy(bts->pcu_version, txt->text, MAX_VERSION_LENGTH); + + if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_13)) + return pcu_tx_si13(bts, true); + + LOGP(DPCU, LOGL_INFO, "SI13 is not available on PCU connection\n"); + break; + case PCU_OML_ALERT: + osmo_signal_dispatch(SS_FAIL, OSMO_EVT_EXT_ALARM, txt->text); + break; + default: + LOGP(DPCU, LOGL_ERROR, "Unknown TXT_IND type %u received\n", + txt->type); + return -EINVAL; + } + + return 0; +} + +static int pcu_rx_act_req(struct gsm_bts *bts, + struct gsm_pcu_if_act_req *act_req) +{ + struct gsm_bts_trx *trx; + struct gsm_lchan *lchan; + + LOGP(DPCU, LOGL_INFO, "%s request received: TRX=%d TX=%d\n", + (act_req->activate) ? "Activate" : "Deactivate", + act_req->trx_nr, act_req->ts_nr); + + trx = gsm_bts_trx_num(bts, act_req->trx_nr); + if (!trx || act_req->ts_nr >= 8) + return -EINVAL; + + lchan = trx->ts[act_req->ts_nr].lchan; + lchan->rel_act_kind = LCHAN_REL_ACT_PCU; + if (lchan->type != GSM_LCHAN_PDTCH) { + LOGP(DPCU, LOGL_ERROR, + "%s request, but lchan is not of type PDTCH (is %s)\n", + (act_req->activate) ? "Activate" : "Deactivate", + gsm_lchant_name(lchan->type)); + return -EINVAL; + } + if (act_req->activate) + l1sap_chan_act(trx, gsm_lchan2chan_nr(lchan), NULL); + else + l1sap_chan_rel(trx, gsm_lchan2chan_nr(lchan)); + + return 0; +} + +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 */ + if (pcu_prim->bts_nr != 0) { + LOGP(DPCU, LOGL_ERROR, "Received PCU Prim for non-existent BTS %u\n", pcu_prim->bts_nr); + return -EINVAL; + } + 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; + case PCU_IF_MSG_ACT_REQ: + rc = pcu_rx_act_req(bts, &pcu_prim->u.act_req); + break; + case PCU_IF_MSG_TXT_IND: + rc = pcu_rx_txt_ind(bts, &pcu_prim->u.txt_ind); + break; + default: + LOGP(DPCU, LOGL_ERROR, "Received unknwon PCU msg type %d\n", + msg_type); + rc = -EINVAL; + } + + return rc; +} + +/* + * PCU socket interface + */ + +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 */ +}; + +static int pcu_sock_send(struct gsm_network *net, struct msgb *msg) +{ + struct pcu_sock_state *state = net->pcu_state; + struct osmo_fd *conn_bfd; + struct gsm_pcu_if *pcu_prim = (struct gsm_pcu_if *) msg->data; + + 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"); + osmo_signal_dispatch(SS_FAIL, OSMO_EVT_PCU_VERS, NULL); + bts->pcu_version[0] = '\0'; + + close(bfd->fd); + bfd->fd = -1; + osmo_fd_unregister(bfd); + + /* re-enable the generation of ACCEPT for new connections */ + state->listen_bfd.when |= BSC_FD_READ; + +#if 0 + /* remove si13, ... */ + bts->si_valid &= ~(1 << SYSINFO_TYPE_13); + osmo_signal_dispatch(SS_GLOBAL, S_NEW_SYSINFO, bts); +#endif + + /* release PDCH */ + for (i = 0; i < 8; i++) { + trx = gsm_bts_trx_num(bts, i); + if (!trx) + break; + for (j = 0; j < 8; j++) { + ts = &trx->ts[j]; + if (ts->mo.nm_state.operational == NM_OPSTATE_ENABLED + && ts->pchan == GSM_PCHAN_PDCH) { + ts->lchan->rel_act_kind = LCHAN_REL_ACT_PCU; + l1sap_chan_rel(trx, + gsm_lchan2chan_nr(ts->lchan)); + } + } + } + + /* 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; + } + + if (rc < sizeof(*pcu_prim)) { + LOGP(DPCU, LOGL_ERROR, "Received %d bytes on PCU Socket, but primitive size " + "is %lu, discarding\n", rc, sizeof(*pcu_prim)); + return 0; + } + + 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"); + + /* send current info */ + pcu_tx_info_ind(); + + return 0; +} + +int pcu_sock_init(const char *path) +{ + 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_gsmnet; + 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 %s unix socket: %s\n", + path, 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; + } + + osmo_signal_register_handler(SS_GLOBAL, pcu_if_signal_cb, NULL); + + bts_gsmnet.pcu_state = state; + + return 0; +} + +void pcu_sock_exit(void) +{ + struct pcu_sock_state *state = bts_gsmnet.pcu_state; + struct osmo_fd *bfd, *conn_bfd; + + if (!state) + return; + + osmo_signal_unregister_handler(SS_GLOBAL, pcu_if_signal_cb, NULL); + 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_gsmnet.pcu_state = NULL; +} + +bool pcu_connected(void) { + struct gsm_network *net = &bts_gsmnet; + struct pcu_sock_state *state = net->pcu_state; + + if (!state) + return false; + if (state->conn_bfd.fd <= 0) + return false; + return true; +} diff --git a/src/common/phy_link.c b/src/common/phy_link.c new file mode 100644 index 0000000..588fcc9 --- /dev/null +++ b/src/common/phy_link.c @@ -0,0 +1,163 @@ +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +static LLIST_HEAD(g_phy_links); + +struct phy_link *phy_link_by_num(int num) +{ + struct phy_link *plink; + + llist_for_each_entry(plink, &g_phy_links, list) { + if (plink->num == num) + return plink; + } + + return NULL; +} + +struct phy_link *phy_link_create(void *ctx, int num) +{ + struct phy_link *plink; + + if (phy_link_by_num(num)) + return NULL; + + plink = talloc_zero(ctx, struct phy_link); + plink->num = num; + plink->state = PHY_LINK_SHUTDOWN; + INIT_LLIST_HEAD(&plink->instances); + llist_add_tail(&plink->list, &g_phy_links); + + bts_model_phy_link_set_defaults(plink); + + return plink; +} + +const struct value_string phy_link_state_vals[] = { + { PHY_LINK_SHUTDOWN, "shutdown" }, + { PHY_LINK_CONNECTING, "connecting" }, + { PHY_LINK_CONNECTED, "connected" }, + { 0, NULL } +}; + +void phy_link_state_set(struct phy_link *plink, enum phy_link_state state) +{ + struct phy_instance *pinst; + + LOGP(DL1C, LOGL_INFO, "PHY link state change %s -> %s\n", + get_value_string(phy_link_state_vals, plink->state), + get_value_string(phy_link_state_vals, state)); + + /* notify all TRX associated with this phy */ + llist_for_each_entry(pinst, &plink->instances, list) { + struct gsm_bts_trx *trx = pinst->trx; + if (!trx) + continue; + + switch (state) { + case PHY_LINK_CONNECTED: + LOGP(DL1C, LOGL_INFO, "trx_set_avail(1)\n"); + trx_set_available(trx, 1); + break; + case PHY_LINK_SHUTDOWN: + LOGP(DL1C, LOGL_INFO, "trx_set_avail(0)\n"); + trx_set_available(trx, 0); + break; + case PHY_LINK_CONNECTING: + /* nothing to do */ + break; + } + } + + plink->state = state; +} + +struct phy_instance *phy_instance_by_num(struct phy_link *plink, int num) +{ + struct phy_instance *pinst; + + llist_for_each_entry(pinst, &plink->instances, list) { + if (pinst->num == num) + return pinst; + } + return NULL; +} + +struct phy_instance *phy_instance_create(struct phy_link *plink, int num) +{ + struct phy_instance *pinst; + + if (phy_instance_by_num(plink, num)) + return NULL; + + pinst = talloc_zero(plink, struct phy_instance); + pinst->num = num; + pinst->phy_link = plink; + llist_add_tail(&pinst->list, &plink->instances); + + bts_model_phy_instance_set_defaults(pinst); + + return pinst; +} + +void phy_instance_link_to_trx(struct phy_instance *pinst, struct gsm_bts_trx *trx) +{ + trx->role_bts.l1h = pinst; + pinst->trx = trx; +} + +void phy_instance_destroy(struct phy_instance *pinst) +{ + /* remove from list of instances in the link */ + llist_del(&pinst->list); + + /* remove reverse link from TRX */ + OSMO_ASSERT(pinst->trx->role_bts.l1h == pinst); + pinst->trx->role_bts.l1h = NULL; + pinst->trx = NULL; + + talloc_free(pinst); +} + +void phy_link_destroy(struct phy_link *plink) +{ + struct phy_instance *pinst, *pinst2; + + llist_for_each_entry_safe(pinst, pinst2, &plink->instances, list) + phy_instance_destroy(pinst); + + talloc_free(plink); +} + +int phy_links_open(void) +{ + struct phy_link *plink; + + llist_for_each_entry(plink, &g_phy_links, list) { + int rc; + + rc = bts_model_phy_link_open(plink); + if (rc < 0) + return rc; + } + + return 0; +} + +const char *phy_instance_name(struct phy_instance *pinst) +{ + static char buf[32]; + + snprintf(buf, sizeof(buf), "phy%u.%u", pinst->phy_link->num, + pinst->num); + return buf; +} diff --git a/src/common/power_control.c b/src/common/power_control.c new file mode 100644 index 0000000..b172870 --- /dev/null +++ b/src/common/power_control.c @@ -0,0 +1,89 @@ +/* MS Power Control Loop L1 */ + +/* (C) 2014 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* + * Check if manual power control is needed + * Check if fixed power was selected + * Check if the MS is already using our level if not + * the value is bogus.. + * TODO: Add a timeout.. e.g. if the ms is not capable of reaching + * the value we have set. + */ +int lchan_ms_pwr_ctrl(struct gsm_lchan *lchan, + const uint8_t ms_power, const int rxLevel) +{ + int rx; + int cur_dBm, new_dBm, new_pwr; + struct gsm_bts *bts = lchan->ts->trx->bts; + const enum gsm_band band = bts->band; + + if (!trx_ms_pwr_ctrl_is_osmo(lchan->ts->trx)) + return 0; + if (lchan->ms_power_ctrl.fixed) + return 0; + + /* The phone hasn't reached the power level yet */ + if (lchan->ms_power_ctrl.current != ms_power) + return 0; + + /* What is the difference between what we want and received? */ + rx = bts->ul_power_target - rxLevel; + + cur_dBm = ms_pwr_dbm(band, ms_power); + new_dBm = cur_dBm + rx; + + /* Clamp negative values and do it depending on the band */ + if (new_dBm < 0) + new_dBm = 0; + + switch (band) { + case GSM_BAND_1800: + /* If MS_TX_PWR_MAX_CCH is set the values 29, + * 30, 31 are not used. Avoid specifying a dBm + * that would lead to these power levels. The + * phone might not be able to reach them. */ + if (new_dBm > 30) + new_dBm = 30; + break; + default: + break; + } + + new_pwr = ms_pwr_ctl_lvl(band, new_dBm); + if (lchan->ms_power_ctrl.current != new_pwr) { + lchan->ms_power_ctrl.current = new_pwr; + bts_model_adjst_ms_pwr(lchan); + return 1; + } + + return 0; +} diff --git a/src/common/rsl.c b/src/common/rsl.c new file mode 100644 index 0000000..5dd2c59 --- /dev/null +++ b/src/common/rsl.c @@ -0,0 +1,2900 @@ +/* GSM TS 08.58 RSL, BTS Side */ + +/* (C) 2011 by Andreas Eversberg + * (C) 2011-2017 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include "btsconfig.h" /* for PACKAGE_VERSION */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#define FAKE_CIPH_MODE_COMPL + + +static int rsl_tx_error_report(struct gsm_bts_trx *trx, uint8_t cause, const uint8_t *chan_nr, + const uint8_t *link_id, const struct msgb *orig_msg); + +/* list of RSL SI types that can occur on the SACCH */ +static const unsigned int rsl_sacch_sitypes[] = { + RSL_SYSTEM_INFO_5, + RSL_SYSTEM_INFO_6, + RSL_SYSTEM_INFO_5bis, + RSL_SYSTEM_INFO_5ter, + RSL_EXT_MEAS_ORDER, + RSL_MEAS_INFO, +}; + +/* FIXME: move this to libosmocore */ +int osmo_in_array(unsigned int search, const unsigned int *arr, unsigned int size) +{ + unsigned int i; + for (i = 0; i < size; i++) { + if (arr[i] == search) + return 1; + } + return 0; +} +#define OSMO_IN_ARRAY(search, arr) osmo_in_array(search, arr, ARRAY_SIZE(arr)) + +int msgb_queue_flush(struct llist_head *list) +{ + struct msgb *msg, *msg2; + int count = 0; + + llist_for_each_entry_safe(msg, msg2, list, list) { + msgb_free(msg); + count++; + } + + return count; +} + +/* FIXME: move this to libosmocore */ +void gsm48_gen_starting_time(uint8_t *out, struct gsm_time *gtime) +{ + uint8_t t1p = gtime->t1 % 32; + out[0] = (t1p << 3) | (gtime->t3 >> 3); + out[1] = (gtime->t3 << 5) | gtime->t2; +} + +/* compute lchan->rsl_cmode and lchan->tch_mode from RSL CHAN MODE IE */ +static void lchan_tchmode_from_cmode(struct gsm_lchan *lchan, + struct rsl_ie_chan_mode *cm) +{ + lchan->rsl_cmode = cm->spd_ind; + lchan->ts->trx->bts->dtxd = (cm->dtx_dtu & RSL_CMOD_DTXd) ? true : false; + + switch (cm->chan_rate) { + case RSL_CMOD_SP_GSM1: + lchan->tch_mode = GSM48_CMODE_SPEECH_V1; + break; + case RSL_CMOD_SP_GSM2: + lchan->tch_mode = GSM48_CMODE_SPEECH_EFR; + break; + case RSL_CMOD_SP_GSM3: + lchan->tch_mode = GSM48_CMODE_SPEECH_AMR; + break; + case RSL_CMOD_SP_NT_14k5: + lchan->tch_mode = GSM48_CMODE_DATA_14k5; + break; + case RSL_CMOD_SP_NT_12k0: + lchan->tch_mode = GSM48_CMODE_DATA_12k0; + break; + case RSL_CMOD_SP_NT_6k0: + lchan->tch_mode = GSM48_CMODE_DATA_6k0; + break; + } +} + + +/* + * support + */ + +/* Is this channel number for a dedicated channel (true) or not (false) */ +static bool chan_nr_is_dchan(uint8_t chan_nr) +{ + /* See TS 48.058 9.3.1 + Osmocom extension for RSL_CHAN_OSMO_PDCH */ + if ((chan_nr & 0xc0) == 0x80) + return false; + else + return true; +} + +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 NULL; + } + return lchan; +} + +static struct msgb *rsl_msgb_alloc(int hdr_size) +{ + struct msgb *nmsg; + + hdr_size += sizeof(struct ipaccess_head); + + nmsg = msgb_alloc_headroom(600+hdr_size, hdr_size, "RSL"); + if (!nmsg) + return NULL; + nmsg->l3h = nmsg->data; + return nmsg; +} + +static void rsl_trx_push_hdr(struct msgb *msg, uint8_t msg_type) +{ + struct abis_rsl_common_hdr *th; + + th = (struct abis_rsl_common_hdr *) msgb_push(msg, sizeof(*th)); + th->msg_discr = ABIS_RSL_MDISC_TRX; + th->msg_type = msg_type; +} + +static void rsl_cch_push_hdr(struct msgb *msg, uint8_t msg_type, uint8_t chan_nr) +{ + struct abis_rsl_cchan_hdr *cch; + + cch = (struct abis_rsl_cchan_hdr *) msgb_push(msg, sizeof(*cch)); + cch->c.msg_discr = ABIS_RSL_MDISC_COM_CHAN; + cch->c.msg_type = msg_type; + cch->ie_chan = RSL_IE_CHAN_NR; + cch->chan_nr = chan_nr; +} + +static void rsl_dch_push_hdr(struct msgb *msg, uint8_t msg_type, uint8_t chan_nr) +{ + struct abis_rsl_dchan_hdr *dch; + + dch = (struct abis_rsl_dchan_hdr *) msgb_push(msg, sizeof(*dch)); + dch->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN; + dch->c.msg_type = msg_type; + dch->ie_chan = RSL_IE_CHAN_NR; + dch->chan_nr = chan_nr; +} + +static void rsl_ipa_push_hdr(struct msgb *msg, uint8_t msg_type, uint8_t chan_nr) +{ + struct abis_rsl_dchan_hdr *dch; + + dch = (struct abis_rsl_dchan_hdr *) msgb_push(msg, sizeof(*dch)); + dch->c.msg_discr = ABIS_RSL_MDISC_IPACCESS; + dch->c.msg_type = msg_type; + dch->ie_chan = RSL_IE_CHAN_NR; + dch->chan_nr = chan_nr; +} + +/* + * TRX related messages + */ + +/* 8.6.4 sending ERROR REPORT */ +static int rsl_tx_error_report(struct gsm_bts_trx *trx, uint8_t cause, const uint8_t *chan_nr, + const uint8_t *link_id, const struct msgb *orig_msg) +{ + unsigned int len = sizeof(struct abis_rsl_common_hdr); + struct msgb *nmsg; + + LOGP(DRSL, LOGL_NOTICE, "Tx RSL Error Report: cause = 0x%02x\n", cause); + + if (orig_msg) + len += 2 + 3+msgb_l2len(orig_msg); /* chan_nr + TLV(orig_msg) */ + if (chan_nr) + len += 2; + if (link_id) + len += 2; + + nmsg = rsl_msgb_alloc(len); + if (!nmsg) + return -ENOMEM; + msgb_tlv_put(nmsg, RSL_IE_CAUSE, 1, &cause); + if (orig_msg && msgb_l2len(orig_msg) >= sizeof(struct abis_rsl_common_hdr)) { + struct abis_rsl_common_hdr *ch = (struct abis_rsl_common_hdr *) msgb_l2(orig_msg); + msgb_tv_put(nmsg, RSL_IE_MSG_ID, ch->msg_type); + } + if (chan_nr) + msgb_tv_put(nmsg, RSL_IE_CHAN_NR, *chan_nr); + if (link_id) + msgb_tv_put(nmsg, RSL_IE_LINK_IDENT, *link_id); + if (orig_msg) + msgb_tlv_put(nmsg, RSL_IE_ERR_MSG, msgb_l2len(orig_msg), msgb_l2(orig_msg)); + + rsl_trx_push_hdr(nmsg, RSL_MT_ERROR_REPORT); + nmsg->trx = trx; + + return abis_bts_rsl_sendmsg(nmsg); +} + +/* 8.6.1 sending RF RESOURCE INDICATION */ +int rsl_tx_rf_res(struct gsm_bts_trx *trx) +{ + struct msgb *nmsg; + + LOGP(DRSL, LOGL_INFO, "Tx RSL RF RESource INDication\n"); + + nmsg = rsl_msgb_alloc(sizeof(struct abis_rsl_common_hdr)); + if (!nmsg) + return -ENOMEM; + // FIXME: add interference levels of TRX + msgb_tlv_put(nmsg, RSL_IE_RESOURCE_INFO, 0, NULL); + rsl_trx_push_hdr(nmsg, RSL_MT_RF_RES_IND); + nmsg->trx = trx; + + return abis_bts_rsl_sendmsg(nmsg); +} + +/* + * common channel releated messages + */ + +/* 8.5.1 BCCH INFOrmation is received */ +static int rsl_rx_bcch_info(struct gsm_bts_trx *trx, struct msgb *msg) +{ + struct gsm_bts *bts = trx->bts; + struct tlv_parsed tp; + uint8_t rsl_si, count; + enum osmo_sysinfo_type osmo_si; + struct gsm48_system_information_type_2quater *si2q; + struct bitvec bv; + rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); + + /* 9.3.30 System Info Type */ + if (!TLVP_PRESENT(&tp, RSL_IE_SYSINFO_TYPE)) + return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR, NULL, NULL, msg); + + rsl_si = *TLVP_VAL(&tp, RSL_IE_SYSINFO_TYPE); + if (OSMO_IN_ARRAY(rsl_si, rsl_sacch_sitypes)) + return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, NULL, NULL, msg); + + osmo_si = osmo_rsl2sitype(rsl_si); + if (osmo_si == SYSINFO_TYPE_NONE) { + LOGP(DRSL, LOGL_NOTICE, " Rx RSL SI 0x%02x not supported.\n", rsl_si); + return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, NULL, NULL, msg); + } + /* 9.3.39 Full BCCH Information */ + if (TLVP_PRESENT(&tp, RSL_IE_FULL_BCCH_INFO)) { + uint8_t len = TLVP_LEN(&tp, RSL_IE_FULL_BCCH_INFO); + if (len > sizeof(sysinfo_buf_t)) { + LOGP(DRSL, LOGL_ERROR, "Truncating received Full BCCH Info (%u -> %zu) for SI%s\n", + len, sizeof(sysinfo_buf_t), get_value_string(osmo_sitype_strs, osmo_si)); + len = sizeof(sysinfo_buf_t); + } + + LOGP(DRSL, LOGL_INFO, " Rx RSL BCCH INFO (SI%s, %u bytes)\n", + get_value_string(osmo_sitype_strs, osmo_si), len); + + if (SYSINFO_TYPE_2quater == osmo_si) { + si2q = (struct gsm48_system_information_type_2quater *) TLVP_VAL(&tp, RSL_IE_FULL_BCCH_INFO); + bv.data = si2q->rest_octets; + bv.data_len = GSM_MACBLOCK_LEN; + bv.cur_bit = 3; + bts->si2q_index = (uint8_t) bitvec_get_uint(&bv, 4); + + count = (uint8_t) bitvec_get_uint(&bv, 4); + if (bts->si2q_count && bts->si2q_count != count) { + LOGP(DRSL, LOGL_NOTICE, " Rx RSL SI2quater count updated: %u -> %d\n", + bts->si2q_count, count); + } + + bts->si2q_count = count; + if (bts->si2q_index > bts->si2q_count) { + LOGP(DRSL, LOGL_ERROR, " Rx RSL SI2quater with index %u > count %u\n", + bts->si2q_index, bts->si2q_count); + return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, NULL, NULL, msg); + } + + if (bts->si2q_index > SI2Q_MAX_NUM || bts->si2q_count > SI2Q_MAX_NUM) { + LOGP(DRSL, LOGL_ERROR, " Rx RSL SI2quater with impossible parameters: index %u, count %u" + "should be <= %u\n", bts->si2q_index, bts->si2q_count, SI2Q_MAX_NUM); + return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, NULL, NULL, msg); + } + + memset(GSM_BTS_SI2Q(bts, bts->si2q_index), GSM_MACBLOCK_PADDING, sizeof(sysinfo_buf_t)); + memcpy(GSM_BTS_SI2Q(bts, bts->si2q_index), TLVP_VAL(&tp, RSL_IE_FULL_BCCH_INFO), len); + } else { + memset(bts->si_buf[osmo_si], GSM_MACBLOCK_PADDING, sizeof(sysinfo_buf_t)); + memcpy(bts->si_buf[osmo_si], TLVP_VAL(&tp, RSL_IE_FULL_BCCH_INFO), len); + } + + bts->si_valid |= (1 << osmo_si); + + if (SYSINFO_TYPE_3 == osmo_si && trx->nr == 0 && + num_agch(trx, "RSL") != 1) { + lchan_deactivate(&trx->bts->c0->ts[0].lchan[CCCH_LCHAN]); + /* will be reactivated by sapi_deactivate_cb() */ + trx->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind = + LCHAN_REL_ACT_REACT; + } + + if (SYSINFO_TYPE_13 == osmo_si) + pcu_tx_si13(trx->bts, true); + + } else if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) { + uint16_t len = TLVP_LEN(&tp, RSL_IE_L3_INFO); + if (len > sizeof(sysinfo_buf_t)) + len = sizeof(sysinfo_buf_t); + bts->si_valid |= (1 << osmo_si); + memset(bts->si_buf[osmo_si], 0x2b, sizeof(sysinfo_buf_t)); + memcpy(bts->si_buf[osmo_si], + TLVP_VAL(&tp, RSL_IE_L3_INFO), len); + LOGP(DRSL, LOGL_INFO, " Rx RSL BCCH INFO (SI%s)\n", + get_value_string(osmo_sitype_strs, osmo_si)); + } else { + bts->si_valid &= ~(1 << osmo_si); + LOGP(DRSL, LOGL_INFO, " RX RSL Disabling BCCH INFO (SI%s)\n", + get_value_string(osmo_sitype_strs, osmo_si)); + if (SYSINFO_TYPE_13 == osmo_si) + pcu_tx_si13(trx->bts, false); + } + osmo_signal_dispatch(SS_GLOBAL, S_NEW_SYSINFO, bts); + + return 0; +} + +/* 8.5.2 CCCH Load Indication (PCH) */ +int rsl_tx_ccch_load_ind_pch(struct gsm_bts *bts, uint16_t paging_avail) +{ + struct msgb *msg; + + msg = rsl_msgb_alloc(sizeof(struct abis_rsl_cchan_hdr)); + if (!msg) + return -ENOMEM; + rsl_cch_push_hdr(msg, RSL_MT_CCCH_LOAD_IND, RSL_CHAN_PCH_AGCH); + msgb_tv16_put(msg, RSL_IE_PAGING_LOAD, paging_avail); + msg->trx = bts->c0; + + return abis_bts_rsl_sendmsg(msg); +} + +/* 8.5.2 CCCH Load Indication (RACH) */ +int rsl_tx_ccch_load_ind_rach(struct gsm_bts *bts, uint16_t total, + uint16_t busy, uint16_t access) +{ + struct msgb *msg; + + msg = rsl_msgb_alloc(sizeof(struct abis_rsl_cchan_hdr)); + if (!msg) + return -ENOMEM; + rsl_cch_push_hdr(msg, RSL_MT_CCCH_LOAD_IND, RSL_CHAN_RACH); + /* tag and length */ + msgb_tv_put(msg, RSL_IE_RACH_LOAD, 6); + /* content of the IE */ + msgb_put_u16(msg, total); + msgb_put_u16(msg, busy); + msgb_put_u16(msg, access); + + msg->trx = bts->c0; + + return abis_bts_rsl_sendmsg(msg); +} + +/* 8.5.4 DELETE INDICATION */ +static int rsl_tx_delete_ind(struct gsm_bts *bts, const uint8_t *ia, uint8_t ia_len) +{ + struct msgb *msg; + + msg = rsl_msgb_alloc(sizeof(struct abis_rsl_cchan_hdr)); + if (!msg) + return -ENOMEM; + rsl_cch_push_hdr(msg, RSL_MT_DELETE_IND, RSL_CHAN_PCH_AGCH); + msgb_tlv_put(msg, RSL_IE_FULL_IMM_ASS_INFO, ia_len, ia); + msg->trx = bts->c0; + + return abis_bts_rsl_sendmsg(msg); +} + +/* 8.5.5 PAGING COMMAND */ +static int rsl_rx_paging_cmd(struct gsm_bts_trx *trx, struct msgb *msg) +{ + struct tlv_parsed tp; + struct gsm_bts *bts = trx->bts; + uint8_t chan_needed = 0, paging_group; + const uint8_t *identity_lv; + int rc; + + rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); + + if (!TLVP_PRESENT(&tp, RSL_IE_PAGING_GROUP) || + !TLVP_PRESENT(&tp, RSL_IE_MS_IDENTITY)) + return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR, NULL, NULL, msg); + + paging_group = *TLVP_VAL(&tp, RSL_IE_PAGING_GROUP); + identity_lv = TLVP_VAL(&tp, RSL_IE_MS_IDENTITY)-1; + + if (TLVP_PRES_LEN(&tp, RSL_IE_CHAN_NEEDED, 1)) + chan_needed = *TLVP_VAL(&tp, RSL_IE_CHAN_NEEDED); + + rc = paging_add_identity(bts->paging_state, paging_group, identity_lv, chan_needed); + if (rc < 0) { + /* FIXME: notfiy the BSC on other errors? */ + if (rc == -ENOSPC) + oml_fail_rep(OSMO_EVT_MIN_PAG_TAB_FULL, + "BTS paging table is full"); + } + + pcu_tx_pag_req(identity_lv, chan_needed); + + return 0; +} + +/* 8.5.8 SMS BROADCAST COMMAND */ +static int rsl_rx_sms_bcast_cmd(struct gsm_bts_trx *trx, struct msgb *msg) +{ + struct tlv_parsed tp; + struct rsl_ie_cb_cmd_type *cb_cmd_type; + + rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); + + if (!TLVP_PRESENT(&tp, RSL_IE_CB_CMD_TYPE) || + !TLVP_PRESENT(&tp, RSL_IE_SMSCB_MSG)) + return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR, NULL, NULL, msg); + + cb_cmd_type = (struct rsl_ie_cb_cmd_type *) + TLVP_VAL(&tp, RSL_IE_CB_CMD_TYPE); + + return bts_process_smscb_cmd(trx->bts, *cb_cmd_type, + TLVP_LEN(&tp, RSL_IE_SMSCB_MSG), + TLVP_VAL(&tp, RSL_IE_SMSCB_MSG)); +} + +/*! Prefix a given SACCH frame with a L2/LAPDm UI header and store it in given output buffer. + * \param[out] buf Output buffer, must be caller-allocated and hold at least len + 2 or sizeof(sysinfo_buf_t) bytes + * \param[out] valid pointer to bit-mask of 'valid' System information types + * \param[in] current input data (L3 without L2/L1 header) + * \param[in] osmo_si Sytstem Infrormation Type (SYSINFO_TYPE_*) + * \param[in] len length of \a current in octets */ +static inline void lapdm_ui_prefix(uint8_t *buf, uint32_t *valid, const uint8_t *current, uint8_t osmo_si, uint16_t len) +{ + /* We have to pre-fix with the two-byte LAPDM UI header */ + if (len > sizeof(sysinfo_buf_t) - 2) { + LOGP(DRSL, LOGL_ERROR, "Truncating received SI%s (%u -> %zu) to prepend LAPDM UI header (2 bytes)\n", + get_value_string(osmo_sitype_strs, osmo_si), len, sizeof(sysinfo_buf_t) - 2); + len = sizeof(sysinfo_buf_t) - 2; + } + + (*valid) |= (1 << osmo_si); + buf[0] = 0x03; /* C/R + EA */ + buf[1] = 0x03; /* UI frame */ + + memset(buf + 2, GSM_MACBLOCK_PADDING, sizeof(sysinfo_buf_t) - 2); + memcpy(buf + 2, current, len); +} + +/*! Prefix a given SACCH frame with a L2/LAPDm UI header and store it in given BTS SACCH buffer + * \param[out] bts BTS in whose System Information State we shall store + * \param[in] current input data (L3 without L2/L1 header) + * \param[in] osmo_si Sytstem Infrormation Type (SYSINFO_TYPE_*) + * \param[in] len length of \a current in octets */ +static inline void lapdm_ui_prefix_bts(struct gsm_bts *bts, const uint8_t *current, uint8_t osmo_si, uint16_t len) +{ + lapdm_ui_prefix(GSM_BTS_SI(bts, osmo_si), &bts->si_valid, current, osmo_si, len); +} + +/*! Prefix a given SACCH frame with a L2/LAPDm UI header and store it in given lchan SACCH buffer + * \param[out] lchan Logical Channel in whose System Information State we shall store + * \param[in] current input data (L3 without L2/L1 header) + * \param[in] osmo_si Sytstem Infrormation Type (SYSINFO_TYPE_*) + * \param[in] len length of \a current in octets */ +static inline void lapdm_ui_prefix_lchan(struct gsm_lchan *lchan, const uint8_t *current, uint8_t osmo_si, uint16_t len) +{ + lapdm_ui_prefix(GSM_LCHAN_SI(lchan, osmo_si), &lchan->si.valid, current, osmo_si, len); +} + +/* 8.6.2 SACCH FILLING */ +static int rsl_rx_sacch_fill(struct gsm_bts_trx *trx, struct msgb *msg) +{ + struct gsm_bts *bts = trx->bts; + struct tlv_parsed tp; + uint8_t rsl_si; + enum osmo_sysinfo_type osmo_si; + + rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); + + /* 9.3.30 System Info Type */ + if (!TLVP_PRESENT(&tp, RSL_IE_SYSINFO_TYPE)) + return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR, NULL, NULL, msg); + + rsl_si = *TLVP_VAL(&tp, RSL_IE_SYSINFO_TYPE); + if (!OSMO_IN_ARRAY(rsl_si, rsl_sacch_sitypes)) + return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, NULL, NULL, msg); + + osmo_si = osmo_rsl2sitype(rsl_si); + if (osmo_si == SYSINFO_TYPE_NONE) { + LOGP(DRSL, LOGL_NOTICE, " Rx SACCH SI 0x%02x not supported.\n", rsl_si); + return rsl_tx_error_report(trx, RSL_ERR_IE_CONTENT, NULL, NULL, msg); + } + if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) { + uint16_t len = TLVP_LEN(&tp, RSL_IE_L3_INFO); + lapdm_ui_prefix_bts(bts, TLVP_VAL(&tp, RSL_IE_L3_INFO), osmo_si, len); + + LOGP(DRSL, LOGL_INFO, " Rx RSL SACCH FILLING (SI%s, %u bytes)\n", + get_value_string(osmo_sitype_strs, osmo_si), len); + } else { + bts->si_valid &= ~(1 << osmo_si); + LOGP(DRSL, LOGL_INFO, " Rx RSL Disabling SACCH FILLING (SI%s)\n", + get_value_string(osmo_sitype_strs, osmo_si)); + } + osmo_signal_dispatch(SS_GLOBAL, S_NEW_SYSINFO, bts); + + return 0; + +} + +/* 8.5.6 IMMEDIATE ASSIGN COMMAND is received */ +static int rsl_rx_imm_ass(struct gsm_bts_trx *trx, struct msgb *msg) +{ + struct tlv_parsed tp; + + rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); + + if (!TLVP_PRESENT(&tp, RSL_IE_FULL_IMM_ASS_INFO)) + return rsl_tx_error_report(trx, RSL_ERR_MAND_IE_ERROR, NULL, NULL, msg); + + rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_AGCH_RCVD); + + /* cut down msg to the 04.08 RR part */ + msg->l3h = (uint8_t *) TLVP_VAL(&tp, RSL_IE_FULL_IMM_ASS_INFO); + msg->data = msg->l3h; + msg->l2h = NULL; + msg->len = TLVP_LEN(&tp, RSL_IE_FULL_IMM_ASS_INFO); + + /* put into the AGCH queue of the BTS */ + if (bts_agch_enqueue(trx->bts, msg) < 0) { + /* if there is no space in the queue: send DELETE IND */ + rsl_tx_delete_ind(trx->bts, TLVP_VAL(&tp, RSL_IE_FULL_IMM_ASS_INFO), + TLVP_LEN(&tp, RSL_IE_FULL_IMM_ASS_INFO)); + rate_ctr_inc2(trx->bts->ctrs, BTS_CTR_AGCH_DELETED); + msgb_free(msg); + } + + /* return 1 means: don't msgb_free() the msg */ + return 1; +} + +/* + * dedicated channel related messages + */ + +/* Send an RF CHANnel RELease ACKnowledge with the given chan_nr. This chan_nr may mismatch the current + * lchan state, if we received a CHANnel RELease for an already released channel, and we're just acking + * what we got without taking any action. */ +static int tx_rf_rel_ack(struct gsm_lchan *lchan, uint8_t chan_nr) +{ + struct msgb *msg; + + msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); + if (!msg) + return -ENOMEM; + + rsl_dch_push_hdr(msg, RSL_MT_RF_CHAN_REL_ACK, chan_nr); + msg->trx = lchan->ts->trx; + + return abis_bts_rsl_sendmsg(msg); +} + +/* 8.4.19 sending RF CHANnel RELease ACKnowledge */ +int rsl_tx_rf_rel_ack(struct gsm_lchan *lchan) +{ + uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + bool send_rel_ack; + + switch (lchan->rel_act_kind) { + case LCHAN_REL_ACT_RSL: + send_rel_ack = true; + break; + + case LCHAN_REL_ACT_PCU: + switch (lchan->ts->pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + if (lchan->ts->dyn.pchan_is != GSM_PCHAN_PDCH) { + LOGP(DRSL, LOGL_ERROR, + "%s (ss=%d) PDCH release: not in PDCH mode\n", + gsm_ts_and_pchan_name(lchan->ts), lchan->nr); + /* well, what to do about it ... carry on and hope it's fine. */ + } + /* remember the fact that the TS is now released */ + lchan->ts->dyn.pchan_is = GSM_PCHAN_NONE; + /* Continue to ack the release below. (This is a non-standard rel ack invented + * specifically for GSM_PCHAN_TCH_F_TCH_H_PDCH). */ + send_rel_ack = true; + break; + case GSM_PCHAN_TCH_F_PDCH: + /* GSM_PCHAN_TCH_F_PDCH, does not require a rel ack. The caller + * l1sap_info_rel_cnf() will continue with bts_model_ts_disconnect(). */ + send_rel_ack = false; + break; + default: + LOGP(DRSL, LOGL_ERROR, "%s PCU rel ack for unexpected lchan kind\n", + gsm_lchan_name(lchan)); + /* Release certainly was not requested by the BSC via RSL, so don't ack. */ + send_rel_ack = false; + break; + } + break; + + default: + /* A rel that was not requested by the BSC via RSL, hence not sending a rel ack to the + * BSC. */ + send_rel_ack = false; + break; + } + + if (!send_rel_ack) { + LOGP(DRSL, LOGL_NOTICE, "%s not sending REL ACK\n", + gsm_lchan_name(lchan)); + return 0; + } + + LOGP(DRSL, LOGL_NOTICE, "%s (ss=%d) %s Tx CHAN REL ACK\n", + gsm_ts_and_pchan_name(lchan->ts), lchan->nr, + gsm_lchant_name(lchan->type)); + + /* + * Free the LAPDm resources now that the BTS + * has released all the resources. + */ + lapdm_channel_exit(&lchan->lapdm_ch); + + return tx_rf_rel_ack(lchan, chan_nr); +} + +/* 8.4.2 sending CHANnel ACTIVation ACKnowledge */ +static int rsl_tx_chan_act_ack(struct gsm_lchan *lchan) +{ + struct gsm_time *gtime = get_time(lchan->ts->trx->bts); + struct msgb *msg; + uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + uint8_t ie[2]; + + LOGP(DRSL, LOGL_NOTICE, "%s (ss=%d) %s Tx CHAN ACT ACK\n", + gsm_ts_and_pchan_name(lchan->ts), lchan->nr, + gsm_lchant_name(lchan->type)); + + msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); + if (!msg) + return -ENOMEM; + + gsm48_gen_starting_time(ie, gtime); + msgb_tv_fixed_put(msg, RSL_IE_FRAME_NUMBER, 2, ie); + rsl_dch_push_hdr(msg, RSL_MT_CHAN_ACTIV_ACK, chan_nr); + msg->trx = lchan->ts->trx; + + /* since activation was successful, do some lchan initialization */ + lchan->meas.res_nr = 0; + + return abis_bts_rsl_sendmsg(msg); +} + +/* 8.4.7 sending HANDOver DETection */ +int rsl_tx_hando_det(struct gsm_lchan *lchan, uint8_t *ho_delay) +{ + struct msgb *msg; + uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + + LOGP(DRSL, LOGL_INFO, "Sending HANDOver DETect\n"); + + msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); + if (!msg) + return -ENOMEM; + + /* 9.3.17 Access Delay */ + if (ho_delay) + msgb_tv_put(msg, RSL_IE_ACCESS_DELAY, *ho_delay); + + rsl_dch_push_hdr(msg, RSL_MT_HANDO_DET, chan_nr); + msg->trx = lchan->ts->trx; + + return abis_bts_rsl_sendmsg(msg); +} + +/* 8.4.3 sending CHANnel ACTIVation Negative ACK */ +static int _rsl_tx_chan_act_nack(struct gsm_bts_trx *trx, uint8_t chan_nr, uint8_t cause, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + + if (lchan) + LOGP(DRSL, LOGL_NOTICE, "%s: ", gsm_lchan_name(lchan)); + else + LOGP(DRSL, LOGL_NOTICE, "0x%02x: ", chan_nr); + LOGPC(DRSL, LOGL_NOTICE, "Sending Channel Activated NACK: cause = 0x%02x\n", cause); + + msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); + if (!msg) + return -ENOMEM; + + /* 9.3.26 Cause */ + msgb_tlv_put(msg, RSL_IE_CAUSE, 1, &cause); + rsl_dch_push_hdr(msg, RSL_MT_CHAN_ACTIV_NACK, chan_nr); + msg->trx = trx; + + return abis_bts_rsl_sendmsg(msg); +} +static int rsl_tx_chan_act_nack(struct gsm_lchan *lchan, uint8_t cause) { + return _rsl_tx_chan_act_nack(lchan->ts->trx, gsm_lchan2chan_nr(lchan), cause, lchan); +} + +/* Send an RSL Channel Activation Ack if cause is zero, a Nack otherwise. */ +int rsl_tx_chan_act_acknack(struct gsm_lchan *lchan, uint8_t cause) +{ + if (lchan->rel_act_kind != LCHAN_REL_ACT_RSL) { + LOGP(DRSL, LOGL_NOTICE, "%s not sending CHAN ACT %s\n", + gsm_lchan_name(lchan), cause ? "NACK" : "ACK"); + return 0; + } + + if (cause) + return rsl_tx_chan_act_nack(lchan, cause); + return rsl_tx_chan_act_ack(lchan); +} + +/* 8.4.4 sending CONNection FAILure */ +int rsl_tx_conn_fail(struct gsm_lchan *lchan, uint8_t cause) +{ + struct msgb *msg; + uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + + LOGP(DRSL, LOGL_NOTICE, + "%s Sending Connection Failure: cause = 0x%02x\n", + gsm_lchan_name(lchan), cause); + + msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); + if (!msg) + return -ENOMEM; + + /* 9.3.26 Cause */ + msgb_tlv_put(msg, RSL_IE_CAUSE, 1, &cause); + rsl_dch_push_hdr(msg, RSL_MT_CONN_FAIL, chan_nr); + msg->trx = lchan->ts->trx; + + return abis_bts_rsl_sendmsg(msg); +} + +/* 8.5.3 sending CHANnel ReQuireD */ +int rsl_tx_chan_rqd(struct gsm_bts_trx *trx, struct gsm_time *gtime, + uint8_t ra, uint8_t acc_delay) +{ + struct msgb *nmsg; + uint8_t payload[3]; + + LOGP(DRSL, LOGL_NOTICE, "Sending Channel Required\n"); + + nmsg = rsl_msgb_alloc(sizeof(struct abis_rsl_cchan_hdr)); + if (!nmsg) + return -ENOMEM; + + /* 9.3.19 Request Reference */ + payload[0] = ra; + gsm48_gen_starting_time(payload+1, gtime); + msgb_tv_fixed_put(nmsg, RSL_IE_REQ_REFERENCE, 3, payload); + + /* 9.3.17 Access Delay */ + msgb_tv_put(nmsg, RSL_IE_ACCESS_DELAY, acc_delay); + + rsl_cch_push_hdr(nmsg, RSL_MT_CHAN_RQD, 0x88); // FIXME + nmsg->trx = trx; + + return abis_bts_rsl_sendmsg(nmsg); +} + +/* copy the SACCH related sysinfo from BTS global buffer to lchan specific buffer */ +static void copy_sacch_si_to_lchan(struct gsm_lchan *lchan) +{ + struct gsm_bts *bts = lchan->ts->trx->bts; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rsl_sacch_sitypes); i++) { + uint8_t rsl_si = rsl_sacch_sitypes[i]; + int osmo_si = osmo_rsl2sitype(rsl_si); + uint32_t osmo_si_shifted = (1 << osmo_si); + osmo_static_assert(_MAX_SYSINFO_TYPE <= sizeof(osmo_si_shifted) * 8, + si_enum_vals_fit_in_bit_mask); + + if (osmo_si == SYSINFO_TYPE_NONE) + continue; + if (!(bts->si_valid & osmo_si_shifted)) { + lchan->si.valid &= ~osmo_si_shifted; + continue; + } + lchan->si.valid |= osmo_si_shifted; + memcpy(GSM_LCHAN_SI(lchan, osmo_si), GSM_BTS_SI(bts, osmo_si), sizeof(sysinfo_buf_t)); + } +} + + +static int encr_info2lchan(struct gsm_lchan *lchan, + const uint8_t *val, uint8_t len) +{ + int rc; + struct gsm_bts *bts = lchan->ts->trx->bts; + + /* check if the encryption algorithm sent by BSC is supported! */ + rc = bts_supports_cipher(bts, *val); + if (rc != 1) { + LOGP(DRSL, LOGL_ERROR, "%s: BTS doesn't support cipher 0x%02x\n", + gsm_lchan_name(lchan), *val); + return -EINVAL; + } + + /* length can be '1' in case of no ciphering */ + if (len < 1) { + LOGP(DRSL, LOGL_ERROR, "%s: Encryption Info cannot have len=%d\n", + gsm_lchan_name(lchan), len); + return -EINVAL; + } + + lchan->encr.alg_id = *val++; + lchan->encr.key_len = len -1; + if (lchan->encr.key_len > sizeof(lchan->encr.key)) + lchan->encr.key_len = sizeof(lchan->encr.key); + memcpy(lchan->encr.key, val, lchan->encr.key_len); + DEBUGP(DRSL, "%s: Setting lchan cipher algorithm 0x%02x\n", + gsm_lchan_name(lchan), lchan->encr.alg_id); + + return 0; +} + +/* Make sure no state from TCH use remains. */ +static void clear_lchan_for_pdch_activ(struct gsm_lchan *lchan) +{ + /* These values don't apply to PDCH, just clear them. Particularly the encryption must be + * cleared, or we would enable encryption on PDCH with parameters remaining from the TCH. */ + lchan->ms_power = ms_pwr_ctl_lvl(lchan->ts->trx->bts->band, 0); + lchan->ms_power_ctrl.current = lchan->ms_power; + lchan->ms_power_ctrl.fixed = 0; + lchan->rsl_cmode = 0; + lchan->tch_mode = 0; + memset(&lchan->encr, 0, sizeof(lchan->encr)); + memset(&lchan->ho, 0, sizeof(lchan->ho)); + lchan->bs_power = 0; + lchan->ms_power = 0; + memset(&lchan->ms_power_ctrl, 0, sizeof(lchan->ms_power_ctrl)); + lchan->rqd_ta = 0; + copy_sacch_si_to_lchan(lchan); + memset(&lchan->tch, 0, sizeof(lchan->tch)); +} + +/*! + * Store the CHAN_ACTIV msg, connect the L1 timeslot in the proper type and + * then invoke rsl_rx_chan_activ() with msg. + */ +static int dyn_ts_l1_reconnect(struct gsm_bts_trx_ts *ts, struct msgb *msg) +{ + DEBUGP(DRSL, "%s dyn_ts_l1_reconnect\n", gsm_ts_and_pchan_name(ts)); + + switch (ts->dyn.pchan_want) { + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_H: + break; + case GSM_PCHAN_PDCH: + /* Only the first lchan matters for PDCH */ + clear_lchan_for_pdch_activ(ts->lchan); + break; + default: + LOGP(DRSL, LOGL_ERROR, + "%s Cannot reconnect as pchan %s\n", + gsm_ts_and_pchan_name(ts), + gsm_pchan_name(ts->dyn.pchan_want)); + return -EINVAL; + } + + /* We will feed this back to rsl_rx_chan_activ() later */ + ts->dyn.pending_chan_activ = msg; + + /* Disconnect, continue connecting from cb_ts_disconnected(). */ + DEBUGP(DRSL, "%s Disconnect\n", gsm_ts_and_pchan_name(ts)); + return bts_model_ts_disconnect(ts); +} + +static enum gsm_phys_chan_config dyn_pchan_from_chan_nr(uint8_t chan_nr) +{ + uint8_t cbits = chan_nr & RSL_CHAN_NR_MASK; + switch (cbits) { + case RSL_CHAN_Bm_ACCHs: + return GSM_PCHAN_TCH_F; + case RSL_CHAN_Lm_ACCHs: + case (RSL_CHAN_Lm_ACCHs + RSL_CHAN_NR_1): + return GSM_PCHAN_TCH_H; + case RSL_CHAN_OSMO_PDCH: + return GSM_PCHAN_PDCH; + default: + LOGP(DRSL, LOGL_ERROR, + "chan nr 0x%x not covered by dyn_pchan_from_chan_nr()\n", + chan_nr); + return GSM_PCHAN_UNKNOWN; + } +} + +/* 8.4.1 CHANnel ACTIVation is received */ +static int rsl_rx_chan_activ(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dch = msgb_l2(msg); + struct gsm_lchan *lchan = msg->lchan; + struct gsm_bts_trx_ts *ts = lchan->ts; + struct rsl_ie_chan_mode *cm; + struct tlv_parsed tp; + uint8_t type; + int rc; + + if (lchan->state != LCHAN_S_NONE) { + LOGP(DRSL, LOGL_ERROR, + "%s: error: lchan is not available, but in state: %s.\n", + gsm_lchan_name(lchan), gsm_lchans_name(lchan->state)); + return rsl_tx_chan_act_nack(lchan, RSL_ERR_EQUIPMENT_FAIL); + } + + if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + ts->dyn.pchan_want = dyn_pchan_from_chan_nr(dch->chan_nr); + DEBUGP(DRSL, "%s rx chan activ\n", gsm_ts_and_pchan_name(ts)); + + if (ts->dyn.pchan_is != ts->dyn.pchan_want) { + /* + * The phy has the timeslot connected in a different + * mode than this activation needs it to be. + * Re-connect, then come back to rsl_rx_chan_activ(). + */ + rc = dyn_ts_l1_reconnect(ts, msg); + if (rc) + return rsl_tx_chan_act_nack(lchan, RSL_ERR_NORMAL_UNSPEC); + /* indicate that the msgb should not be freed. */ + return 1; + } + } + + LOGP(DRSL, LOGL_DEBUG, "%s: rx Channel Activation in state: %s.\n", + gsm_lchan_name(lchan), gsm_lchans_name(lchan->state)); + + /* Initialize channel defaults */ + lchan->ms_power = ms_pwr_ctl_lvl(lchan->ts->trx->bts->band, 0); + lchan->ms_power_ctrl.current = lchan->ms_power; + lchan->ms_power_ctrl.fixed = 0; + + rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); + + /* 9.3.3 Activation Type */ + if (!TLVP_PRESENT(&tp, RSL_IE_ACT_TYPE)) { + LOGP(DRSL, LOGL_NOTICE, "missing Activation Type\n"); + return rsl_tx_chan_act_nack(lchan, RSL_ERR_MAND_IE_ERROR); + } + type = *TLVP_VAL(&tp, RSL_IE_ACT_TYPE); + + /* 9.3.6 Channel Mode */ + if (type != RSL_ACT_OSMO_PDCH) { + if (!TLVP_PRESENT(&tp, RSL_IE_CHAN_MODE)) { + LOGP(DRSL, LOGL_NOTICE, "missing Channel Mode\n"); + return rsl_tx_chan_act_nack(lchan, RSL_ERR_MAND_IE_ERROR); + } + cm = (struct rsl_ie_chan_mode *) TLVP_VAL(&tp, RSL_IE_CHAN_MODE); + lchan_tchmode_from_cmode(lchan, cm); + } + + /* 9.3.7 Encryption Information */ + if (TLVP_PRESENT(&tp, RSL_IE_ENCR_INFO)) { + uint8_t len = TLVP_LEN(&tp, RSL_IE_ENCR_INFO); + const uint8_t *val = TLVP_VAL(&tp, RSL_IE_ENCR_INFO); + + if (encr_info2lchan(lchan, val, len) < 0) { + rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, NULL, msg); + return rsl_tx_chan_act_acknack(lchan, RSL_ERR_ENCR_UNIMPL); + } + } else + memset(&lchan->encr, 0, sizeof(lchan->encr)); + + /* 9.3.9 Handover Reference */ + if ((type == RSL_ACT_INTER_ASYNC || + type == RSL_ACT_INTER_SYNC) && + TLVP_PRES_LEN(&tp, RSL_IE_HANDO_REF, 1)) { + lchan->ho.active = HANDOVER_ENABLED; + lchan->ho.ref = *TLVP_VAL(&tp, RSL_IE_HANDO_REF); + } + + /* 9.3.4 BS Power */ + if (TLVP_PRES_LEN(&tp, RSL_IE_BS_POWER, 1)) + lchan->bs_power = *TLVP_VAL(&tp, RSL_IE_BS_POWER); + /* 9.3.13 MS Power */ + if (TLVP_PRES_LEN(&tp, RSL_IE_MS_POWER, 1)) { + lchan->ms_power = *TLVP_VAL(&tp, RSL_IE_MS_POWER); + lchan->ms_power_ctrl.current = lchan->ms_power; + lchan->ms_power_ctrl.fixed = 0; + } + /* 9.3.24 Timing Advance */ + if (TLVP_PRES_LEN(&tp, RSL_IE_TIMING_ADVANCE, 1)) + lchan->rqd_ta = *TLVP_VAL(&tp, RSL_IE_TIMING_ADVANCE); + + /* 9.3.32 BS Power Parameters */ + /* 9.3.31 MS Power Parameters */ + /* 9.3.16 Physical Context */ + + /* 9.3.29 SACCH Information */ + if (TLVP_PRESENT(&tp, RSL_IE_SACCH_INFO)) { + uint8_t tot_len = TLVP_LEN(&tp, RSL_IE_SACCH_INFO); + const uint8_t *val = TLVP_VAL(&tp, RSL_IE_SACCH_INFO); + const uint8_t *cur = val; + uint8_t num_msgs = *cur++; + unsigned int i; + for (i = 0; i < num_msgs; i++) { + uint8_t rsl_si = *cur++; + uint8_t si_len = *cur++; + uint8_t osmo_si; + + if (!OSMO_IN_ARRAY(rsl_si, rsl_sacch_sitypes)) { + rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, + &dch->chan_nr, NULL, msg); + return rsl_tx_chan_act_acknack(lchan, RSL_ERR_IE_CONTENT); + } + + osmo_si = osmo_rsl2sitype(rsl_si); + if (osmo_si == SYSINFO_TYPE_NONE) { + LOGP(DRSL, LOGL_NOTICE, " Rx SACCH SI 0x%02x not supported.\n", rsl_si); + rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, + NULL, msg); + return rsl_tx_chan_act_acknack(lchan, RSL_ERR_IE_CONTENT); + } + + lapdm_ui_prefix_lchan(lchan, cur, osmo_si, si_len); + + cur += si_len; + if (cur >= val + tot_len) { + LOGP(DRSL, LOGL_ERROR, "Error parsing SACCH INFO IE\n"); + rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, + NULL, msg); + return rsl_tx_chan_act_acknack(lchan, RSL_ERR_IE_CONTENT); + } + } + } else { + /* use standard SACCH filling of the BTS */ + copy_sacch_si_to_lchan(lchan); + } + /* 9.3.52 MultiRate Configuration */ + if (TLVP_PRESENT(&tp, RSL_IE_MR_CONFIG)) { + if (TLVP_LEN(&tp, RSL_IE_MR_CONFIG) > sizeof(lchan->mr_bts_lv) - 1) { + LOGP(DRSL, LOGL_ERROR, "Error parsing MultiRate conf IE\n"); + rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, NULL, msg); + return rsl_tx_chan_act_acknack(lchan, RSL_ERR_IE_CONTENT); + } + memcpy(lchan->mr_bts_lv, TLVP_VAL(&tp, RSL_IE_MR_CONFIG) - 1, + TLVP_LEN(&tp, RSL_IE_MR_CONFIG) + 1); + amr_parse_mr_conf(&lchan->tch.amr_mr, TLVP_VAL(&tp, RSL_IE_MR_CONFIG), + TLVP_LEN(&tp, RSL_IE_MR_CONFIG)); + amr_log_mr_conf(DRTP, LOGL_DEBUG, gsm_lchan_name(lchan), + &lchan->tch.amr_mr); + lchan->tch.last_cmr = AMR_CMR_NONE; + } + /* 9.3.53 MultiRate Control */ + /* 9.3.54 Supported Codec Types */ + + LOGP(DRSL, LOGL_INFO, " chan_nr=0x%02x type=0x%02x mode=0x%02x\n", + dch->chan_nr, type, lchan->tch_mode); + + /* Connecting PDCH on dyn TS goes via PCU instead. */ + if (ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH + && ts->dyn.pchan_want == GSM_PCHAN_PDCH) { + /* + * We ack the activation to the BSC right away, regardless of + * the PCU succeeding or not; if a dynamic timeslot fails to go + * to PDCH mode for any reason, the BSC should still be able to + * switch it back to TCH modes and should not put the time slot + * in an error state. So for operating dynamic TS, the BSC + * would not take any action if the PDCH mode failed, e.g. + * because the PCU is not yet running. Even if alerting the + * core network of broken GPRS service is desired, this only + * makes sense when the PCU has not shown up for some time. + * It's easiest to not forward activation delays to the BSC: if + * the BSC tells us to do PDCH, we do our best, and keep the + * details on the BTS and PCU level. This is kind of analogous + * to how plain PDCH TS operate. Directly call + * rsl_tx_chan_act_ack() instead of rsl_tx_chan_act_acknack() + * because we don't want/need to decide whether to drop due to + * lchan->rel_act_kind. + */ + rc = rsl_tx_chan_act_ack(lchan); + if (rc < 0) + LOGP(DRSL, LOGL_ERROR, "%s Cannot send act ack: %d\n", + gsm_ts_and_pchan_name(ts), rc); + + /* + * pcu_tx_info_ind() will pick up the ts->dyn.pchan_want. If + * the PCU is not connected yet, ignore for now; the PCU will + * catch up (and send the RSL ack) once it connects. + */ + if (pcu_connected()) { + DEBUGP(DRSL, "%s Activate via PCU\n", gsm_ts_and_pchan_name(ts)); + rc = pcu_tx_info_ind(); + } + else { + DEBUGP(DRSL, "%s Activate via PCU when PCU connects\n", + gsm_ts_and_pchan_name(ts)); + rc = 0; + } + if (rc) { + rsl_tx_error_report(msg->trx, RSL_ERR_NORMAL_UNSPEC, &dch->chan_nr, NULL, msg); + return rsl_tx_chan_act_acknack(lchan, RSL_ERR_NORMAL_UNSPEC); + } + return 0; + } + + /* Remember to send an RSL ACK once the lchan is active */ + lchan->rel_act_kind = LCHAN_REL_ACT_RSL; + + /* actually activate the channel in the BTS */ + rc = l1sap_chan_act(lchan->ts->trx, dch->chan_nr, &tp); + if (rc < 0) + return rsl_tx_chan_act_acknack(lchan, -rc); + + return 0; +} + +static int dyn_ts_pdch_release(struct gsm_lchan *lchan) +{ + struct gsm_bts_trx_ts *ts = lchan->ts; + + if (ts->dyn.pchan_is != ts->dyn.pchan_want) { + LOGP(DRSL, LOGL_ERROR, "%s: PDCH release requested but already" + " in switchover\n", gsm_ts_and_pchan_name(ts)); + return -EINVAL; + } + + /* + * Indicate PDCH Disconnect in dyn_pdch.want, let pcu_tx_info_ind() + * pick it up and wait for PCU to disable the channel. + */ + ts->dyn.pchan_want = GSM_PCHAN_NONE; + + if (!pcu_connected()) { + /* PCU not connected yet. Just record the new type and done, + * the PCU will pick it up once connected. */ + ts->dyn.pchan_is = GSM_PCHAN_NONE; + return 1; + } + + return pcu_tx_info_ind(); +} + +/* 8.4.14 RF CHANnel RELease is received */ +static int rsl_rx_rf_chan_rel(struct gsm_lchan *lchan, uint8_t chan_nr) +{ + int rc; + + if (lchan->state == LCHAN_S_NONE) { + LOGP(DRSL, LOGL_ERROR, + "%s ss=%d state=%s Rx RSL RF Channel Release, but is already inactive;" + " just ACKing the release\n", + gsm_ts_and_pchan_name(lchan->ts), lchan->nr, + gsm_lchans_name(lchan->state)); + /* Just ack the release and ignore. Make sure to reflect the same chan_nr we received, + * not necessarily reflecting the current lchan state. */ + return tx_rf_rel_ack(lchan, chan_nr); + } + + if (lchan->abis_ip.rtp_socket) { + rsl_tx_ipac_dlcx_ind(lchan, RSL_ERR_NORMAL_UNSPEC); + osmo_rtp_socket_log_stats(lchan->abis_ip.rtp_socket, DRTP, LOGL_INFO, + "Closing RTP socket on Channel Release "); + osmo_rtp_socket_free(lchan->abis_ip.rtp_socket); + lchan->abis_ip.rtp_socket = NULL; + msgb_queue_flush(&lchan->dl_tch_queue); + } + + /* release handover state */ + handover_reset(lchan); + + lchan->rel_act_kind = LCHAN_REL_ACT_RSL; + + /* Dynamic channel in PDCH mode is released via PCU */ + if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH + && lchan->ts->dyn.pchan_is == GSM_PCHAN_PDCH) { + rc = dyn_ts_pdch_release(lchan); + if (rc == 1) { + /* If the PCU is not connected, continue to rel ack right away. */ + lchan->rel_act_kind = LCHAN_REL_ACT_PCU; + return rsl_tx_rf_rel_ack(lchan); + } + /* Waiting for PDCH release */ + return rc; + } + + l1sap_chan_rel(lchan->ts->trx, chan_nr); + + lapdm_channel_exit(&lchan->lapdm_ch); + + return 0; +} + +#ifdef FAKE_CIPH_MODE_COMPL +/* ugly hack to send a fake CIPH MODE COMPLETE back to the BSC */ +#include +#include +static int tx_ciph_mod_compl_hack(struct gsm_lchan *lchan, uint8_t link_id, + const char *imeisv) +{ + struct msgb *fake_msg; + struct gsm48_hdr *g48h; + uint8_t mid_buf[11]; + int rc; + + fake_msg = rsl_msgb_alloc(128); + if (!fake_msg) + return -ENOMEM; + + /* generate 04.08 RR message */ + g48h = (struct gsm48_hdr *) msgb_put(fake_msg, sizeof(*g48h)); + g48h->proto_discr = GSM48_PDISC_RR; + g48h->msg_type = GSM48_MT_RR_CIPH_M_COMPL; + + /* add IMEISV, if requested */ + if (imeisv) { + rc = gsm48_generate_mid_from_imsi(mid_buf, imeisv); + if (rc > 0) { + mid_buf[2] = (mid_buf[2] & 0xf8) | GSM_MI_TYPE_IMEISV; + memcpy(msgb_put(fake_msg, rc), mid_buf, rc); + } + } + + rsl_rll_push_l3(fake_msg, RSL_MT_DATA_IND, gsm_lchan2chan_nr(lchan), + link_id, 1); + + fake_msg->lchan = lchan; + fake_msg->trx = lchan->ts->trx; + + /* send it back to the BTS */ + return abis_bts_rsl_sendmsg(fake_msg); +} + +struct ciph_mod_compl { + struct osmo_timer_list timer; + struct gsm_lchan *lchan; + int send_imeisv; + uint8_t link_id; +}; + +static void cmc_timer_cb(void *data) +{ + struct ciph_mod_compl *cmc = data; + const char *imeisv = NULL; + + LOGP(DRSL, LOGL_NOTICE, + "%s Sending FAKE CIPHERING MODE COMPLETE to BSC (Alg %u)\n", + gsm_lchan_name(cmc->lchan), cmc->lchan->encr.alg_id); + + if (cmc->send_imeisv) + imeisv = "0123456789012345"; + + /* We have no clue whatsoever that this lchan still exists! */ + tx_ciph_mod_compl_hack(cmc->lchan, cmc->link_id, imeisv); + + talloc_free(cmc); +} +#endif + + +/* 8.4.6 ENCRYPTION COMMAND */ +static int rsl_rx_encr_cmd(struct msgb *msg) +{ + struct gsm_lchan *lchan = msg->lchan; + struct abis_rsl_dchan_hdr *dch = msgb_l2(msg); + struct tlv_parsed tp; + uint8_t link_id; + + if (rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)) < 0) { + return rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, NULL, msg); + } + + if (!TLVP_PRESENT(&tp, RSL_IE_ENCR_INFO) || + !TLVP_PRESENT(&tp, RSL_IE_L3_INFO) || + !TLVP_PRESENT(&tp, RSL_IE_LINK_IDENT)) { + return rsl_tx_error_report(msg->trx, RSL_ERR_MAND_IE_ERROR, &dch->chan_nr, NULL, msg); + } + + /* 9.3.7 Encryption Information */ + if (TLVP_PRESENT(&tp, RSL_IE_ENCR_INFO)) { + uint8_t len = TLVP_LEN(&tp, RSL_IE_ENCR_INFO); + const uint8_t *val = TLVP_VAL(&tp, RSL_IE_ENCR_INFO); + + if (encr_info2lchan(lchan, val, len) < 0) { + return rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, + NULL, msg); + } + } + + /* 9.3.2 Link Identifier */ + link_id = *TLVP_VAL(&tp, RSL_IE_LINK_IDENT); + + /* we have to set msg->l3h as rsl_rll_push_l3 will use it to + * determine the length field of the L3_INFO IE */ + msg->l3h = (uint8_t *) TLVP_VAL(&tp, RSL_IE_L3_INFO); + + /* pop the RSL dchan header, but keep L3 TLV */ + msgb_pull(msg, msg->l3h - msg->data); + + /* push a fake RLL DATA REQ header */ + rsl_rll_push_l3(msg, RSL_MT_DATA_REQ, dch->chan_nr, link_id, 1); + + +#ifdef FAKE_CIPH_MODE_COMPL + if (lchan->encr.alg_id != RSL_ENC_ALG_A5(0)) { + struct ciph_mod_compl *cmc; + struct gsm48_hdr *g48h = (struct gsm48_hdr *) msg->l3h; + + cmc = talloc_zero(NULL, struct ciph_mod_compl); + if (g48h->data[0] & 0x10) + cmc->send_imeisv = 1; + cmc->lchan = lchan; + cmc->link_id = link_id; + cmc->timer.cb = cmc_timer_cb; + cmc->timer.data = cmc; + osmo_timer_schedule(&cmc->timer, 1, 0); + + /* FIXME: send fake CM SERVICE ACCEPT to MS */ + + return 0; + } else +#endif + { + LOGP(DRSL, LOGL_INFO, "%s Fwd RSL ENCR CMD (Alg %u) to LAPDm\n", + gsm_lchan_name(lchan), lchan->encr.alg_id); + /* hand it into RSLms for transmission of L3_INFO to the MS */ + lapdm_rslms_recvmsg(msg, &lchan->lapdm_ch); + /* return 1 to make sure the msgb is not free'd */ + return 1; + } +} + +/* 8.4.11 MODE MODIFY NEGATIVE ACKNOWLEDGE */ +static int _rsl_tx_mode_modif_nack(struct gsm_bts_trx *trx, uint8_t chan_nr, uint8_t cause, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + + if (lchan) + LOGP(DRSL, LOGL_NOTICE, "%s: ", gsm_lchan_name(lchan)); + else + LOGP(DRSL, LOGL_NOTICE, "0x%02x: ", chan_nr); + LOGPC(DRSL, LOGL_NOTICE, "Tx MODE MODIFY NACK (cause = 0x%02x)\n", cause); + + msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); + if (!msg) + return -ENOMEM; + + msg->len = 0; + msg->data = msg->tail = msg->l3h; + + /* 9.3.26 Cause */ + msgb_tlv_put(msg, RSL_IE_CAUSE, 1, &cause); + rsl_dch_push_hdr(msg, RSL_MT_MODE_MODIFY_NACK, chan_nr); + msg->trx = trx; + + return abis_bts_rsl_sendmsg(msg); +} +static int rsl_tx_mode_modif_nack(struct gsm_lchan *lchan, uint8_t cause) +{ + return _rsl_tx_mode_modif_nack(lchan->ts->trx, gsm_lchan2chan_nr(lchan), cause, lchan); +} + + +/* 8.4.10 MODE MODIFY ACK */ +static int rsl_tx_mode_modif_ack(struct gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + + LOGP(DRSL, LOGL_INFO, "%s Tx MODE MODIF ACK\n", gsm_lchan_name(lchan)); + + msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); + if (!msg) + return -ENOMEM; + + rsl_dch_push_hdr(msg, RSL_MT_MODE_MODIFY_ACK, chan_nr); + msg->trx = lchan->ts->trx; + + return abis_bts_rsl_sendmsg(msg); +} + +/* 8.4.9 MODE MODIFY */ +static int rsl_rx_mode_modif(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dch = msgb_l2(msg); + struct gsm_lchan *lchan = msg->lchan; + struct rsl_ie_chan_mode *cm; + struct tlv_parsed tp; + + rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); + + /* 9.3.6 Channel Mode */ + if (!TLVP_PRESENT(&tp, RSL_IE_CHAN_MODE)) { + LOGP(DRSL, LOGL_NOTICE, "missing Channel Mode\n"); + return rsl_tx_mode_modif_nack(lchan, RSL_ERR_MAND_IE_ERROR); + } + cm = (struct rsl_ie_chan_mode *) TLVP_VAL(&tp, RSL_IE_CHAN_MODE); + lchan_tchmode_from_cmode(lchan, cm); + + if (bts_supports_cm(lchan->ts->trx->bts, lchan->ts->pchan, lchan->tch_mode) != 1) { + LOGP(DRSL, LOGL_ERROR, "invalid mode/codec instructed by BSC, check BSC configuration.\n"); + return rsl_tx_mode_modif_nack(lchan, RSL_ERR_SERV_OPT_UNAVAIL); + } + + /* 9.3.7 Encryption Information */ + if (TLVP_PRESENT(&tp, RSL_IE_ENCR_INFO)) { + uint8_t len = TLVP_LEN(&tp, RSL_IE_ENCR_INFO); + const uint8_t *val = TLVP_VAL(&tp, RSL_IE_ENCR_INFO); + + if (encr_info2lchan(lchan, val, len) < 0) { + rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, NULL, msg); + return rsl_tx_mode_modif_nack(lchan, RSL_ERR_ENCR_UNIMPL); + } + } + + /* 9.3.45 Main channel reference */ + + /* 9.3.52 MultiRate Configuration */ + if (TLVP_PRESENT(&tp, RSL_IE_MR_CONFIG)) { + if (TLVP_LEN(&tp, RSL_IE_MR_CONFIG) > sizeof(lchan->mr_bts_lv) - 1) { + LOGP(DRSL, LOGL_ERROR, "Error parsing MultiRate conf IE\n"); + rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, NULL, msg); + return rsl_tx_mode_modif_nack(lchan, RSL_ERR_IE_CONTENT);; + } + memcpy(lchan->mr_bts_lv, TLVP_VAL(&tp, RSL_IE_MR_CONFIG) - 1, + TLVP_LEN(&tp, RSL_IE_MR_CONFIG) + 1); + amr_parse_mr_conf(&lchan->tch.amr_mr, TLVP_VAL(&tp, RSL_IE_MR_CONFIG), + TLVP_LEN(&tp, RSL_IE_MR_CONFIG)); + amr_log_mr_conf(DRTP, LOGL_DEBUG, gsm_lchan_name(lchan), + &lchan->tch.amr_mr); + lchan->tch.last_cmr = AMR_CMR_NONE; + } + /* 9.3.53 MultiRate Control */ + /* 9.3.54 Supported Codec Types */ + + l1sap_chan_modify(lchan->ts->trx, dch->chan_nr); + + /* FIXME: delay this until L1 says OK? */ + rsl_tx_mode_modif_ack(lchan); + + return 0; +} + +/* 8.4.15 MS POWER CONTROL */ +static int rsl_rx_ms_pwr_ctrl(struct msgb *msg) +{ + struct gsm_lchan *lchan = msg->lchan; + struct tlv_parsed tp; + + rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); + if (TLVP_PRES_LEN(&tp, RSL_IE_MS_POWER, 1)) { + uint8_t pwr = *TLVP_VAL(&tp, RSL_IE_MS_POWER) & 0x1F; + lchan->ms_power_ctrl.fixed = 1; + lchan->ms_power_ctrl.current = pwr; + + LOGP(DRSL, LOGL_NOTICE, "%s forcing power to %d\n", + gsm_lchan_name(lchan), lchan->ms_power_ctrl.current); + bts_model_adjst_ms_pwr(lchan); + } + + return 0; +} + +/* 8.4.20 SACCH INFO MODify */ +static int rsl_rx_sacch_inf_mod(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dch = msgb_l2(msg); + struct gsm_lchan *lchan = msg->lchan; + struct tlv_parsed tp; + uint8_t rsl_si, osmo_si; + + rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); + + if (TLVP_PRESENT(&tp, RSL_IE_STARTNG_TIME)) { + LOGP(DRSL, LOGL_NOTICE, "Starting time not supported\n"); + return rsl_tx_error_report(msg->trx, RSL_ERR_SERV_OPT_UNIMPL, &dch->chan_nr, NULL, msg); + } + + /* 9.3.30 System Info Type */ + if (!TLVP_PRESENT(&tp, RSL_IE_SYSINFO_TYPE)) + return rsl_tx_error_report(msg->trx, RSL_ERR_MAND_IE_ERROR, &dch->chan_nr, NULL, msg); + + rsl_si = *TLVP_VAL(&tp, RSL_IE_SYSINFO_TYPE); + if (!OSMO_IN_ARRAY(rsl_si, rsl_sacch_sitypes)) + return rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, NULL, msg); + + osmo_si = osmo_rsl2sitype(rsl_si); + if (osmo_si == SYSINFO_TYPE_NONE) { + LOGP(DRSL, LOGL_NOTICE, "%s Rx SACCH SI 0x%02x not supported.\n", + gsm_lchan_name(lchan), rsl_si); + return rsl_tx_error_report(msg->trx, RSL_ERR_IE_CONTENT, &dch->chan_nr, NULL, msg); + } + if (TLVP_PRESENT(&tp, RSL_IE_L3_INFO)) { + uint16_t len = TLVP_LEN(&tp, RSL_IE_L3_INFO); + lapdm_ui_prefix_lchan(lchan, TLVP_VAL(&tp, RSL_IE_L3_INFO), osmo_si, len); + + LOGP(DRSL, LOGL_INFO, "%s Rx RSL SACCH FILLING (SI%s)\n", + gsm_lchan_name(lchan), + get_value_string(osmo_sitype_strs, osmo_si)); + } else { + lchan->si.valid &= ~(1 << osmo_si); + LOGP(DRSL, LOGL_INFO, "%s Rx RSL Disabling SACCH FILLING (SI%s)\n", + gsm_lchan_name(lchan), + get_value_string(osmo_sitype_strs, osmo_si)); + } + + return 0; +} + +/* + * ip.access related messages + */ +static void rsl_add_rtp_stats(struct gsm_lchan *lchan, struct msgb *msg) +{ + struct ipa_stats { + uint32_t packets_sent; + uint32_t octets_sent; + uint32_t packets_recv; + uint32_t octets_recv; + uint32_t packets_lost; + uint32_t arrival_jitter; + uint32_t avg_tx_delay; + } __attribute__((packed)); + + struct ipa_stats stats; + + memset(&stats, 0, sizeof(stats)); + + if (lchan->abis_ip.rtp_socket) + osmo_rtp_socket_stats(lchan->abis_ip.rtp_socket, + &stats.packets_sent, &stats.octets_sent, + &stats.packets_recv, &stats.octets_recv, + &stats.packets_lost, &stats.arrival_jitter); + /* convert to network byte order */ + stats.packets_sent = htonl(stats.packets_sent); + stats.octets_sent = htonl(stats.octets_sent); + stats.packets_recv = htonl(stats.packets_recv); + stats.octets_recv = htonl(stats.octets_recv); + stats.packets_lost = htonl(stats.packets_lost); + + msgb_tlv_put(msg, RSL_IE_IPAC_CONN_STAT, sizeof(stats), (uint8_t *) &stats); +} + +int rsl_tx_ipac_dlcx_ind(struct gsm_lchan *lchan, uint8_t cause) +{ + struct msgb *nmsg; + + LOGP(DRSL, LOGL_NOTICE, "%s Sending RTP delete indication: cause = %s\n", + gsm_lchan_name(lchan), rsl_err_name(cause)); + + nmsg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); + if (!nmsg) + return -ENOMEM; + + msgb_tv16_put(nmsg, RSL_IE_IPAC_CONN_ID, htons(lchan->abis_ip.conn_id)); + rsl_add_rtp_stats(lchan, nmsg); + msgb_tlv_put(nmsg, RSL_IE_CAUSE, 1, &cause); + rsl_ipa_push_hdr(nmsg, RSL_MT_IPAC_DLCX_IND, gsm_lchan2chan_nr(lchan)); + + nmsg->trx = lchan->ts->trx; + + return abis_bts_rsl_sendmsg(nmsg); +} + +/* transmit an CRCX ACK for the lchan */ +static int rsl_tx_ipac_XXcx_ack(struct gsm_lchan *lchan, int inc_pt2, + uint8_t orig_msgt) +{ + struct msgb *msg; + uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + const char *name; + struct in_addr ia; + + if (orig_msgt == RSL_MT_IPAC_CRCX) + name = "CRCX"; + else + name = "MDCX"; + + ia.s_addr = htonl(lchan->abis_ip.bound_ip); + LOGP(DRSL, LOGL_INFO, "%s RSL Tx IPAC_%s_ACK (local %s:%u, ", + gsm_lchan_name(lchan), name, + inet_ntoa(ia), lchan->abis_ip.bound_port); + ia.s_addr = htonl(lchan->abis_ip.connect_ip); + LOGPC(DRSL, LOGL_INFO, "remote %s:%u)\n", + inet_ntoa(ia), lchan->abis_ip.connect_port); + + msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); + if (!msg) + return -ENOMEM; + + + /* Connection ID */ + msgb_tv16_put(msg, RSL_IE_IPAC_CONN_ID, htons(lchan->abis_ip.conn_id)); + + /* locally bound IP */ + msgb_v_put(msg, RSL_IE_IPAC_LOCAL_IP); + msgb_put_u32(msg, lchan->abis_ip.bound_ip); + + /* locally bound port */ + msgb_tv16_put(msg, RSL_IE_IPAC_LOCAL_PORT, + lchan->abis_ip.bound_port); + + if (inc_pt2) { + /* RTP Payload Type 2 */ + msgb_tv_put(msg, RSL_IE_IPAC_RTP_PAYLOAD2, + lchan->abis_ip.rtp_payload2); + } + + /* push the header in front */ + rsl_ipa_push_hdr(msg, orig_msgt + 1, chan_nr); + msg->trx = lchan->ts->trx; + + return abis_bts_rsl_sendmsg(msg); +} + +static int rsl_tx_ipac_dlcx_ack(struct gsm_lchan *lchan, int inc_conn_id) +{ + struct msgb *msg; + uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + + LOGP(DRSL, LOGL_INFO, "%s RSL Tx IPAC_DLCX_ACK\n", + gsm_lchan_name(lchan)); + + msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); + if (!msg) + return -ENOMEM; + + if (inc_conn_id) { + msgb_tv16_put(msg, RSL_IE_IPAC_CONN_ID, lchan->abis_ip.conn_id); + rsl_add_rtp_stats(lchan, msg); + } + + rsl_ipa_push_hdr(msg, RSL_MT_IPAC_DLCX_ACK, chan_nr); + msg->trx = lchan->ts->trx; + + return abis_bts_rsl_sendmsg(msg); +} + +static int rsl_tx_ipac_dlcx_nack(struct gsm_lchan *lchan, int inc_conn_id, + uint8_t cause) +{ + struct msgb *msg; + uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + + LOGP(DRSL, LOGL_INFO, "%s RSL Tx IPAC_DLCX_NACK\n", + gsm_lchan_name(lchan)); + + msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); + if (!msg) + return -ENOMEM; + + if (inc_conn_id) + msgb_tv_put(msg, RSL_IE_IPAC_CONN_ID, lchan->abis_ip.conn_id); + + msgb_tlv_put(msg, RSL_IE_CAUSE, 1, &cause); + + rsl_ipa_push_hdr(msg, RSL_MT_IPAC_DLCX_NACK, chan_nr); + msg->trx = lchan->ts->trx; + + return abis_bts_rsl_sendmsg(msg); + +} + + +/* transmit an CRCX NACK for the lchan */ +static int tx_ipac_XXcx_nack(struct gsm_lchan *lchan, uint8_t cause, + int inc_ipport, uint8_t orig_msgtype) +{ + struct msgb *msg; + uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + + /* FIXME: allocate new msgb and copy old over */ + LOGP(DRSL, LOGL_NOTICE, "%s RSL Tx IPAC_BIND_NACK\n", + gsm_lchan_name(lchan)); + + msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); + if (!msg) + return -ENOMEM; + + if (inc_ipport) { + /* remote IP */ + msgb_v_put(msg, RSL_IE_IPAC_REMOTE_IP); + msgb_put_u32(msg, lchan->abis_ip.connect_ip); + + /* remote port */ + msgb_tv16_put(msg, RSL_IE_IPAC_REMOTE_PORT, + htons(lchan->abis_ip.connect_port)); + } + + /* 9.3.26 Cause */ + msgb_tlv_put(msg, RSL_IE_CAUSE, 1, &cause); + + /* push the header in front */ + rsl_ipa_push_hdr(msg, orig_msgtype + 2, chan_nr); + msg->trx = lchan->ts->trx; + + return abis_bts_rsl_sendmsg(msg); +} + +static char *get_rsl_local_ip(struct gsm_bts_trx *trx) +{ + struct e1inp_ts *ts = trx->rsl_link->ts; + struct sockaddr_storage ss; + socklen_t sa_len = sizeof(ss); + static char hostbuf[256]; + int rc; + + rc = getsockname(ts->driver.ipaccess.fd.fd, (struct sockaddr *) &ss, + &sa_len); + if (rc < 0) + return NULL; + + rc = getnameinfo((struct sockaddr *)&ss, sa_len, + hostbuf, sizeof(hostbuf), NULL, 0, + NI_NUMERICHOST); + if (rc < 0) + return NULL; + + return hostbuf; +} + +static int rsl_rx_ipac_XXcx(struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dch = msgb_l2(msg); + struct tlv_parsed tp; + struct gsm_lchan *lchan = msg->lchan; + struct gsm_bts *bts = lchan->ts->trx->bts; + const uint8_t *payload_type, *speech_mode, *payload_type2; + uint32_t connect_ip = 0; + uint16_t connect_port = 0; + int rc, inc_ip_port = 0, port; + char *name; + struct in_addr ia; + struct in_addr addr; + + if (dch->c.msg_type == RSL_MT_IPAC_CRCX) + name = "CRCX"; + else + name = "MDCX"; + + /* check the kind of channel and reject */ + if (lchan->type != GSM_LCHAN_TCH_F && lchan->type != GSM_LCHAN_TCH_H) + return tx_ipac_XXcx_nack(lchan, 0x52, + 0, dch->c.msg_type); + + rc = rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); + if (rc < 0) + return tx_ipac_XXcx_nack(lchan, RSL_ERR_MAND_IE_ERROR, + 0, dch->c.msg_type); + + LOGP(DRSL, LOGL_DEBUG, "%s IPAC_%s: ", gsm_lchan_name(lchan), name); + if (TLVP_PRES_LEN(&tp, RSL_IE_IPAC_REMOTE_IP, 4)) { + connect_ip = tlvp_val32_unal(&tp, RSL_IE_IPAC_REMOTE_IP); + addr.s_addr = connect_ip; + LOGPC(DRSL, LOGL_DEBUG, "connect_ip=%s ", inet_ntoa(addr)); + } + + if (TLVP_PRES_LEN(&tp, RSL_IE_IPAC_REMOTE_PORT, 2)) { + connect_port = tlvp_val16_unal(&tp, RSL_IE_IPAC_REMOTE_PORT); + LOGPC(DRSL, LOGL_DEBUG, "connect_port=%u ", + ntohs(connect_port)); + } + + speech_mode = TLVP_VAL(&tp, RSL_IE_IPAC_SPEECH_MODE); + if (speech_mode) + LOGPC(DRSL, LOGL_DEBUG, "speech_mode=%u ", *speech_mode); + + payload_type = TLVP_VAL(&tp, RSL_IE_IPAC_RTP_PAYLOAD); + if (payload_type) + LOGPC(DRSL, LOGL_DEBUG, "payload_type=%u ", *payload_type); + + LOGPC(DRSL, LOGL_DEBUG, "\n"); + + payload_type2 = TLVP_VAL(&tp, RSL_IE_IPAC_RTP_PAYLOAD2); + + if (dch->c.msg_type == RSL_MT_IPAC_CRCX && connect_ip && connect_port) + inc_ip_port = 1; + + if (payload_type && payload_type2) { + LOGP(DRSL, LOGL_ERROR, "%s Rx RSL IPAC %s, " + "RTP_PT and RTP_PT2 in same msg !?!\n", + gsm_lchan_name(lchan), name); + return tx_ipac_XXcx_nack(lchan, RSL_ERR_MAND_IE_ERROR, + inc_ip_port, dch->c.msg_type); + } + + if (dch->c.msg_type == RSL_MT_IPAC_CRCX) { + char cname[32]; + char *ipstr = NULL; + if (lchan->abis_ip.rtp_socket) { + LOGP(DRSL, LOGL_ERROR, "%s Rx RSL IPAC CRCX, " + "but we already have socket!\n", + gsm_lchan_name(lchan)); + return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL, + inc_ip_port, dch->c.msg_type); + } + /* FIXME: select default value depending on speech_mode */ + //if (!payload_type) + lchan->tch.last_fn = LCHAN_FN_DUMMY; + lchan->abis_ip.rtp_socket = osmo_rtp_socket_create(lchan->ts->trx, + OSMO_RTP_F_POLL); + if (!lchan->abis_ip.rtp_socket) { + LOGP(DRTP, LOGL_ERROR, + "%s IPAC Failed to create RTP/RTCP sockets\n", + gsm_lchan_name(lchan)); + oml_fail_rep(OSMO_EVT_CRIT_RTP_TOUT, + "%s IPAC Failed to create RTP/RTCP sockets", + gsm_lchan_name(lchan)); + return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL, + inc_ip_port, dch->c.msg_type); + } + rc = osmo_rtp_socket_set_param(lchan->abis_ip.rtp_socket, + bts->rtp_jitter_adaptive ? + OSMO_RTP_P_JIT_ADAP : + OSMO_RTP_P_JITBUF, + bts->rtp_jitter_buf_ms); + if (rc < 0) + LOGP(DRTP, LOGL_ERROR, + "%s IPAC Failed to set RTP socket parameters: %s\n", + gsm_lchan_name(lchan), strerror(-rc)); + else + LOGP(DRTP, LOGL_INFO, + "%s IPAC set RTP socket parameters: %d\n", + gsm_lchan_name(lchan), rc); + lchan->abis_ip.rtp_socket->priv = lchan; + lchan->abis_ip.rtp_socket->rx_cb = &l1sap_rtp_rx_cb; + + if (connect_ip && connect_port) { + /* if CRCX specifies a remote IP, we can bind() + * here to 0.0.0.0 and wait for the connect() + * below, after which the kernel will have + * selected the local IP address. */ + ipstr = "0.0.0.0"; + } else { + /* if CRCX does not specify a remote IP, we will + * not do any connect() below, and thus the + * local socket will remain bound to 0.0.0.0 - + * which however we cannot legitimately report + * back to the BSC in the CRCX_ACK */ + ipstr = get_rsl_local_ip(lchan->ts->trx); + } + rc = osmo_rtp_socket_bind(lchan->abis_ip.rtp_socket, + ipstr, -1); + if (rc < 0) { + LOGP(DRTP, LOGL_ERROR, + "%s IPAC Failed to bind RTP/RTCP sockets\n", + gsm_lchan_name(lchan)); + oml_fail_rep(OSMO_EVT_CRIT_RTP_TOUT, + "%s IPAC Failed to bind RTP/RTCP sockets", + gsm_lchan_name(lchan)); + osmo_rtp_socket_free(lchan->abis_ip.rtp_socket); + lchan->abis_ip.rtp_socket = NULL; + msgb_queue_flush(&lchan->dl_tch_queue); + return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL, + inc_ip_port, dch->c.msg_type); + } + /* Ensure RTCP SDES contains some useful information */ + snprintf(cname, sizeof(cname), "bts@%s", ipstr); + osmo_rtp_set_source_desc(lchan->abis_ip.rtp_socket, cname, + gsm_lchan_name(lchan), NULL, NULL, + gsm_trx_unit_id(lchan->ts->trx), + "OsmoBTS-" PACKAGE_VERSION, NULL); + /* FIXME: multiplex connection, BSC proxy */ + } else { + /* MDCX */ + if (!lchan->abis_ip.rtp_socket) { + LOGP(DRSL, LOGL_ERROR, "%s Rx RSL IPAC MDCX, " + "but we have no RTP socket!\n", + gsm_lchan_name(lchan)); + return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL, + inc_ip_port, dch->c.msg_type); + } + } + + + /* Special rule: If connect_ip == 0.0.0.0, use RSL IP + * address */ + if (connect_ip == 0) { + struct e1inp_sign_link *sign_link = + lchan->ts->trx->rsl_link; + + ia.s_addr = htonl(get_signlink_remote_ip(sign_link)); + } else + ia.s_addr = connect_ip; + rc = osmo_rtp_socket_connect(lchan->abis_ip.rtp_socket, + inet_ntoa(ia), ntohs(connect_port)); + if (rc < 0) { + LOGP(DRTP, LOGL_ERROR, + "%s Failed to connect RTP/RTCP sockets\n", + gsm_lchan_name(lchan)); + osmo_rtp_socket_free(lchan->abis_ip.rtp_socket); + lchan->abis_ip.rtp_socket = NULL; + msgb_queue_flush(&lchan->dl_tch_queue); + return tx_ipac_XXcx_nack(lchan, RSL_ERR_RES_UNAVAIL, + inc_ip_port, dch->c.msg_type); + } + /* save IP address and port number */ + lchan->abis_ip.connect_ip = ntohl(ia.s_addr); + lchan->abis_ip.connect_port = ntohs(connect_port); + + rc = osmo_rtp_get_bound_ip_port(lchan->abis_ip.rtp_socket, + &lchan->abis_ip.bound_ip, + &port); + if (rc < 0) + LOGP(DRTP, LOGL_ERROR, "%s IPAC cannot obtain " + "locally bound IP/port: %d\n", + gsm_lchan_name(lchan), rc); + lchan->abis_ip.bound_port = port; + + /* Everything has succeeded, we can store new values in lchan */ + if (payload_type) { + lchan->abis_ip.rtp_payload = *payload_type; + if (lchan->abis_ip.rtp_socket) + osmo_rtp_socket_set_pt(lchan->abis_ip.rtp_socket, + *payload_type); + } + if (payload_type2) { + lchan->abis_ip.rtp_payload2 = *payload_type2; + if (lchan->abis_ip.rtp_socket) + osmo_rtp_socket_set_pt(lchan->abis_ip.rtp_socket, + *payload_type2); + } + if (speech_mode) + lchan->abis_ip.speech_mode = *speech_mode; + + /* FIXME: CSD, jitterbuffer, compression */ + + return rsl_tx_ipac_XXcx_ack(lchan, payload_type2 ? 1 : 0, + dch->c.msg_type); +} + +static int rsl_rx_ipac_dlcx(struct msgb *msg) +{ + struct tlv_parsed tp; + struct gsm_lchan *lchan = msg->lchan; + int rc, inc_conn_id = 0; + + rc = rsl_tlv_parse(&tp, msgb_l3(msg), msgb_l3len(msg)); + if (rc < 0) + return rsl_tx_ipac_dlcx_nack(lchan, 0, RSL_ERR_MAND_IE_ERROR); + + if (TLVP_PRESENT(&tp, RSL_IE_IPAC_CONN_ID)) + inc_conn_id = 1; + + rc = rsl_tx_ipac_dlcx_ack(lchan, inc_conn_id); + if (lchan->abis_ip.rtp_socket) { + osmo_rtp_socket_log_stats(lchan->abis_ip.rtp_socket, DRTP, LOGL_INFO, + "Closing RTP socket on DLCX "); + osmo_rtp_socket_free(lchan->abis_ip.rtp_socket); + lchan->abis_ip.rtp_socket = NULL; + msgb_queue_flush(&lchan->dl_tch_queue); + } + return rc; +} + +/* + * dynamic TCH/F_PDCH related messages, originally ip.access specific but + * reused for other BTS models (sysmo-bts, ...) + */ + +/* PDCH ACT/DEACT ACKNOWLEDGE */ +static int rsl_tx_dyn_pdch_ack(struct gsm_lchan *lchan, bool pdch_act) +{ + struct gsm_time *gtime = get_time(lchan->ts->trx->bts); + uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + struct msgb *msg; + uint8_t ie[2]; + + LOGP(DRSL, LOGL_NOTICE, "%s Tx PDCH %s ACK\n", + gsm_lchan_name(lchan), pdch_act? "ACT" : "DEACT"); + + msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); + if (!msg) + return -ENOMEM; + + if (pdch_act) { + gsm48_gen_starting_time(ie, gtime); + msgb_tv_fixed_put(msg, RSL_IE_FRAME_NUMBER, 2, ie); + } + rsl_dch_push_hdr(msg, + pdch_act? RSL_MT_IPAC_PDCH_ACT_ACK + : RSL_MT_IPAC_PDCH_DEACT_ACK, + chan_nr); + msg->lchan = lchan; + msg->trx = lchan->ts->trx; + + return abis_bts_rsl_sendmsg(msg); +} + +/* PDCH ACT/DEACT NEGATIVE ACKNOWLEDGE */ +static int rsl_tx_dyn_pdch_nack(struct gsm_lchan *lchan, bool pdch_act, + uint8_t cause) +{ + struct msgb *msg; + uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + + LOGP(DRSL, LOGL_NOTICE, "%s Tx PDCH %s NACK (cause = 0x%02x)\n", + gsm_lchan_name(lchan), pdch_act? "ACT" : "DEACT", cause); + + msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); + if (!msg) + return -ENOMEM; + + msg->len = 0; + msg->data = msg->tail = msg->l3h; + + /* 9.3.26 Cause */ + msgb_tlv_put(msg, RSL_IE_CAUSE, 1, &cause); + rsl_dch_push_hdr(msg, + pdch_act? RSL_MT_IPAC_PDCH_ACT_NACK + : RSL_MT_IPAC_PDCH_DEACT_NACK, + chan_nr); + msg->lchan = lchan; + msg->trx = lchan->ts->trx; + + return abis_bts_rsl_sendmsg(msg); +} + +/* + * Starting point for dynamic PDCH switching. See osmo-gsm-manuals.git for a + * diagram of what will happen here. The implementation is as follows: + * + * PDCH ACT == TCH/F -> PDCH: + * 1. call bts_model_ts_disconnect() to disconnect TCH/F; + * 2. cb_ts_disconnected() is called when done; + * 3. call bts_model_ts_connect() to connect as PDTCH; + * 4. cb_ts_connected() is called when done; + * 5. instruct the PCU to enable PDTCH; + * 6. the PCU will call back with an activation request; + * 7. l1sap_info_act_cnf() will call ipacc_dyn_pdch_complete() when SAPI + * activations are done; + * 8. send a PDCH ACT ACK. + * + * PDCH DEACT == PDCH -> TCH/F: + * 1. instruct the PCU to disable PDTCH; + * 2. the PCU will call back with a deactivation request; + * 3. l1sap_info_rel_cnf() will call bts_model_ts_disconnect() when SAPI + * deactivations are done; + * 4. cb_ts_disconnected() is called when done; + * 5. call bts_model_ts_connect() to connect as TCH/F; + * 6. cb_ts_connected() is called when done; + * 7. directly call ipacc_dyn_pdch_complete(), since no further action required + * for TCH/F; + * 8. send a PDCH DEACT ACK. + * + * When an error happens along the way, a PDCH DE/ACT NACK is sent. + * TODO: may need to be made more waterproof in all stages, to send a NACK and + * clear the PDCH pending flags from ts->flags. + */ +static void rsl_rx_dyn_pdch(struct msgb *msg, bool pdch_act) +{ + int rc; + struct gsm_lchan *lchan = msg->lchan; + struct gsm_bts_trx_ts *ts = lchan->ts; + bool is_pdch_act = (ts->flags & TS_F_PDCH_ACTIVE); + + if (ts->flags & TS_F_PDCH_PENDING_MASK) { + /* Only one of the pending flags should ever be set at the same + * time, but just log both in case both should be set. */ + LOGP(DRSL, LOGL_ERROR, + "%s Request to PDCH %s, but PDCH%s%s is still pending\n", + gsm_lchan_name(lchan), pdch_act? "ACT" : "DEACT", + (ts->flags & TS_F_PDCH_ACT_PENDING)? " ACT" : "", + (ts->flags & TS_F_PDCH_DEACT_PENDING)? " DEACT" : ""); + rsl_tx_dyn_pdch_nack(lchan, pdch_act, RSL_ERR_NORMAL_UNSPEC); + return; + } + + if (lchan->state != LCHAN_S_NONE) { + LOGP(DRSL, LOGL_ERROR, + "%s Request to PDCH %s, but lchan is still active\n", + gsm_ts_and_pchan_name(ts), pdch_act? "ACT" : "DEACT"); + rsl_tx_dyn_pdch_nack(lchan, pdch_act, RSL_ERR_NORMAL_UNSPEC); + } + + ts->flags |= pdch_act? TS_F_PDCH_ACT_PENDING + : TS_F_PDCH_DEACT_PENDING; + + /* ensure that this is indeed a dynamic-PDCH channel */ + if (ts->pchan != GSM_PCHAN_TCH_F_PDCH) { + LOGP(DRSL, LOGL_ERROR, + "%s Attempt to PDCH %s on TS that is not a TCH/F_PDCH (is %s)\n", + gsm_lchan_name(lchan), pdch_act? "ACT" : "DEACT", + gsm_pchan_name(ts->pchan)); + ipacc_dyn_pdch_complete(ts, -EINVAL); + return; + } + + if (is_pdch_act == pdch_act) { + LOGP(DL1C, LOGL_NOTICE, + "%s Request to PDCH %s, but is already so\n", + gsm_lchan_name(lchan), pdch_act? "ACT" : "DEACT"); + ipacc_dyn_pdch_complete(ts, 0); + return; + } + + if (pdch_act) { + /* Clear TCH state. Only first lchan matters for PDCH */ + clear_lchan_for_pdch_activ(ts->lchan); + + /* First, disconnect the TCH channel, to connect PDTCH later */ + rc = bts_model_ts_disconnect(ts); + } else { + /* First, deactivate PDTCH through the PCU, to connect TCH + * later. + * pcu_tx_info_ind() will pick up TS_F_PDCH_DEACT_PENDING and + * trigger a deactivation. + * Except when the PCU is not connected yet, then trigger + * disconnect immediately from here. The PCU will catch up when + * it connects. */ + /* TODO: timeout on channel connect / disconnect request from PCU? */ + if (pcu_connected()) + rc = pcu_tx_info_ind(); + else + rc = bts_model_ts_disconnect(ts); + } + + /* Error? then NACK right now. */ + if (rc) + ipacc_dyn_pdch_complete(ts, rc); +} + +static void ipacc_dyn_pdch_ts_disconnected(struct gsm_bts_trx_ts *ts) +{ + int rc; + enum gsm_phys_chan_config as_pchan; + + if (ts->flags & TS_F_PDCH_DEACT_PENDING) { + LOGP(DRSL, LOGL_DEBUG, + "%s PDCH DEACT operation: channel disconnected, will reconnect as TCH\n", + gsm_lchan_name(ts->lchan)); + as_pchan = GSM_PCHAN_TCH_F; + } else if (ts->flags & TS_F_PDCH_ACT_PENDING) { + LOGP(DRSL, LOGL_DEBUG, + "%s PDCH ACT operation: channel disconnected, will reconnect as PDTCH\n", + gsm_lchan_name(ts->lchan)); + as_pchan = GSM_PCHAN_PDCH; + } else + /* No reconnect pending. */ + return; + + rc = conf_lchans_as_pchan(ts, as_pchan); + if (rc) + goto error_nack; + + rc = bts_model_ts_connect(ts, as_pchan); + +error_nack: + /* Error? then NACK right now. */ + if (rc) + ipacc_dyn_pdch_complete(ts, rc); +} + +static void osmo_dyn_ts_disconnected(struct gsm_bts_trx_ts *ts) +{ + DEBUGP(DRSL, "%s Disconnected\n", gsm_ts_and_pchan_name(ts)); + ts->dyn.pchan_is = GSM_PCHAN_NONE; + + switch (ts->dyn.pchan_want) { + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_TCH_H: + case GSM_PCHAN_PDCH: + break; + default: + LOGP(DRSL, LOGL_ERROR, + "%s Dyn TS disconnected, but invalid desired pchan: %s\n", + gsm_ts_and_pchan_name(ts), gsm_pchan_name(ts->dyn.pchan_want)); + ts->dyn.pchan_want = GSM_PCHAN_NONE; + /* TODO: how would this recover? */ + return; + } + + conf_lchans_as_pchan(ts, ts->dyn.pchan_want); + DEBUGP(DRSL, "%s Connect\n", gsm_ts_and_pchan_name(ts)); + bts_model_ts_connect(ts, ts->dyn.pchan_want); +} + +void cb_ts_disconnected(struct gsm_bts_trx_ts *ts) +{ + OSMO_ASSERT(ts); + + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_PDCH: + return ipacc_dyn_pdch_ts_disconnected(ts); + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + return osmo_dyn_ts_disconnected(ts); + default: + return; + } +} + +static void ipacc_dyn_pdch_ts_connected(struct gsm_bts_trx_ts *ts) +{ + int rc; + + if (ts->flags & TS_F_PDCH_DEACT_PENDING) { + if (ts->lchan[0].type != GSM_LCHAN_TCH_F) + LOGP(DRSL, LOGL_ERROR, "%s PDCH DEACT error:" + " timeslot connected, so expecting" + " lchan type TCH/F, but is %s\n", + gsm_lchan_name(ts->lchan), + gsm_lchant_name(ts->lchan[0].type)); + + LOGP(DRSL, LOGL_DEBUG, "%s PDCH DEACT operation:" + " timeslot connected as TCH/F\n", + gsm_lchan_name(ts->lchan)); + + /* During PDCH DEACT, we're done right after the TCH/F came + * back up. */ + ipacc_dyn_pdch_complete(ts, 0); + + } else if (ts->flags & TS_F_PDCH_ACT_PENDING) { + if (ts->lchan[0].type != GSM_LCHAN_PDTCH) + LOGP(DRSL, LOGL_ERROR, "%s PDCH ACT error:" + " timeslot connected, so expecting" + " lchan type PDTCH, but is %s\n", + gsm_lchan_name(ts->lchan), + gsm_lchant_name(ts->lchan[0].type)); + + LOGP(DRSL, LOGL_DEBUG, "%s PDCH ACT operation:" + " timeslot connected as PDTCH\n", + gsm_lchan_name(ts->lchan)); + + /* The PDTCH is connected, now tell the PCU about it. Except + * when the PCU is not connected (yet), then there's nothing + * left to do now. The PCU will catch up when it connects. */ + if (!pcu_connected()) { + ipacc_dyn_pdch_complete(ts, 0); + return; + } + + /* The PCU will request to activate the PDTCH SAPIs, which, + * when done, will call back to ipacc_dyn_pdch_complete(). */ + /* TODO: timeout on channel connect / disconnect request from PCU? */ + rc = pcu_tx_info_ind(); + + /* Error? then NACK right now. */ + if (rc) + ipacc_dyn_pdch_complete(ts, rc); + } +} + +static void osmo_dyn_ts_connected(struct gsm_bts_trx_ts *ts) +{ + int rc; + struct msgb *msg = ts->dyn.pending_chan_activ; + ts->dyn.pending_chan_activ = NULL; + + if (!msg) { + LOGP(DRSL, LOGL_ERROR, + "%s TS re-connected, but no chan activ msg pending\n", + gsm_ts_and_pchan_name(ts)); + return; + } + + ts->dyn.pchan_is = ts->dyn.pchan_want; + DEBUGP(DRSL, "%s Connected\n", gsm_ts_and_pchan_name(ts)); + + /* continue where we left off before re-connecting the TS. */ + rc = rsl_rx_chan_activ(msg); + if (rc != 1) + msgb_free(msg); +} + +void cb_ts_connected(struct gsm_bts_trx_ts *ts) +{ + OSMO_ASSERT(ts); + + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_PDCH: + return ipacc_dyn_pdch_ts_connected(ts); + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + return osmo_dyn_ts_connected(ts); + default: + return; + } +} + +void ipacc_dyn_pdch_complete(struct gsm_bts_trx_ts *ts, int rc) +{ + bool pdch_act; + OSMO_ASSERT(ts); + + pdch_act = ts->flags & TS_F_PDCH_ACT_PENDING; + + if ((ts->flags & TS_F_PDCH_PENDING_MASK) == TS_F_PDCH_PENDING_MASK) + LOGP(DRSL, LOGL_ERROR, + "%s Internal Error: both PDCH ACT and PDCH DEACT pending\n", + gsm_lchan_name(ts->lchan)); + + ts->flags &= ~TS_F_PDCH_PENDING_MASK; + + if (rc != 0) { + LOGP(DRSL, LOGL_ERROR, + "PDCH %s on dynamic TCH/F_PDCH returned error %d\n", + pdch_act? "ACT" : "DEACT", rc); + rsl_tx_dyn_pdch_nack(ts->lchan, pdch_act, RSL_ERR_NORMAL_UNSPEC); + return; + } + + if (pdch_act) + ts->flags |= TS_F_PDCH_ACTIVE; + else + ts->flags &= ~TS_F_PDCH_ACTIVE; + DEBUGP(DRSL, "%s %s switched to %s mode (ts->flags == %x)\n", + gsm_lchan_name(ts->lchan), gsm_pchan_name(ts->pchan), + pdch_act? "PDCH" : "TCH/F", ts->flags); + + rc = rsl_tx_dyn_pdch_ack(ts->lchan, pdch_act); + if (rc) + LOGP(DRSL, LOGL_ERROR, + "Failed to transmit PDCH %s ACK, rc %d\n", + pdch_act? "ACT" : "DEACT", rc); +} + +/* handle a message with an RSL CHAN_NR that is incompatible/unknown */ +static int rsl_reject_unknown_lchan(struct msgb *msg) +{ + struct abis_rsl_common_hdr *rh = msgb_l2(msg); + struct abis_rsl_dchan_hdr *dch; + int rc; + + /* Handle GSM 08.58 7 Error Handling for the given input. This method will + * send either a CHANNEL ACTIVATION NACK, MODE MODIFY NACK or ERROR REPORT + * depending on the input of the method. */ + + /* TS 48.058 Section 7 explains how to do error handling */ + switch (rh->msg_discr & 0xfe) { + case ABIS_RSL_MDISC_DED_CHAN: + dch = msgb_l2(msg); + switch (dch->c.msg_type) { + case RSL_MT_CHAN_ACTIV: + rc = _rsl_tx_chan_act_nack(msg->trx, dch->chan_nr, + RSL_ERR_MAND_IE_ERROR, NULL); + break; + case RSL_MT_MODE_MODIFY_REQ: + rc = _rsl_tx_mode_modif_nack(msg->trx, dch->chan_nr, + RSL_ERR_MAND_IE_ERROR, NULL); + break; + default: + rc = rsl_tx_error_report(msg->trx, RSL_ERR_MAND_IE_ERROR, NULL, NULL, msg); + break; + } + break; + case ABIS_RSL_MDISC_RLL: + /* fall-through */ + case ABIS_RSL_MDISC_COM_CHAN: + /* fall-through */ + case ABIS_RSL_MDISC_TRX: + /* fall-through */ + case ABIS_RSL_MDISC_IPACCESS: + /* fall-through */ + default: + /* ERROR REPORT */ + rc = rsl_tx_error_report(msg->trx, RSL_ERR_MAND_IE_ERROR, NULL, NULL, msg); + } + + msgb_free(msg); + return rc; +} + +/* + * selecting message + */ + +static int rsl_rx_rll(struct gsm_bts_trx *trx, struct msgb *msg) +{ + struct abis_rsl_rll_hdr *rh = msgb_l2(msg); + struct gsm_lchan *lchan; + + if (msgb_l2len(msg) < sizeof(*rh)) { + LOGP(DRSL, LOGL_NOTICE, "RSL Radio Link Layer message too short\n"); + rsl_tx_error_report(trx, RSL_ERR_PROTO, &rh->chan_nr, &rh->link_id, msg); + msgb_free(msg); + return -EIO; + } + msg->l3h = (unsigned char *)rh + sizeof(*rh); + + if (!chan_nr_is_dchan(rh->chan_nr)) + return rsl_reject_unknown_lchan(msg); + + lchan = lchan_lookup(trx, rh->chan_nr, "RSL rx RLL: "); + if (!lchan) { + LOGP(DRLL, LOGL_NOTICE, "Rx RLL %s for unknown lchan\n", + rsl_msg_name(rh->c.msg_type)); + return rsl_reject_unknown_lchan(msg); + } + + DEBUGP(DRLL, "%s Rx RLL %s Abis -> LAPDm\n", gsm_lchan_name(lchan), + rsl_msg_name(rh->c.msg_type)); + + /* exception: RLL messages are _NOT_ freed as they are now + * owned by LAPDm which might have queued them */ + return lapdm_rslms_recvmsg(msg, &lchan->lapdm_ch); +} + +static inline int rsl_link_id_is_sacch(uint8_t link_id) +{ + if (link_id >> 6 == 1) + return 1; + else + return 0; +} + +static int rslms_is_meas_rep(struct msgb *msg) +{ + struct abis_rsl_common_hdr *rh = msgb_l2(msg); + struct abis_rsl_rll_hdr *rllh; + struct gsm48_hdr *gh; + + if ((rh->msg_discr & 0xfe) != ABIS_RSL_MDISC_RLL) + return 0; + + if (rh->msg_type != RSL_MT_UNIT_DATA_IND) + return 0; + + rllh = msgb_l2(msg); + if (rsl_link_id_is_sacch(rllh->link_id) == 0) + return 0; + + gh = msgb_l3(msg); + if (gh->proto_discr != GSM48_PDISC_RR) + return 0; + + switch (gh->msg_type) { + case GSM48_MT_RR_MEAS_REP: + case GSM48_MT_RR_EXT_MEAS_REP: + return 1; + default: + break; + } + + /* FIXME: this does not cover the Bter frame format and the associated + * short RR protocol descriptor for ENHANCED MEASUREMENT REPORT */ + + return 0; +} + +static inline uint8_t ms_to2rsl(const struct gsm_lchan *lchan, const struct lapdm_entity *le) +{ + return (lchan->ms_t_offs >= 0) ? lchan->ms_t_offs : (lchan->p_offs - le->ta); +} + +static inline bool ms_to_valid(const struct gsm_lchan *lchan) +{ + return (lchan->ms_t_offs >= 0) || (lchan->p_offs >= 0); +} + +/* 8.4.8 MEASUREMENT RESult */ +static int rsl_tx_meas_res(struct gsm_lchan *lchan, uint8_t *l3, int l3_len, const struct lapdm_entity *le) +{ + struct msgb *msg; + uint8_t meas_res[16]; + uint8_t chan_nr = gsm_lchan2chan_nr(lchan); + int res_valid = lchan->meas.flags & LC_UL_M_F_RES_VALID; + struct gsm_bts *bts = lchan->ts->trx->bts; + + LOGP(DRSL, LOGL_DEBUG, + "%s chan_num:%u Tx MEAS RES valid(%d), flags(%02x)\n", + gsm_lchan_name(lchan), chan_nr, res_valid, lchan->meas.flags); + + if (!res_valid) + return -EINPROGRESS; + + msg = rsl_msgb_alloc(sizeof(struct abis_rsl_dchan_hdr)); + if (!msg) + return -ENOMEM; + + LOGP(DRSL, LOGL_DEBUG, + "%s Send Meas RES: NUM:%u, RXLEV_FULL:%u, RXLEV_SUB:%u, RXQUAL_FULL:%u, RXQUAL_SUB:%u, MS_PWR:%u, UL_TA:%u, L3_LEN:%d, TimingOff:%u\n", + gsm_lchan_name(lchan), + lchan->meas.res_nr, + lchan->meas.ul_res.full.rx_lev, + lchan->meas.ul_res.sub.rx_lev, + lchan->meas.ul_res.full.rx_qual, + lchan->meas.ul_res.sub.rx_qual, + lchan->meas.l1_info[0], + lchan->meas.l1_info[1], l3_len, ms_to2rsl(lchan, le) - MEAS_MAX_TIMING_ADVANCE); + + msgb_tv_put(msg, RSL_IE_MEAS_RES_NR, lchan->meas.res_nr++); + size_t ie_len = gsm0858_rsl_ul_meas_enc(&lchan->meas.ul_res, + lchan->tch.dtx.dl_active, + meas_res); + lchan->tch.dtx.dl_active = false; + if (ie_len >= 3) { + if (bts->supp_meas_toa256) { + /* append signed 16bit value containing MS timing offset in 1/256th symbols + * in the vendor-specific "Supplementary Measurement Information" part of + * the uplink measurements IE. This is the current offset *relative* to the + * TA which the MS has already applied. So if you want to know the total + * propagation time between MS and BTS, you need to add the actual TA value + * used (from L1_INFO below, in full symbols) plus the ms_toa256 value + * in 1/256 symbol periods. */ + meas_res[ie_len++] = lchan->meas.ms_toa256 >> 8; + meas_res[ie_len++] = lchan->meas.ms_toa256 & 0xff; + } + msgb_tlv_put(msg, RSL_IE_UPLINK_MEAS, ie_len, meas_res); + lchan->meas.flags &= ~LC_UL_M_F_RES_VALID; + } + msgb_tv_put(msg, RSL_IE_BS_POWER, lchan->meas.bts_tx_pwr); + if (lchan->meas.flags & LC_UL_M_F_L1_VALID) { + msgb_tv_fixed_put(msg, RSL_IE_L1_INFO, 2, lchan->meas.l1_info); + lchan->meas.flags &= ~LC_UL_M_F_L1_VALID; + } + msgb_tl16v_put(msg, RSL_IE_L3_INFO, l3_len, l3); + if (ms_to_valid(lchan)) { + msgb_tv_put(msg, RSL_IE_MS_TIMING_OFFSET, ms_to2rsl(lchan, le)); + lchan->ms_t_offs = -1; + lchan->p_offs = -1; + } + + rsl_dch_push_hdr(msg, RSL_MT_MEAS_RES, chan_nr); + msg->trx = lchan->ts->trx; + + return abis_bts_rsl_sendmsg(msg); +} + +/* call-back for LAPDm code, called when it wants to send msgs UP */ +int lapdm_rll_tx_cb(struct msgb *msg, struct lapdm_entity *le, void *ctx) +{ + struct gsm_lchan *lchan = ctx; + struct abis_rsl_common_hdr *rh; + + OSMO_ASSERT(msg); + rh = msgb_l2(msg); + + if (lchan->state != LCHAN_S_ACTIVE) { + LOGP(DRSL, LOGL_INFO, "%s(%s) is not active . Dropping message.\n", + gsm_lchan_name(lchan), gsm_lchans_name(lchan->state)); + msgb_free(msg); + return 0; + } + + msg->trx = lchan->ts->trx; + msg->lchan = lchan; + + /* check if this is a measurement report from SACCH which needs special + * processing before forwarding */ + if (rslms_is_meas_rep(msg)) { + int rc; + + LOGP(DRSL, LOGL_INFO, "%s Handing RLL msg %s from LAPDm to MEAS REP\n", + gsm_lchan_name(lchan), rsl_msg_name(rh->msg_type)); + + /* REL_IND handling */ + if (rh->msg_type == RSL_MT_REL_IND && + (lchan->type == GSM_LCHAN_TCH_F || lchan->type == GSM_LCHAN_TCH_H)) { + LOGP(DRSL, LOGL_INFO, "%s Scheduling %s to L3 in next associated TCH-RTS.ind\n", + gsm_lchan_name(lchan), + rsl_msg_name(rh->msg_type)); + + if(lchan->pending_rel_ind_msg) { + LOGP(DRSL, LOGL_INFO, "Dropping pending release indication message\n"); + msgb_free(lchan->pending_rel_ind_msg); + } + + lchan->pending_rel_ind_msg = msg; + return 0; + } + + rc = rsl_tx_meas_res(lchan, msgb_l3(msg), msgb_l3len(msg), le); + msgb_free(msg); + return rc; + } else { + LOGP(DRSL, LOGL_INFO, "%s Fwd RLL msg %s from LAPDm to A-bis\n", + gsm_lchan_name(lchan), rsl_msg_name(rh->msg_type)); + + return abis_bts_rsl_sendmsg(msg); + } +} + +static int rsl_rx_cchan(struct gsm_bts_trx *trx, struct msgb *msg) +{ + struct abis_rsl_cchan_hdr *cch = msgb_l2(msg); + int ret = 0; + + if (msgb_l2len(msg) < sizeof(*cch)) { + LOGP(DRSL, LOGL_NOTICE, "RSL Common Channel Management message too short\n"); + rsl_tx_error_report(trx, RSL_ERR_PROTO, NULL, NULL, msg); + msgb_free(msg); + return -EIO; + } + msg->l3h = (unsigned char *)cch + sizeof(*cch); + + if (chan_nr_is_dchan(cch->chan_nr)) + return rsl_reject_unknown_lchan(msg); + + msg->lchan = lchan_lookup(trx, cch->chan_nr, "RSL rx CCHAN: "); + if (!msg->lchan) { + LOGP(DRSL, LOGL_ERROR, "Rx RSL %s for unknown lchan\n", + rsl_msg_name(cch->c.msg_type)); + return rsl_reject_unknown_lchan(msg); + } + + LOGP(DRSL, LOGL_INFO, "%s Rx RSL %s\n", gsm_lchan_name(msg->lchan), + rsl_msg_name(cch->c.msg_type)); + + switch (cch->c.msg_type) { + case RSL_MT_BCCH_INFO: + ret = rsl_rx_bcch_info(trx, msg); + break; + case RSL_MT_IMMEDIATE_ASSIGN_CMD: + ret = rsl_rx_imm_ass(trx, msg); + break; + case RSL_MT_PAGING_CMD: + ret = rsl_rx_paging_cmd(trx, msg); + break; + case RSL_MT_SMS_BC_CMD: + ret = rsl_rx_sms_bcast_cmd(trx, msg); + break; + case RSL_MT_SMS_BC_REQ: + case RSL_MT_NOT_CMD: + LOGP(DRSL, LOGL_NOTICE, "unimplemented RSL cchan msg_type %s\n", + rsl_msg_name(cch->c.msg_type)); + break; + default: + LOGP(DRSL, LOGL_NOTICE, "undefined RSL cchan msg_type 0x%02x\n", + cch->c.msg_type); + ret = -EINVAL; + break; + } + + if (ret != 1) + msgb_free(msg); + + return ret; +} + +static int rsl_rx_dchan(struct gsm_bts_trx *trx, struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dch = msgb_l2(msg); + int ret = 0; + + if (msgb_l2len(msg) < sizeof(*dch)) { + LOGP(DRSL, LOGL_NOTICE, "RSL Dedicated Channel Management message too short\n"); + msgb_free(msg); + return -EIO; + } + msg->l3h = (unsigned char *)dch + sizeof(*dch); + + if (!chan_nr_is_dchan(dch->chan_nr)) + return rsl_reject_unknown_lchan(msg); + + msg->lchan = lchan_lookup(trx, dch->chan_nr, "RSL rx DCHAN: "); + if (!msg->lchan) { + LOGP(DRSL, LOGL_ERROR, "Rx RSL %s for unknown lchan\n", + rsl_or_ipac_msg_name(dch->c.msg_type)); + return rsl_reject_unknown_lchan(msg); + } + + LOGP(DRSL, LOGL_INFO, "%s ss=%d Rx RSL %s\n", + gsm_ts_and_pchan_name(msg->lchan->ts), msg->lchan->nr, + rsl_or_ipac_msg_name(dch->c.msg_type)); + + switch (dch->c.msg_type) { + case RSL_MT_CHAN_ACTIV: + ret = rsl_rx_chan_activ(msg); + break; + case RSL_MT_RF_CHAN_REL: + ret = rsl_rx_rf_chan_rel(msg->lchan, dch->chan_nr); + break; + case RSL_MT_SACCH_INFO_MODIFY: + ret = rsl_rx_sacch_inf_mod(msg); + break; + case RSL_MT_DEACTIVATE_SACCH: + ret = l1sap_chan_deact_sacch(trx, dch->chan_nr); + break; + case RSL_MT_ENCR_CMD: + ret = rsl_rx_encr_cmd(msg); + break; + case RSL_MT_MODE_MODIFY_REQ: + ret = rsl_rx_mode_modif(msg); + break; + case RSL_MT_MS_POWER_CONTROL: + ret = rsl_rx_ms_pwr_ctrl(msg); + break; + case RSL_MT_IPAC_PDCH_ACT: + case RSL_MT_IPAC_PDCH_DEACT: + rsl_rx_dyn_pdch(msg, dch->c.msg_type == RSL_MT_IPAC_PDCH_ACT); + ret = 0; + break; + case RSL_MT_PHY_CONTEXT_REQ: + case RSL_MT_PREPROC_CONFIG: + case RSL_MT_RTD_REP: + case RSL_MT_PRE_HANDO_NOTIF: + case RSL_MT_MR_CODEC_MOD_REQ: + case RSL_MT_TFO_MOD_REQ: + LOGP(DRSL, LOGL_NOTICE, "unimplemented RSL dchan msg_type %s\n", + rsl_msg_name(dch->c.msg_type)); + break; + default: + LOGP(DRSL, LOGL_NOTICE, "undefined RSL dchan msg_type 0x%02x\n", + dch->c.msg_type); + ret = -EINVAL; + } + + if (ret != 1) + msgb_free(msg); + + return ret; +} + +static int rsl_rx_trx(struct gsm_bts_trx *trx, struct msgb *msg) +{ + struct abis_rsl_common_hdr *th = msgb_l2(msg); + int ret = 0; + + if (msgb_l2len(msg) < sizeof(*th)) { + LOGP(DRSL, LOGL_NOTICE, "RSL TRX message too short\n"); + rsl_tx_error_report(trx, RSL_ERR_PROTO, NULL, NULL, msg); + msgb_free(msg); + return -EIO; + } + msg->l3h = (unsigned char *)th + sizeof(*th); + + switch (th->msg_type) { + case RSL_MT_SACCH_FILL: + ret = rsl_rx_sacch_fill(trx, msg); + break; + default: + LOGP(DRSL, LOGL_NOTICE, "undefined RSL TRX msg_type 0x%02x\n", + th->msg_type); + ret = -EINVAL; + } + + if (ret != 1) + msgb_free(msg); + + return ret; +} + +static int rsl_rx_ipaccess(struct gsm_bts_trx *trx, struct msgb *msg) +{ + struct abis_rsl_dchan_hdr *dch = msgb_l2(msg); + int ret = 0; + + if (msgb_l2len(msg) < sizeof(*dch)) { + LOGP(DRSL, LOGL_NOTICE, "RSL ip.access message too short\n"); + rsl_tx_error_report(trx, RSL_ERR_PROTO, NULL, NULL, msg); + msgb_free(msg); + return -EIO; + } + msg->l3h = (unsigned char *)dch + sizeof(*dch); + + if (!chan_nr_is_dchan(dch->chan_nr)) + return rsl_reject_unknown_lchan(msg); + + msg->lchan = lchan_lookup(trx, dch->chan_nr, "RSL rx IPACC: "); + if (!msg->lchan) { + LOGP(DRSL, LOGL_ERROR, "Rx RSL %s for unknow lchan\n", + rsl_msg_name(dch->c.msg_type)); + return rsl_reject_unknown_lchan(msg); + } + + LOGP(DRSL, LOGL_INFO, "%s Rx RSL %s\n", gsm_lchan_name(msg->lchan), + rsl_ipac_msg_name(dch->c.msg_type)); + + switch (dch->c.msg_type) { + case RSL_MT_IPAC_CRCX: + case RSL_MT_IPAC_MDCX: + ret = rsl_rx_ipac_XXcx(msg); + break; + case RSL_MT_IPAC_DLCX: + ret = rsl_rx_ipac_dlcx(msg); + break; + default: + LOGP(DRSL, LOGL_NOTICE, "unsupported RSL ip.access msg_type 0x%02x\n", + dch->c.msg_type); + ret = -EINVAL; + } + + if (ret != 1) + msgb_free(msg); + return ret; +} + +int lchan_deactivate(struct gsm_lchan *lchan) +{ + OSMO_ASSERT(lchan); + + lchan->ciph_state = 0; + return bts_model_lchan_deactivate(lchan); +} + +int down_rsl(struct gsm_bts_trx *trx, struct msgb *msg) +{ + struct abis_rsl_common_hdr *rslh; + int ret = 0; + + OSMO_ASSERT(trx); + OSMO_ASSERT(msg); + + rslh = msgb_l2(msg); + + if (msgb_l2len(msg) < sizeof(*rslh)) { + LOGP(DRSL, LOGL_NOTICE, "RSL message too short\n"); + rsl_tx_error_report(trx, RSL_ERR_PROTO, NULL, NULL, msg); + msgb_free(msg); + return -EIO; + } + + switch (rslh->msg_discr & 0xfe) { + case ABIS_RSL_MDISC_RLL: + ret = rsl_rx_rll(trx, msg); + /* exception: RLL messages are _NOT_ freed as they are now + * owned by LAPDm which might have queued them */ + break; + case ABIS_RSL_MDISC_COM_CHAN: + ret = rsl_rx_cchan(trx, msg); + break; + case ABIS_RSL_MDISC_DED_CHAN: + ret = rsl_rx_dchan(trx, msg); + break; + case ABIS_RSL_MDISC_TRX: + ret = rsl_rx_trx(trx, msg); + break; + case ABIS_RSL_MDISC_IPACCESS: + ret = rsl_rx_ipaccess(trx, msg); + break; + default: + LOGP(DRSL, LOGL_NOTICE, "unknown RSL msg_discr 0x%02x\n", + rslh->msg_discr); + rsl_tx_error_report(trx, RSL_ERR_MSG_DISCR, NULL, NULL, msg); + msgb_free(msg); + ret = -EINVAL; + } + + /* we don't free here, as rsl_rx{cchan,dchan,trx,ipaccess,rll} are + * responsible for owning the msg */ + + return ret; +} diff --git a/src/common/scheduler.c b/src/common/scheduler.c new file mode 100644 index 0000000..8c9d30d --- /dev/null +++ b/src/common/scheduler.c @@ -0,0 +1,938 @@ +/* Scheduler for OsmoBTS-TRX */ + +/* (C) 2013 by Andreas Eversberg + * (C) 2015 by Alexander Chemeris + * (C) 2015 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +extern void *tall_bts_ctx; + +static int rts_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan); +static int rts_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan); +static int rts_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan); +/*! \brief Dummy Burst (TS 05.02 Chapter 5.2.6) */ +static const ubit_t dummy_burst[GSM_BURST_LEN] = { + 0,0,0, + 1,1,1,1,1,0,1,1,0,1,1,1,0,1,1,0,0,0,0,0,1,0,1,0,0,1,0,0,1,1,1,0, + 0,0,0,0,1,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,1,1,1,0,0, + 0,1,0,1,1,1,0,0,0,1,0,1,1,1,0,0,0,1,0,1,0,1,1,1,0,1,0,0,1,0,1,0, + 0,0,1,1,0,0,1,1,0,0,1,1,1,0,0,1,1,1,1,0,1,0,0,1,1,1,1,1,0,0,0,1, + 0,0,1,0,1,1,1,1,1,0,1,0,1,0, + 0,0,0, +}; + +/*! \brief FCCH Burst (TS 05.02 Chapter 5.2.4) */ +const ubit_t _sched_fcch_burst[GSM_BURST_LEN] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +}; + +/*! \brief Training Sequences (TS 05.02 Chapter 5.2.3) */ +const ubit_t _sched_tsc[8][26] = { + { 0,0,1,0,0,1,0,1,1,1,0,0,0,0,1,0,0,0,1,0,0,1,0,1,1,1, }, + { 0,0,1,0,1,1,0,1,1,1,0,1,1,1,1,0,0,0,1,0,1,1,0,1,1,1, }, + { 0,1,0,0,0,0,1,1,1,0,1,1,1,0,1,0,0,1,0,0,0,0,1,1,1,0, }, + { 0,1,0,0,0,1,1,1,1,0,1,1,0,1,0,0,0,1,0,0,0,1,1,1,1,0, }, + { 0,0,0,1,1,0,1,0,1,1,1,0,0,1,0,0,0,0,0,1,1,0,1,0,1,1, }, + { 0,1,0,0,1,1,1,0,1,0,1,1,0,0,0,0,0,1,0,0,1,1,1,0,1,0, }, + { 1,0,1,0,0,1,1,1,1,1,0,1,1,0,0,0,1,0,1,0,0,1,1,1,1,1, }, + { 1,1,1,0,1,1,1,1,0,0,0,1,0,0,1,0,1,1,1,0,1,1,1,1,0,0, }, +}; + +const ubit_t _sched_egprs_tsc[8][78] = { + { 1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,0,0,1,0,0, + 1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1, + 1,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,0,0,1,0,0,1,0,0,1, }, + { 1,1,1,1,1,1,0,0,1,1,1,1,0,0,1,0,0,1,1,1,1,0,0,1,0,0, + 1,0,0,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0,1,1,1,1,1,1,1,1, + 1,1,0,0,1,1,1,1,0,0,1,0,0,1,1,1,1,0,0,1,0,0,1,0,0,1, }, + { 1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0, + 1,1,1,1,0,0,1,0,0,1,0,0,1,1,1,1,0,0,1,1,1,1,1,1,1,0, + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,1,1,1, }, + { 1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0, + 1,1,1,1,0,0,1,0,0,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,0, + 0,1,1,1,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0,1,1,1,1, }, + { 1,1,1,1,1,1,1,1,1,0,0,1,0,0,1,1,1,1,0,0,1,1,1,1,0,0, + 1,0,0,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,0,0,1,0,0,1,1,1,1,0,0,1,1,1,1,0,0,1,0,0,1, }, + { 1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,1,1,1,0,0, + 1,1,1,1,0,0,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, + 0,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,1,1,1,0,0,1,1,1,1, }, + { 0,0,1,1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0, + 1,0,0,1,1,1,1,0,0,1,0,0,1,1,1,1,1,1,1,1,1,1,0,0,1,1, + 1,1,0,0,1,1,1,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1, }, + { 0,0,1,0,0,1,0,0,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0,1,1,1, + 1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,0,0,1,0, + 0,1,0,0,1,1,1,1,0,0,1,0,0,1,0,0,1,0,0,1,1,1,1,1,1,1, }, +}; + +/*! \brief SCH training sequence (TS 05.02 Chapter 5.2.5) */ +const ubit_t _sched_sch_train[64] = { + 1,0,1,1,1,0,0,1,0,1,1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,1,1,1,1, + 0,0,1,0,1,1,0,1,0,1,0,0,0,1,0,1,0,1,1,1,0,1,1,0,0,0,0,1,1,0,1,1, +}; + +/* + * subchannel description structure + */ + +const struct trx_chan_desc trx_chan_desc[_TRX_CHAN_MAX] = { + { 0, TRXC_IDLE, 0, LID_DEDIC, "IDLE", NULL, tx_idle_fn, NULL, 1 }, + { 0, TRXC_FCCH, 0, LID_DEDIC, "FCCH", NULL, tx_fcch_fn, NULL, 1 }, + { 0, TRXC_SCH, 0, LID_DEDIC, "SCH", NULL, tx_sch_fn, NULL, 1 }, + { 0, TRXC_BCCH, 0x80, LID_DEDIC, "BCCH", rts_data_fn, tx_data_fn, NULL, 1 }, + { 0, TRXC_RACH, 0x88, LID_DEDIC, "RACH", NULL, NULL, rx_rach_fn, 1 }, + { 0, TRXC_CCCH, 0x90, LID_DEDIC, "CCCH", rts_data_fn, tx_data_fn, NULL, 1 }, + { 0, TRXC_TCHF, 0x08, LID_DEDIC, "TCH/F", rts_tchf_fn, tx_tchf_fn, rx_tchf_fn, 0 }, + { 0, TRXC_TCHH_0, 0x10, LID_DEDIC, "TCH/H(0)", rts_tchh_fn, tx_tchh_fn, rx_tchh_fn, 0 }, + { 0, TRXC_TCHH_1, 0x18, LID_DEDIC, "TCH/H(1)", rts_tchh_fn, tx_tchh_fn, rx_tchh_fn, 0 }, + { 0, TRXC_SDCCH4_0, 0x20, LID_DEDIC, "SDCCH/4(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SDCCH4_1, 0x28, LID_DEDIC, "SDCCH/4(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SDCCH4_2, 0x30, LID_DEDIC, "SDCCH/4(2)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SDCCH4_3, 0x38, LID_DEDIC, "SDCCH/4(3)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SDCCH8_0, 0x40, LID_DEDIC, "SDCCH/8(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SDCCH8_1, 0x48, LID_DEDIC, "SDCCH/8(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SDCCH8_2, 0x50, LID_DEDIC, "SDCCH/8(2)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SDCCH8_3, 0x58, LID_DEDIC, "SDCCH/8(3)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SDCCH8_4, 0x60, LID_DEDIC, "SDCCH/8(4)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SDCCH8_5, 0x68, LID_DEDIC, "SDCCH/8(5)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SDCCH8_6, 0x70, LID_DEDIC, "SDCCH/8(6)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SDCCH8_7, 0x78, LID_DEDIC, "SDCCH/8(7)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCHTF, 0x08, LID_SACCH, "SACCH/TF", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCHTH_0, 0x10, LID_SACCH, "SACCH/TH(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCHTH_1, 0x18, LID_SACCH, "SACCH/TH(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCH4_0, 0x20, LID_SACCH, "SACCH/4(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCH4_1, 0x28, LID_SACCH, "SACCH/4(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCH4_2, 0x30, LID_SACCH, "SACCH/4(2)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCH4_3, 0x38, LID_SACCH, "SACCH/4(3)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCH8_0, 0x40, LID_SACCH, "SACCH/8(0)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCH8_1, 0x48, LID_SACCH, "SACCH/8(1)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCH8_2, 0x50, LID_SACCH, "SACCH/8(2)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCH8_3, 0x58, LID_SACCH, "SACCH/8(3)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCH8_4, 0x60, LID_SACCH, "SACCH/8(4)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCH8_5, 0x68, LID_SACCH, "SACCH/8(5)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCH8_6, 0x70, LID_SACCH, "SACCH/8(6)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 0, TRXC_SACCH8_7, 0x78, LID_SACCH, "SACCH/8(7)", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, + { 1, TRXC_PDTCH, 0xc0, LID_DEDIC, "PDTCH", rts_data_fn, tx_pdtch_fn, rx_pdtch_fn, 0 }, + { 1, TRXC_PTCCH, 0xc0, LID_DEDIC, "PTCCH", rts_data_fn, tx_data_fn, rx_data_fn, 0 }, +}; + +const struct value_string trx_chan_type_names[] = { + OSMO_VALUE_STRING(TRXC_IDLE), + OSMO_VALUE_STRING(TRXC_FCCH), + OSMO_VALUE_STRING(TRXC_SCH), + OSMO_VALUE_STRING(TRXC_BCCH), + OSMO_VALUE_STRING(TRXC_RACH), + OSMO_VALUE_STRING(TRXC_CCCH), + OSMO_VALUE_STRING(TRXC_TCHF), + OSMO_VALUE_STRING(TRXC_TCHH_0), + OSMO_VALUE_STRING(TRXC_TCHH_1), + OSMO_VALUE_STRING(TRXC_SDCCH4_0), + OSMO_VALUE_STRING(TRXC_SDCCH4_1), + OSMO_VALUE_STRING(TRXC_SDCCH4_2), + OSMO_VALUE_STRING(TRXC_SDCCH4_3), + OSMO_VALUE_STRING(TRXC_SDCCH8_0), + OSMO_VALUE_STRING(TRXC_SDCCH8_1), + OSMO_VALUE_STRING(TRXC_SDCCH8_2), + OSMO_VALUE_STRING(TRXC_SDCCH8_3), + OSMO_VALUE_STRING(TRXC_SDCCH8_4), + OSMO_VALUE_STRING(TRXC_SDCCH8_5), + OSMO_VALUE_STRING(TRXC_SDCCH8_6), + OSMO_VALUE_STRING(TRXC_SDCCH8_7), + OSMO_VALUE_STRING(TRXC_SACCHTF), + OSMO_VALUE_STRING(TRXC_SACCHTH_0), + OSMO_VALUE_STRING(TRXC_SACCHTH_1), + OSMO_VALUE_STRING(TRXC_SACCH4_0), + OSMO_VALUE_STRING(TRXC_SACCH4_1), + OSMO_VALUE_STRING(TRXC_SACCH4_2), + OSMO_VALUE_STRING(TRXC_SACCH4_3), + OSMO_VALUE_STRING(TRXC_SACCH8_0), + OSMO_VALUE_STRING(TRXC_SACCH8_1), + OSMO_VALUE_STRING(TRXC_SACCH8_2), + OSMO_VALUE_STRING(TRXC_SACCH8_3), + OSMO_VALUE_STRING(TRXC_SACCH8_4), + OSMO_VALUE_STRING(TRXC_SACCH8_5), + OSMO_VALUE_STRING(TRXC_SACCH8_6), + OSMO_VALUE_STRING(TRXC_SACCH8_7), + OSMO_VALUE_STRING(TRXC_PDTCH), + OSMO_VALUE_STRING(TRXC_PTCCH), + OSMO_VALUE_STRING(_TRX_CHAN_MAX), + { 0, NULL } +}; + +/* + * init / exit + */ + +int trx_sched_init(struct l1sched_trx *l1t, struct gsm_bts_trx *trx) +{ + uint8_t tn; + unsigned int i; + + if (!trx) + return -EINVAL; + + l1t->trx = trx; + + LOGP(DL1C, LOGL_NOTICE, "Init scheduler for trx=%u\n", l1t->trx->nr); + + for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) { + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + + l1ts->mf_index = 0; + l1ts->mf_last_fn = 0; + INIT_LLIST_HEAD(&l1ts->dl_prims); + for (i = 0; i < ARRAY_SIZE(l1ts->chan_state); i++) { + struct l1sched_chan_state *chan_state; + chan_state = &l1ts->chan_state[i]; + chan_state->active = 0; + } + } + + return 0; +} + +void trx_sched_exit(struct l1sched_trx *l1t) +{ + struct gsm_bts_trx_ts *ts; + uint8_t tn; + int i; + + LOGP(DL1C, LOGL_NOTICE, "Exit scheduler for trx=%u\n", l1t->trx->nr); + + for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) { + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + msgb_queue_flush(&l1ts->dl_prims); + for (i = 0; i < _TRX_CHAN_MAX; i++) { + struct l1sched_chan_state *chan_state; + chan_state = &l1ts->chan_state[i]; + if (chan_state->dl_bursts) { + talloc_free(chan_state->dl_bursts); + chan_state->dl_bursts = NULL; + } + if (chan_state->ul_bursts) { + talloc_free(chan_state->ul_bursts); + chan_state->ul_bursts = NULL; + } + } + /* clear lchan channel states */ + ts = &l1t->trx->ts[tn]; + for (i = 0; i < ARRAY_SIZE(ts->lchan); i++) + lchan_set_state(&ts->lchan[i], LCHAN_S_NONE); + } +} + +/* close all logical channels and reset timeslots */ +void trx_sched_reset(struct l1sched_trx *l1t) +{ + trx_sched_exit(l1t); + trx_sched_init(l1t, l1t->trx); +} + +struct msgb *_sched_dequeue_prim(struct l1sched_trx *l1t, int8_t tn, uint32_t fn, + enum trx_chan_type chan) +{ + struct msgb *msg, *msg2; + struct osmo_phsap_prim *l1sap; + uint32_t prim_fn; + uint8_t chan_nr, link_id; + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + + /* get prim of current fn from queue */ + llist_for_each_entry_safe(msg, msg2, &l1ts->dl_prims, list) { + l1sap = msgb_l1sap_prim(msg); + if (l1sap->oph.operation != PRIM_OP_REQUEST) { +wrong_type: + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Prim has wrong type.\n"); +free_msg: + /* unlink and free message */ + llist_del(&msg->list); + msgb_free(msg); + return NULL; + } + switch (l1sap->oph.primitive) { + case PRIM_PH_DATA: + chan_nr = l1sap->u.data.chan_nr; + link_id = l1sap->u.data.link_id; + prim_fn = ((l1sap->u.data.fn + GSM_HYPERFRAME - fn) % GSM_HYPERFRAME); + break; + case PRIM_TCH: + chan_nr = l1sap->u.tch.chan_nr; + link_id = 0; + prim_fn = ((l1sap->u.tch.fn + GSM_HYPERFRAME - fn) % GSM_HYPERFRAME); + break; + default: + goto wrong_type; + } + if (prim_fn > 100) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, + "Prim %u is out of range (100), or channel %s with " + "type %s is already disabled. If this happens in " + "conjunction with PCU, increase 'rts-advance' by 5.\n", + prim_fn, get_lchan_by_chan_nr(l1t->trx, chan_nr)->name, + get_value_string(trx_chan_type_names, chan)); + /* unlink and free message */ + llist_del(&msg->list); + msgb_free(msg); + continue; + } + if (prim_fn > 0) + continue; + + goto found_msg; + } + + return NULL; + +found_msg: + if ((chan_nr ^ (trx_chan_desc[chan].chan_nr | tn)) + || ((link_id & 0xc0) ^ trx_chan_desc[chan].link_id)) { + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Prim has wrong chan_nr=%02x link_id=%02x, " + "expecting chan_nr=%02x link_id=%02x.\n", chan_nr, link_id, + trx_chan_desc[chan].chan_nr | tn, trx_chan_desc[chan].link_id); + goto free_msg; + } + + /* unlink and return message */ + llist_del(&msg->list); + return msg; +} + +int _sched_compose_ph_data_ind(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t *l2, + uint8_t l2_len, float rssi, + int16_t ta_offs_256bits, int16_t link_qual_cb, + uint16_t ber10k, + enum osmo_ph_pres_info_type presence_info) +{ + struct msgb *msg; + struct osmo_phsap_prim *l1sap; + uint8_t chan_nr = trx_chan_desc[chan].chan_nr | tn; + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + + /* compose primitive */ + msg = l1sap_msgb_alloc(l2_len); + l1sap = msgb_l1sap_prim(msg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA, + PRIM_OP_INDICATION, msg); + l1sap->u.data.chan_nr = chan_nr; + l1sap->u.data.link_id = trx_chan_desc[chan].link_id; + l1sap->u.data.fn = fn; + l1sap->u.data.rssi = (int8_t) (rssi); + l1sap->u.data.ber10k = ber10k; + l1sap->u.data.ta_offs_256bits = ta_offs_256bits; + l1sap->u.data.lqual_cb = link_qual_cb; + l1sap->u.data.pdch_presence_info = presence_info; + msg->l2h = msgb_put(msg, l2_len); + if (l2_len) + memcpy(msg->l2h, l2, l2_len); + + if (L1SAP_IS_LINK_SACCH(trx_chan_desc[chan].link_id)) + l1ts->chan_state[chan].lost = 0; + + /* forward primitive */ + l1sap_up(l1t->trx, l1sap); + + return 0; +} + +int _sched_compose_tch_ind(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t *tch, uint8_t tch_len) +{ + struct msgb *msg; + struct osmo_phsap_prim *l1sap; + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + + /* compose primitive */ + msg = l1sap_msgb_alloc(tch_len); + l1sap = msgb_l1sap_prim(msg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH, + PRIM_OP_INDICATION, msg); + l1sap->u.tch.chan_nr = trx_chan_desc[chan].chan_nr | tn; + l1sap->u.tch.fn = fn; + msg->l2h = msgb_put(msg, tch_len); + if (tch_len) + memcpy(msg->l2h, tch, tch_len); + + if (l1ts->chan_state[chan].lost) + l1ts->chan_state[chan].lost--; + + /* forward primitive */ + l1sap_up(l1t->trx, l1sap); + + return 0; +} + + + +/* + * data request (from upper layer) + */ + +int trx_sched_ph_data_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap) +{ + uint8_t tn = l1sap->u.data.chan_nr & 7; + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + + LOGL1S(DL1P, LOGL_INFO, l1t, tn, -1, l1sap->u.data.fn, + "PH-DATA.req: chan_nr=0x%02x link_id=0x%02x\n", + l1sap->u.data.chan_nr, l1sap->u.data.link_id); + + if (!l1sap->oph.msg) + abort(); + + /* ignore empty frame */ + if (!msgb_l2len(l1sap->oph.msg)) { + msgb_free(l1sap->oph.msg); + return 0; + } + + msgb_enqueue(&l1ts->dl_prims, l1sap->oph.msg); + + return 0; +} + +int trx_sched_tch_req(struct l1sched_trx *l1t, struct osmo_phsap_prim *l1sap) +{ + uint8_t tn = l1sap->u.tch.chan_nr & 7; + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + + LOGL1S(DL1P, LOGL_INFO, l1t, tn, -1, l1sap->u.tch.fn, "TCH.req: chan_nr=0x%02x\n", + l1sap->u.tch.chan_nr); + + if (!l1sap->oph.msg) + abort(); + + /* ignore empty frame */ + if (!msgb_l2len(l1sap->oph.msg)) { + msgb_free(l1sap->oph.msg); + return 0; + } + + msgb_enqueue(&l1ts->dl_prims, l1sap->oph.msg); + + return 0; +} + + +/* + * ready-to-send indication (to upper layer) + */ + +/* RTS for data frame */ +static int rts_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan) +{ + uint8_t chan_nr, link_id; + struct msgb *msg; + struct osmo_phsap_prim *l1sap; + + /* get data for RTS indication */ + chan_nr = trx_chan_desc[chan].chan_nr | tn; + link_id = trx_chan_desc[chan].link_id; + + if (!chan_nr) { + LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, + "RTS func with non-existing chan_nr %d\n", chan_nr); + return -ENODEV; + } + + LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, + "PH-RTS.ind: chan_nr=0x%02x link_id=0x%02x\n", chan_nr, link_id); + + /* generate prim */ + msg = l1sap_msgb_alloc(200); + if (!msg) + return -ENOMEM; + l1sap = msgb_l1sap_prim(msg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RTS, + PRIM_OP_INDICATION, msg); + l1sap->u.data.chan_nr = chan_nr; + l1sap->u.data.link_id = link_id; + l1sap->u.data.fn = fn; + + return l1sap_up(l1t->trx, l1sap); +} + +static int rts_tch_common(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, int facch) +{ + uint8_t chan_nr, link_id; + struct msgb *msg; + struct osmo_phsap_prim *l1sap; + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + int rc = 0; + + /* get data for RTS indication */ + chan_nr = trx_chan_desc[chan].chan_nr | tn; + link_id = trx_chan_desc[chan].link_id; + + if (!chan_nr) { + LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, + "RTS func with non-existing chan_nr %d\n", chan_nr); + return -ENODEV; + } + + LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "TCH RTS.ind: chan_nr=0x%02x\n", chan_nr); + + /* only send, if FACCH is selected */ + if (facch) { + /* generate prim */ + msg = l1sap_msgb_alloc(200); + if (!msg) + return -ENOMEM; + l1sap = msgb_l1sap_prim(msg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RTS, + PRIM_OP_INDICATION, msg); + l1sap->u.data.chan_nr = chan_nr; + l1sap->u.data.link_id = link_id; + l1sap->u.data.fn = fn; + + rc = l1sap_up(l1t->trx, l1sap); + } + + /* dont send, if TCH is in signalling only mode */ + if (l1ts->chan_state[chan].rsl_cmode != RSL_CMOD_SPD_SIGN) { + /* generate prim */ + msg = l1sap_msgb_alloc(200); + if (!msg) + return -ENOMEM; + l1sap = msgb_l1sap_prim(msg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH_RTS, + PRIM_OP_INDICATION, msg); + l1sap->u.tch.chan_nr = chan_nr; + l1sap->u.tch.fn = fn; + + return l1sap_up(l1t->trx, l1sap); + } + + return rc; +} + +/* RTS for full rate traffic frame */ +static int rts_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan) +{ + /* TCH/F may include FACCH on every 4th burst */ + return rts_tch_common(l1t, tn, fn, chan, 1); +} + + +/* RTS for half rate traffic frame */ +static int rts_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan) +{ + /* the FN 4/5, 13/14, 21/22 defines that FACCH may be included. */ + return rts_tch_common(l1t, tn, fn, chan, ((fn % 26) >> 2) & 1); +} + +/* set multiframe scheduler to given pchan */ +int trx_sched_set_pchan(struct l1sched_trx *l1t, uint8_t tn, + enum gsm_phys_chan_config pchan) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + int i; + + i = find_sched_mframe_idx(pchan, tn); + if (i < 0) { + LOGP(DL1C, LOGL_NOTICE, "Failed to configure multiframe " + "trx=%d ts=%d\n", l1t->trx->nr, tn); + return -ENOTSUP; + } + l1ts->mf_index = i; + l1ts->mf_period = trx_sched_multiframes[i].period; + l1ts->mf_frames = trx_sched_multiframes[i].frames; + LOGP(DL1C, LOGL_NOTICE, "Configuring multiframe with %s trx=%d ts=%d\n", + trx_sched_multiframes[i].name, l1t->trx->nr, tn); + return 0; +} + +/* setting all logical channels given attributes to active/inactive */ +int trx_sched_set_lchan(struct l1sched_trx *l1t, uint8_t chan_nr, uint8_t link_id, + int active) +{ + uint8_t tn = L1SAP_CHAN2TS(chan_nr); + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + uint8_t ss = l1sap_chan2ss(chan_nr); + int i; + int rc = -EINVAL; + + /* look for all matching chan_nr/link_id */ + for (i = 0; i < _TRX_CHAN_MAX; i++) { + struct l1sched_chan_state *chan_state; + chan_state = &l1ts->chan_state[i]; + /* skip if pchan type does not match pdch flag */ + if ((trx_sched_multiframes[l1ts->mf_index].pchan + == GSM_PCHAN_PDCH) + != trx_chan_desc[i].pdch) + continue; + if (trx_chan_desc[i].chan_nr == (chan_nr & 0xf8) + && trx_chan_desc[i].link_id == link_id) { + rc = 0; + if (chan_state->active == active) + continue; + LOGP(DL1C, LOGL_NOTICE, "%s %s on trx=%d ts=%d\n", + (active) ? "Activating" : "Deactivating", + trx_chan_desc[i].name, l1t->trx->nr, tn); + if (active) + memset(chan_state, 0, sizeof(*chan_state)); + chan_state->active = active; + /* free burst memory, to cleanly start with burst 0 */ + if (chan_state->dl_bursts) { + talloc_free(chan_state->dl_bursts); + chan_state->dl_bursts = NULL; + } + if (chan_state->ul_bursts) { + talloc_free(chan_state->ul_bursts); + chan_state->ul_bursts = NULL; + } + if (!active) + chan_state->ho_rach_detect = 0; + } + } + + /* disable handover detection (on deactivation) */ + if (!active) + _sched_act_rach_det(l1t, tn, ss, 0); + + return rc; +} + +/* setting all logical channels given attributes to active/inactive */ +int trx_sched_set_mode(struct l1sched_trx *l1t, uint8_t chan_nr, uint8_t rsl_cmode, + uint8_t tch_mode, int codecs, uint8_t codec0, uint8_t codec1, + uint8_t codec2, uint8_t codec3, uint8_t initial_id, uint8_t handover) +{ + uint8_t tn = L1SAP_CHAN2TS(chan_nr); + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + uint8_t ss = l1sap_chan2ss(chan_nr); + int i; + int rc = -EINVAL; + struct l1sched_chan_state *chan_state; + + /* no mode for PDCH */ + if (trx_sched_multiframes[l1ts->mf_index].pchan == GSM_PCHAN_PDCH) + return 0; + + /* look for all matching chan_nr/link_id */ + for (i = 0; i < _TRX_CHAN_MAX; i++) { + if (trx_chan_desc[i].chan_nr == (chan_nr & 0xf8) + && trx_chan_desc[i].link_id == 0x00) { + chan_state = &l1ts->chan_state[i]; + LOGP(DL1C, LOGL_NOTICE, "Set mode %u, %u, handover %u " + "on %s of trx=%d ts=%d\n", rsl_cmode, tch_mode, + handover, trx_chan_desc[i].name, l1t->trx->nr, + tn); + chan_state->rsl_cmode = rsl_cmode; + chan_state->tch_mode = tch_mode; + chan_state->ho_rach_detect = handover; + if (rsl_cmode == RSL_CMOD_SPD_SPEECH + && tch_mode == GSM48_CMODE_SPEECH_AMR) { + chan_state->codecs = codecs; + chan_state->codec[0] = codec0; + chan_state->codec[1] = codec1; + chan_state->codec[2] = codec2; + chan_state->codec[3] = codec3; + chan_state->ul_ft = initial_id; + chan_state->dl_ft = initial_id; + chan_state->ul_cmr = initial_id; + chan_state->dl_cmr = initial_id; + chan_state->ber_sum = 0; + chan_state->ber_num = 0; + } + rc = 0; + } + } + + /* command rach detection + * always enable handover, even if state is still set (due to loss + * of transceiver link). + * disable handover, if state is still set, since we might not know + * the actual state of transceiver (due to loss of link) */ + _sched_act_rach_det(l1t, tn, ss, handover); + + return rc; +} + +/* setting cipher on logical channels */ +int trx_sched_set_cipher(struct l1sched_trx *l1t, uint8_t chan_nr, int downlink, + int algo, uint8_t *key, int key_len) +{ + uint8_t tn = L1SAP_CHAN2TS(chan_nr); + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + int i; + int rc = -EINVAL; + struct l1sched_chan_state *chan_state; + + /* no cipher for PDCH */ + if (trx_sched_multiframes[l1ts->mf_index].pchan == GSM_PCHAN_PDCH) + return 0; + + /* no algorithm given means a5/0 */ + if (algo <= 0) + algo = 0; + else if (key_len != 8) { + LOGP(DL1C, LOGL_ERROR, "Algo A5/%d not supported with given " + "key len=%d\n", algo, key_len); + return -ENOTSUP; + } + + /* look for all matching chan_nr */ + for (i = 0; i < _TRX_CHAN_MAX; i++) { + /* skip if pchan type */ + if (trx_chan_desc[i].pdch) + continue; + if (trx_chan_desc[i].chan_nr == (chan_nr & 0xf8)) { + chan_state = &l1ts->chan_state[i]; + LOGP(DL1C, LOGL_NOTICE, "Set a5/%d %s for %s on trx=%d " + "ts=%d\n", algo, + (downlink) ? "downlink" : "uplink", + trx_chan_desc[i].name, l1t->trx->nr, tn); + if (downlink) { + chan_state->dl_encr_algo = algo; + memcpy(chan_state->dl_encr_key, key, key_len); + chan_state->dl_encr_key_len = key_len; + } else { + chan_state->ul_encr_algo = algo; + memcpy(chan_state->ul_encr_key, key, key_len); + chan_state->ul_encr_key_len = key_len; + } + rc = 0; + } + } + + return rc; +} + +/* process ready-to-send */ +int _sched_rts(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + const struct trx_sched_frame *frame; + uint8_t offset, period, bid; + trx_sched_rts_func *func; + enum trx_chan_type chan; + + /* no multiframe set */ + if (!l1ts->mf_index) + return 0; + + /* get frame from multiframe */ + period = l1ts->mf_period; + offset = fn % period; + frame = l1ts->mf_frames + offset; + + chan = frame->dl_chan; + bid = frame->dl_bid; + func = trx_chan_desc[frame->dl_chan].rts_fn; + + /* only on bid == 0 */ + if (bid != 0) + return 0; + + /* no RTS function */ + if (!func) + return 0; + + /* check if channel is active */ + if (!trx_chan_desc[chan].auto_active + && !l1ts->chan_state[chan].active) + return -EINVAL; + + return func(l1t, tn, fn, frame->dl_chan); +} + +/* process downlink burst */ +const ubit_t *_sched_dl_burst(struct l1sched_trx *l1t, uint8_t tn, + uint32_t fn, uint16_t *nbits) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct l1sched_chan_state *l1cs; + const struct trx_sched_frame *frame; + uint8_t offset, period, bid; + trx_sched_dl_func *func; + enum trx_chan_type chan; + ubit_t *bits = NULL; + + if (!l1ts->mf_index) + goto no_data; + + /* get frame from multiframe */ + period = l1ts->mf_period; + offset = fn % period; + frame = l1ts->mf_frames + offset; + + chan = frame->dl_chan; + bid = frame->dl_bid; + func = trx_chan_desc[chan].dl_fn; + + l1cs = &l1ts->chan_state[chan]; + + /* check if channel is active */ + if (!trx_chan_desc[chan].auto_active && !l1cs->active) { + if (nbits) + *nbits = GSM_BURST_LEN; + goto no_data; + } + + /* get burst from function */ + bits = func(l1t, tn, fn, chan, bid, nbits); + + /* encrypt */ + if (bits && l1cs->dl_encr_algo) { + ubit_t ks[114]; + int i; + + osmo_a5(l1cs->dl_encr_algo, l1cs->dl_encr_key, fn, ks, NULL); + for (i = 0; i < 57; i++) { + bits[i + 3] ^= ks[i]; + bits[i + 88] ^= ks[i + 57]; + } + } + +no_data: + /* in case of C0, we need a dummy burst to maintain RF power */ + if (bits == NULL && l1t->trx == l1t->trx->bts->c0) { +#if 0 + if (chan != TRXC_IDLE) // hack + LOGP(DL1C, LOGL_DEBUG, "No burst data for %s fn=%u ts=%u " + "burst=%d on C0, so filling with dummy burst\n", + trx_chan_desc[chan].name, fn, tn, bid); +#endif + bits = (ubit_t *) dummy_burst; + } + + return bits; +} + +/* process uplink burst */ +int trx_sched_ul_burst(struct l1sched_trx *l1t, uint8_t tn, uint32_t current_fn, + sbit_t *bits, uint16_t nbits, int8_t rssi, int16_t toa256) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct l1sched_chan_state *l1cs; + const struct trx_sched_frame *frame; + uint8_t offset, period, bid; + trx_sched_ul_func *func; + enum trx_chan_type chan; + uint32_t fn, elapsed; + + if (!l1ts->mf_index) + return -EINVAL; + + /* calculate how many frames have been elapsed */ + elapsed = (current_fn + GSM_HYPERFRAME - l1ts->mf_last_fn) % GSM_HYPERFRAME; + + /* start counting from last fn + 1, but only if not too many fn have + * been elapsed */ + if (elapsed < 10) + fn = (l1ts->mf_last_fn + 1) % GSM_HYPERFRAME; + else + fn = current_fn; + + while (42) { + /* get frame from multiframe */ + period = l1ts->mf_period; + offset = fn % period; + frame = l1ts->mf_frames + offset; + + chan = frame->ul_chan; + bid = frame->ul_bid; + func = trx_chan_desc[chan].ul_fn; + + l1cs = &l1ts->chan_state[chan]; + + /* check if channel is active */ + if (!trx_chan_desc[chan].auto_active && !l1cs->active) + goto next_frame; + + /* omit bursts which have no handler, like IDLE bursts */ + if (!func) + goto next_frame; + + /* put burst to function */ + if (fn == current_fn) { + /* decrypt */ + if (bits && l1cs->ul_encr_algo) { + ubit_t ks[114]; + int i; + + osmo_a5(l1cs->ul_encr_algo, + l1cs->ul_encr_key, + fn, NULL, ks); + for (i = 0; i < 57; i++) { + if (ks[i]) + bits[i + 3] = - bits[i + 3]; + if (ks[i + 57]) + bits[i + 88] = - bits[i + 88]; + } + } + + func(l1t, tn, fn, chan, bid, bits, nbits, rssi, toa256); + } else if (chan != TRXC_RACH && !l1cs->ho_rach_detect) { + sbit_t spare[GSM_BURST_LEN]; + memset(spare, 0, GSM_BURST_LEN); + /* We missed a couple of frame numbers (system overload?) and are now + * substituting some zero-filled bursts for those bursts we missed */ + LOGPFN(DL1P, LOGL_ERROR, fn, "Substituting all-zero burst (current_fn=%u, " + "elapsed=%u\n", current_fn, elapsed); + func(l1t, tn, fn, chan, bid, spare, GSM_BURST_LEN, -128, 0); + } + +next_frame: + /* reached current fn */ + if (fn == current_fn) + break; + + fn = (fn + 1) % GSM_HYPERFRAME; + } + + l1ts->mf_last_fn = fn; + + return 0; +} + +struct l1sched_ts *l1sched_trx_get_ts(struct l1sched_trx *l1t, uint8_t tn) +{ + OSMO_ASSERT(tn < ARRAY_SIZE(l1t->ts)); + return &l1t->ts[tn]; +} diff --git a/src/common/scheduler_mframe.c b/src/common/scheduler_mframe.c new file mode 100644 index 0000000..ab0b5b6 --- /dev/null +++ b/src/common/scheduler_mframe.c @@ -0,0 +1,824 @@ +/* Scheduler for OsmoBTS-TRX */ + +/* (C) 2013 by Andreas Eversberg + * (C) 2015 by Alexander Chemeris + * (C) 2015-2017 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + + +/* + * multiframe structure + */ + +static const struct trx_sched_frame frame_bcch[51] = { +/* dl_chan dl_bid ul_chan ul_bid */ + { TRXC_FCCH, 0, TRXC_RACH, 0 }, + { TRXC_SCH, 0, TRXC_RACH, 0 }, + { TRXC_BCCH, 0, TRXC_RACH, 0 }, { TRXC_BCCH, 1, TRXC_RACH, 0 }, { TRXC_BCCH, 2, TRXC_RACH, 0 }, { TRXC_BCCH, 3, TRXC_RACH, 0 }, + { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 }, + { TRXC_FCCH, 0, TRXC_RACH, 0 }, + { TRXC_SCH, 0, TRXC_RACH, 0 }, + { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 }, + { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 }, + { TRXC_FCCH, 0, TRXC_RACH, 0 }, + { TRXC_SCH, 0, TRXC_RACH, 0 }, + { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 }, + { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 }, + { TRXC_FCCH, 0, TRXC_RACH, 0 }, + { TRXC_SCH, 0, TRXC_RACH, 0 }, + { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 }, + { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 }, + { TRXC_FCCH, 0, TRXC_RACH, 0 }, + { TRXC_SCH, 0, TRXC_RACH, 0 }, + { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 }, + { TRXC_CCCH, 0, TRXC_RACH, 0 }, { TRXC_CCCH, 1, TRXC_RACH, 0 }, { TRXC_CCCH, 2, TRXC_RACH, 0 }, { TRXC_CCCH, 3, TRXC_RACH, 0 }, + { TRXC_IDLE, 0, TRXC_RACH, 0 }, +}; + +static const struct trx_sched_frame frame_bcch_sdcch4[102] = { +/* dl_chan dl_bid ul_chan ul_bid */ + { TRXC_FCCH, 0, TRXC_SDCCH4_3, 0 }, + { TRXC_SCH, 0, TRXC_SDCCH4_3, 1 }, + { TRXC_BCCH, 0, TRXC_SDCCH4_3, 2 }, + { TRXC_BCCH, 1, TRXC_SDCCH4_3, 3 }, + { TRXC_BCCH, 2, TRXC_RACH, 0 }, + { TRXC_BCCH, 3, TRXC_RACH, 0 }, + { TRXC_CCCH, 0, TRXC_SACCH4_2, 0 }, + { TRXC_CCCH, 1, TRXC_SACCH4_2, 1 }, + { TRXC_CCCH, 2, TRXC_SACCH4_2, 2 }, + { TRXC_CCCH, 3, TRXC_SACCH4_2, 3 }, + { TRXC_FCCH, 0, TRXC_SACCH4_3, 0 }, + { TRXC_SCH, 0, TRXC_SACCH4_3, 1 }, + { TRXC_CCCH, 0, TRXC_SACCH4_3, 2 }, + { TRXC_CCCH, 1, TRXC_SACCH4_3, 3 }, + { TRXC_CCCH, 2, TRXC_RACH, 0 }, + { TRXC_CCCH, 3, TRXC_RACH, 0 }, + { TRXC_CCCH, 0, TRXC_RACH, 0 }, + { TRXC_CCCH, 1, TRXC_RACH, 0 }, + { TRXC_CCCH, 2, TRXC_RACH, 0 }, + { TRXC_CCCH, 3, TRXC_RACH, 0 }, + { TRXC_FCCH, 0, TRXC_RACH, 0 }, + { TRXC_SCH, 0, TRXC_RACH, 0 }, + { TRXC_SDCCH4_0, 0, TRXC_RACH, 0 }, + { TRXC_SDCCH4_0, 1, TRXC_RACH, 0 }, + { TRXC_SDCCH4_0, 2, TRXC_RACH, 0 }, + { TRXC_SDCCH4_0, 3, TRXC_RACH, 0 }, + { TRXC_SDCCH4_1, 0, TRXC_RACH, 0 }, + { TRXC_SDCCH4_1, 1, TRXC_RACH, 0 }, + { TRXC_SDCCH4_1, 2, TRXC_RACH, 0 }, + { TRXC_SDCCH4_1, 3, TRXC_RACH, 0 }, + { TRXC_FCCH, 0, TRXC_RACH, 0 }, + { TRXC_SCH, 0, TRXC_RACH, 0 }, + { TRXC_SDCCH4_2, 0, TRXC_RACH, 0 }, + { TRXC_SDCCH4_2, 1, TRXC_RACH, 0 }, + { TRXC_SDCCH4_2, 2, TRXC_RACH, 0 }, + { TRXC_SDCCH4_2, 3, TRXC_RACH, 0 }, + { TRXC_SDCCH4_3, 0, TRXC_RACH, 0 }, + { TRXC_SDCCH4_3, 1, TRXC_SDCCH4_0, 0 }, + { TRXC_SDCCH4_3, 2, TRXC_SDCCH4_0, 1 }, + { TRXC_SDCCH4_3, 3, TRXC_SDCCH4_0, 2 }, + { TRXC_FCCH, 0, TRXC_SDCCH4_0, 3 }, + { TRXC_SCH, 0, TRXC_SDCCH4_1, 0 }, + { TRXC_SACCH4_0, 0, TRXC_SDCCH4_1, 1 }, + { TRXC_SACCH4_0, 1, TRXC_SDCCH4_1, 2 }, + { TRXC_SACCH4_0, 2, TRXC_SDCCH4_1, 3 }, + { TRXC_SACCH4_0, 3, TRXC_RACH, 0 }, + { TRXC_SACCH4_1, 0, TRXC_RACH, 0 }, + { TRXC_SACCH4_1, 1, TRXC_SDCCH4_2, 0 }, + { TRXC_SACCH4_1, 2, TRXC_SDCCH4_2, 1 }, + { TRXC_SACCH4_1, 3, TRXC_SDCCH4_2, 2 }, + { TRXC_IDLE, 0, TRXC_SDCCH4_2, 3 }, + + { TRXC_FCCH, 0, TRXC_SDCCH4_3, 0 }, + { TRXC_SCH, 0, TRXC_SDCCH4_3, 1 }, + { TRXC_BCCH, 0, TRXC_SDCCH4_3, 2 }, + { TRXC_BCCH, 1, TRXC_SDCCH4_3, 3 }, + { TRXC_BCCH, 2, TRXC_RACH, 0 }, + { TRXC_BCCH, 3, TRXC_RACH, 0 }, + { TRXC_CCCH, 0, TRXC_SACCH4_0, 0 }, + { TRXC_CCCH, 1, TRXC_SACCH4_0, 1 }, + { TRXC_CCCH, 2, TRXC_SACCH4_0, 2 }, + { TRXC_CCCH, 3, TRXC_SACCH4_0, 3 }, + { TRXC_FCCH, 0, TRXC_SACCH4_1, 0 }, + { TRXC_SCH, 0, TRXC_SACCH4_1, 1 }, + { TRXC_CCCH, 0, TRXC_SACCH4_1, 2 }, + { TRXC_CCCH, 1, TRXC_SACCH4_1, 3 }, + { TRXC_CCCH, 2, TRXC_RACH, 0 }, + { TRXC_CCCH, 3, TRXC_RACH, 0 }, + { TRXC_CCCH, 0, TRXC_RACH, 0 }, + { TRXC_CCCH, 1, TRXC_RACH, 0 }, + { TRXC_CCCH, 2, TRXC_RACH, 0 }, + { TRXC_CCCH, 3, TRXC_RACH, 0 }, + { TRXC_FCCH, 0, TRXC_RACH, 0 }, + { TRXC_SCH, 0, TRXC_RACH, 0 }, + { TRXC_SDCCH4_0, 0, TRXC_RACH, 0 }, + { TRXC_SDCCH4_0, 1, TRXC_RACH, 0 }, + { TRXC_SDCCH4_0, 2, TRXC_RACH, 0 }, + { TRXC_SDCCH4_0, 3, TRXC_RACH, 0 }, + { TRXC_SDCCH4_1, 0, TRXC_RACH, 0 }, + { TRXC_SDCCH4_1, 1, TRXC_RACH, 0 }, + { TRXC_SDCCH4_1, 2, TRXC_RACH, 0 }, + { TRXC_SDCCH4_1, 3, TRXC_RACH, 0 }, + { TRXC_FCCH, 0, TRXC_RACH, 0 }, + { TRXC_SCH, 0, TRXC_RACH, 0 }, + { TRXC_SDCCH4_2, 0, TRXC_RACH, 0 }, + { TRXC_SDCCH4_2, 1, TRXC_RACH, 0 }, + { TRXC_SDCCH4_2, 2, TRXC_RACH, 0 }, + { TRXC_SDCCH4_2, 3, TRXC_RACH, 0 }, + { TRXC_SDCCH4_3, 0, TRXC_RACH, 0 }, + { TRXC_SDCCH4_3, 1, TRXC_SDCCH4_0, 0 }, + { TRXC_SDCCH4_3, 2, TRXC_SDCCH4_0, 1 }, + { TRXC_SDCCH4_3, 3, TRXC_SDCCH4_0, 2 }, + { TRXC_FCCH, 0, TRXC_SDCCH4_0, 3 }, + { TRXC_SCH, 0, TRXC_SDCCH4_1, 0 }, + { TRXC_SACCH4_2, 0, TRXC_SDCCH4_1, 1 }, + { TRXC_SACCH4_2, 1, TRXC_SDCCH4_1, 2 }, + { TRXC_SACCH4_2, 2, TRXC_SDCCH4_1, 3 }, + { TRXC_SACCH4_2, 3, TRXC_RACH, 0 }, + { TRXC_SACCH4_3, 0, TRXC_RACH, 0 }, + { TRXC_SACCH4_3, 1, TRXC_SDCCH4_2, 0 }, + { TRXC_SACCH4_3, 2, TRXC_SDCCH4_2, 1 }, + { TRXC_SACCH4_3, 3, TRXC_SDCCH4_2, 2 }, + { TRXC_IDLE, 0, TRXC_SDCCH4_2, 3 }, +}; + +static const struct trx_sched_frame frame_sdcch8[102] = { +/* dl_chan dl_bid ul_chan ul_bid */ + { TRXC_SDCCH8_0, 0, TRXC_SACCH8_5, 0 }, + { TRXC_SDCCH8_0, 1, TRXC_SACCH8_5, 1 }, + { TRXC_SDCCH8_0, 2, TRXC_SACCH8_5, 2 }, + { TRXC_SDCCH8_0, 3, TRXC_SACCH8_5, 3 }, + { TRXC_SDCCH8_1, 0, TRXC_SACCH8_6, 0 }, + { TRXC_SDCCH8_1, 1, TRXC_SACCH8_6, 1 }, + { TRXC_SDCCH8_1, 2, TRXC_SACCH8_6, 2 }, + { TRXC_SDCCH8_1, 3, TRXC_SACCH8_6, 3 }, + { TRXC_SDCCH8_2, 0, TRXC_SACCH8_7, 0 }, + { TRXC_SDCCH8_2, 1, TRXC_SACCH8_7, 1 }, + { TRXC_SDCCH8_2, 2, TRXC_SACCH8_7, 2 }, + { TRXC_SDCCH8_2, 3, TRXC_SACCH8_7, 3 }, + { TRXC_SDCCH8_3, 0, TRXC_IDLE, 0 }, + { TRXC_SDCCH8_3, 1, TRXC_IDLE, 0 }, + { TRXC_SDCCH8_3, 2, TRXC_IDLE, 0 }, + { TRXC_SDCCH8_3, 3, TRXC_SDCCH8_0, 0 }, + { TRXC_SDCCH8_4, 0, TRXC_SDCCH8_0, 1 }, + { TRXC_SDCCH8_4, 1, TRXC_SDCCH8_0, 2 }, + { TRXC_SDCCH8_4, 2, TRXC_SDCCH8_0, 3 }, + { TRXC_SDCCH8_4, 3, TRXC_SDCCH8_1, 0 }, + { TRXC_SDCCH8_5, 0, TRXC_SDCCH8_1, 1 }, + { TRXC_SDCCH8_5, 1, TRXC_SDCCH8_1, 2 }, + { TRXC_SDCCH8_5, 2, TRXC_SDCCH8_1, 3 }, + { TRXC_SDCCH8_5, 3, TRXC_SDCCH8_2, 0 }, + { TRXC_SDCCH8_6, 0, TRXC_SDCCH8_2, 1 }, + { TRXC_SDCCH8_6, 1, TRXC_SDCCH8_2, 2 }, + { TRXC_SDCCH8_6, 2, TRXC_SDCCH8_2, 3 }, + { TRXC_SDCCH8_6, 3, TRXC_SDCCH8_3, 0 }, + { TRXC_SDCCH8_7, 0, TRXC_SDCCH8_3, 1 }, + { TRXC_SDCCH8_7, 1, TRXC_SDCCH8_3, 2 }, + { TRXC_SDCCH8_7, 2, TRXC_SDCCH8_3, 3 }, + { TRXC_SDCCH8_7, 3, TRXC_SDCCH8_4, 0 }, + { TRXC_SACCH8_0, 0, TRXC_SDCCH8_4, 1 }, + { TRXC_SACCH8_0, 1, TRXC_SDCCH8_4, 2 }, + { TRXC_SACCH8_0, 2, TRXC_SDCCH8_4, 3 }, + { TRXC_SACCH8_0, 3, TRXC_SDCCH8_5, 0 }, + { TRXC_SACCH8_1, 0, TRXC_SDCCH8_5, 1 }, + { TRXC_SACCH8_1, 1, TRXC_SDCCH8_5, 2 }, + { TRXC_SACCH8_1, 2, TRXC_SDCCH8_5, 3 }, + { TRXC_SACCH8_1, 3, TRXC_SDCCH8_6, 0 }, + { TRXC_SACCH8_2, 0, TRXC_SDCCH8_6, 1 }, + { TRXC_SACCH8_2, 1, TRXC_SDCCH8_6, 2 }, + { TRXC_SACCH8_2, 2, TRXC_SDCCH8_6, 3 }, + { TRXC_SACCH8_2, 3, TRXC_SDCCH8_7, 0 }, + { TRXC_SACCH8_3, 0, TRXC_SDCCH8_7, 1 }, + { TRXC_SACCH8_3, 1, TRXC_SDCCH8_7, 2 }, + { TRXC_SACCH8_3, 2, TRXC_SDCCH8_7, 3 }, + { TRXC_SACCH8_3, 3, TRXC_SACCH8_0, 0 }, + { TRXC_IDLE, 0, TRXC_SACCH8_0, 1 }, + { TRXC_IDLE, 0, TRXC_SACCH8_0, 2 }, + { TRXC_IDLE, 0, TRXC_SACCH8_0, 3 }, + + { TRXC_SDCCH8_0, 0, TRXC_SACCH8_1, 0 }, + { TRXC_SDCCH8_0, 1, TRXC_SACCH8_1, 1 }, + { TRXC_SDCCH8_0, 2, TRXC_SACCH8_1, 2 }, + { TRXC_SDCCH8_0, 3, TRXC_SACCH8_1, 3 }, + { TRXC_SDCCH8_1, 0, TRXC_SACCH8_2, 0 }, + { TRXC_SDCCH8_1, 1, TRXC_SACCH8_2, 1 }, + { TRXC_SDCCH8_1, 2, TRXC_SACCH8_2, 2 }, + { TRXC_SDCCH8_1, 3, TRXC_SACCH8_2, 3 }, + { TRXC_SDCCH8_2, 0, TRXC_SACCH8_3, 0 }, + { TRXC_SDCCH8_2, 1, TRXC_SACCH8_3, 1 }, + { TRXC_SDCCH8_2, 2, TRXC_SACCH8_3, 2 }, + { TRXC_SDCCH8_2, 3, TRXC_SACCH8_3, 3 }, + { TRXC_SDCCH8_3, 0, TRXC_IDLE, 0 }, + { TRXC_SDCCH8_3, 1, TRXC_IDLE, 0 }, + { TRXC_SDCCH8_3, 2, TRXC_IDLE, 0 }, + { TRXC_SDCCH8_3, 3, TRXC_SDCCH8_0, 0 }, + { TRXC_SDCCH8_4, 0, TRXC_SDCCH8_0, 1 }, + { TRXC_SDCCH8_4, 1, TRXC_SDCCH8_0, 2 }, + { TRXC_SDCCH8_4, 2, TRXC_SDCCH8_0, 3 }, + { TRXC_SDCCH8_4, 3, TRXC_SDCCH8_1, 0 }, + { TRXC_SDCCH8_5, 0, TRXC_SDCCH8_1, 1 }, + { TRXC_SDCCH8_5, 1, TRXC_SDCCH8_1, 2 }, + { TRXC_SDCCH8_5, 2, TRXC_SDCCH8_1, 3 }, + { TRXC_SDCCH8_5, 3, TRXC_SDCCH8_2, 0 }, + { TRXC_SDCCH8_6, 0, TRXC_SDCCH8_2, 1 }, + { TRXC_SDCCH8_6, 1, TRXC_SDCCH8_2, 2 }, + { TRXC_SDCCH8_6, 2, TRXC_SDCCH8_2, 3 }, + { TRXC_SDCCH8_6, 3, TRXC_SDCCH8_3, 0 }, + { TRXC_SDCCH8_7, 0, TRXC_SDCCH8_3, 1 }, + { TRXC_SDCCH8_7, 1, TRXC_SDCCH8_3, 2 }, + { TRXC_SDCCH8_7, 2, TRXC_SDCCH8_3, 3 }, + { TRXC_SDCCH8_7, 3, TRXC_SDCCH8_4, 0 }, + { TRXC_SACCH8_4, 0, TRXC_SDCCH8_4, 1 }, + { TRXC_SACCH8_4, 1, TRXC_SDCCH8_4, 2 }, + { TRXC_SACCH8_4, 2, TRXC_SDCCH8_4, 3 }, + { TRXC_SACCH8_4, 3, TRXC_SDCCH8_5, 0 }, + { TRXC_SACCH8_5, 0, TRXC_SDCCH8_5, 1 }, + { TRXC_SACCH8_5, 1, TRXC_SDCCH8_5, 2 }, + { TRXC_SACCH8_5, 2, TRXC_SDCCH8_5, 3 }, + { TRXC_SACCH8_5, 3, TRXC_SDCCH8_6, 0 }, + { TRXC_SACCH8_6, 0, TRXC_SDCCH8_6, 1 }, + { TRXC_SACCH8_6, 1, TRXC_SDCCH8_6, 2 }, + { TRXC_SACCH8_6, 2, TRXC_SDCCH8_6, 3 }, + { TRXC_SACCH8_6, 3, TRXC_SDCCH8_7, 0 }, + { TRXC_SACCH8_7, 0, TRXC_SDCCH8_7, 1 }, + { TRXC_SACCH8_7, 1, TRXC_SDCCH8_7, 2 }, + { TRXC_SACCH8_7, 2, TRXC_SDCCH8_7, 3 }, + { TRXC_SACCH8_7, 3, TRXC_SACCH8_4, 0 }, + { TRXC_IDLE, 0, TRXC_SACCH8_4, 1 }, + { TRXC_IDLE, 0, TRXC_SACCH8_4, 2 }, + { TRXC_IDLE, 0, TRXC_SACCH8_4, 3 }, +}; + +static const struct trx_sched_frame frame_tchf_ts0[104] = { +/* dl_chan dl_bid ul_chan ul_bid */ + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, +}; + +static const struct trx_sched_frame frame_tchf_ts1[104] = { +/* dl_chan dl_bid ul_chan ul_bid */ + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, +}; + +static const struct trx_sched_frame frame_tchf_ts2[104] = { +/* dl_chan dl_bid ul_chan ul_bid */ + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, +}; + +static const struct trx_sched_frame frame_tchf_ts3[104] = { +/* dl_chan dl_bid ul_chan ul_bid */ + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, +}; + +static const struct trx_sched_frame frame_tchf_ts4[104] = { +/* dl_chan dl_bid ul_chan ul_bid */ + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, +}; + +static const struct trx_sched_frame frame_tchf_ts5[104] = { +/* dl_chan dl_bid ul_chan ul_bid */ + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, +}; + +static const struct trx_sched_frame frame_tchf_ts6[104] = { +/* dl_chan dl_bid ul_chan ul_bid */ + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, +}; + +static const struct trx_sched_frame frame_tchf_ts7[104] = { +/* dl_chan dl_bid ul_chan ul_bid */ + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_TCHF, 0, TRXC_TCHF, 0 }, { TRXC_TCHF, 1, TRXC_TCHF, 1 }, { TRXC_TCHF, 2, TRXC_TCHF, 2 }, { TRXC_TCHF, 3, TRXC_TCHF, 3 }, + { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 }, +}; + +static const struct trx_sched_frame frame_tchh_ts01[104] = { +/* dl_chan dl_bid ul_chan ul_bid */ + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 }, +}; + +static const struct trx_sched_frame frame_tchh_ts23[104] = { +/* dl_chan dl_bid ul_chan ul_bid */ + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 }, +}; + +static const struct trx_sched_frame frame_tchh_ts45[104] = { +/* dl_chan dl_bid ul_chan ul_bid */ + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 }, +}; + +static const struct trx_sched_frame frame_tchh_ts67[104] = { +/* dl_chan dl_bid ul_chan ul_bid */ + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 }, { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 }, { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 }, { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 }, + { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 }, +}; + +static const struct trx_sched_frame frame_pdch[104] = { +/* dl_chan dl_bid ul_chan ul_bid */ + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PTCCH, 0, TRXC_PTCCH, 0 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PTCCH, 1, TRXC_PTCCH, 1 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PTCCH, 2, TRXC_PTCCH, 2 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PTCCH, 3, TRXC_PTCCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_PDTCH, 0, TRXC_PDTCH, 0 }, { TRXC_PDTCH, 1, TRXC_PDTCH, 1 }, { TRXC_PDTCH, 2, TRXC_PDTCH, 2 }, { TRXC_PDTCH, 3, TRXC_PDTCH, 3 }, + { TRXC_IDLE, 0, TRXC_IDLE, 0 }, +}; + +const struct trx_sched_multiframe trx_sched_multiframes[] = { + { GSM_PCHAN_NONE, 0xff, 0, NULL, "NONE"}, + { GSM_PCHAN_CCCH, 0xff, 51, frame_bcch, "BCCH+CCCH" }, + { GSM_PCHAN_CCCH_SDCCH4, 0xff, 102, frame_bcch_sdcch4, "BCCH+CCCH+SDCCH/4+SACCH/4" }, + { GSM_PCHAN_SDCCH8_SACCH8C, 0xff, 102, frame_sdcch8, "SDCCH/8+SACCH/8" }, + { GSM_PCHAN_TCH_F, 0x01, 104, frame_tchf_ts0, "TCH/F+SACCH" }, + { GSM_PCHAN_TCH_F, 0x02, 104, frame_tchf_ts1, "TCH/F+SACCH" }, + { GSM_PCHAN_TCH_F, 0x04, 104, frame_tchf_ts2, "TCH/F+SACCH" }, + { GSM_PCHAN_TCH_F, 0x08, 104, frame_tchf_ts3, "TCH/F+SACCH" }, + { GSM_PCHAN_TCH_F, 0x10, 104, frame_tchf_ts4, "TCH/F+SACCH" }, + { GSM_PCHAN_TCH_F, 0x20, 104, frame_tchf_ts5, "TCH/F+SACCH" }, + { GSM_PCHAN_TCH_F, 0x40, 104, frame_tchf_ts6, "TCH/F+SACCH" }, + { GSM_PCHAN_TCH_F, 0x80, 104, frame_tchf_ts7, "TCH/F+SACCH" }, + { GSM_PCHAN_TCH_H, 0x03, 104, frame_tchh_ts01, "TCH/H+SACCH" }, + { GSM_PCHAN_TCH_H, 0x0c, 104, frame_tchh_ts23, "TCH/H+SACCH" }, + { GSM_PCHAN_TCH_H, 0x30, 104, frame_tchh_ts45, "TCH/H+SACCH" }, + { GSM_PCHAN_TCH_H, 0xc0, 104, frame_tchh_ts67, "TCH/H+SACCH" }, + { GSM_PCHAN_PDCH, 0xff, 104, frame_pdch, "PDCH" }, +}; + + +/* + * scheduler functions + */ + +int find_sched_mframe_idx(enum gsm_phys_chan_config pchan, uint8_t tn) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(trx_sched_multiframes); i++) { + if (trx_sched_multiframes[i].pchan == pchan + && (trx_sched_multiframes[i].slotmask & (1 << tn))) { + return i; + } + } + return -1; +} + +/* Determine if given frame number contains SACCH (true) or other (false) burst */ +bool trx_sched_is_sacch_fn(struct gsm_bts_trx_ts *ts, uint32_t fn, bool uplink) +{ + int i; + const struct trx_sched_multiframe *sched; + const struct trx_sched_frame *frame; + enum trx_chan_type ch_type; + + i = find_sched_mframe_idx(ts->pchan, ts->nr); + if (i < 0) + return -EINVAL; + sched = &trx_sched_multiframes[i]; + frame = &sched->frames[fn % sched->period]; + if (uplink) + ch_type = frame->ul_chan; + else + ch_type = frame->dl_chan; + + switch (ch_type) { + case TRXC_SACCH4_0: + case TRXC_SACCH4_1: + case TRXC_SACCH4_2: + case TRXC_SACCH4_3: + case TRXC_SACCH8_0: + case TRXC_SACCH8_1: + case TRXC_SACCH8_2: + case TRXC_SACCH8_3: + case TRXC_SACCH8_4: + case TRXC_SACCH8_5: + case TRXC_SACCH8_6: + case TRXC_SACCH8_7: + case TRXC_SACCHTF: + case TRXC_SACCHTH_0: + case TRXC_SACCHTH_1: + return true; + default: + return false; + } +} diff --git a/src/common/sysinfo.c b/src/common/sysinfo.c new file mode 100644 index 0000000..5c66e08 --- /dev/null +++ b/src/common/sysinfo.c @@ -0,0 +1,177 @@ +/* (C) 2011 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include + +#include +#include + +#include +#include + +/* properly increment SI2q index and return SI2q data for scheduling */ +static inline uint8_t *get_si2q_inc_index(struct gsm_bts *bts) +{ + uint8_t i = bts->si2q_index; + /* si2q_count is the max si2q_index value, not the number of messages */ + bts->si2q_index = (bts->si2q_index + 1) % (bts->si2q_count + 1); + + return (uint8_t *)GSM_BTS_SI2Q(bts, i); +} + +/* Apply the rules from 05.02 6.3.1.3 Mapping of BCCH Data */ +uint8_t *bts_sysinfo_get(struct gsm_bts *bts, const struct gsm_time *g_time) +{ + unsigned int tc4_cnt = 0; + unsigned int tc4_sub[4]; + + /* System information type 2 bis or 2 ter messages are sent if + * needed, as determined by the system operator. If only one of + * them is needed, it is sent when TC = 5. If both are needed, + * 2bis is sent when TC = 5 and 2ter is sent at least once + * within any of 4 consecutive occurrences of TC = 4. */ + /* System information type 2 quater is sent if needed, as + * determined by the system operator. If sent on BCCH Norm, it + * shall be sent when TC = 5 if neither of 2bis and 2ter are + * used, otherwise it shall be sent at least once within any of + * 4 consecutive occurrences of TC = 4. If sent on BCCH Ext, it + * is sent at least once within any of 4 consecutive occurrences + * of TC = 5. */ + /* System Information type 9 is sent in those blocks with + * TC = 4 which are specified in system information type 3 as + * defined in 3GPP TS 04.08. */ + /* System Information Type 13 need only be sent if GPRS support + * is indicated in one or more of System Information Type 3 or 4 + * or 7 or 8 messages. These messages also indicate if the + * message is sent on the BCCH Norm or if the message is + * transmitted on the BCCH Ext. In the case that the message is + * sent on the BCCH Norm, it is sent at least once within any of + * 4 consecutive occurrences of TC = 4. */ + + /* We only implement BCCH Norm at this time */ + switch (g_time->tc) { + case 0: + /* System Information Type 1 need only be sent if + * frequency hopping is in use or when the NCH is + * present in a cell. If the MS finds another message + * when TC = 0, it can assume that System Information + * Type 1 is not in use. */ + if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_1)) + return GSM_BTS_SI(bts, SYSINFO_TYPE_1); + return GSM_BTS_SI(bts, SYSINFO_TYPE_2); + case 1: + /* A SI 2 message will be sent at least every time TC = 1. */ + return GSM_BTS_SI(bts, SYSINFO_TYPE_2); + case 2: + return GSM_BTS_SI(bts, SYSINFO_TYPE_3); + case 3: + return GSM_BTS_SI(bts, SYSINFO_TYPE_4); + case 4: + /* iterate over 2ter, 2quater, 9, 13 */ + /* determine how many SI we need to send on TC=4, + * and which of them we send when */ + if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter) && GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2bis)) { + tc4_sub[tc4_cnt] = SYSINFO_TYPE_2ter; + tc4_cnt += 1; + } + if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2quater) && + (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2bis) || GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter))) { + tc4_sub[tc4_cnt] = SYSINFO_TYPE_2quater; + tc4_cnt += 1; + } + if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_13)) { + tc4_sub[tc4_cnt] = SYSINFO_TYPE_13; + tc4_cnt += 1; + } + if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_9)) { + /* FIXME: check SI3 scheduling info! */ + tc4_sub[tc4_cnt] = SYSINFO_TYPE_9; + tc4_cnt += 1; + } + /* simply send SI2 if we have nothing else to send */ + if (tc4_cnt == 0) + return GSM_BTS_SI(bts, SYSINFO_TYPE_2); + else { + /* increment static counter by one, modulo count */ + bts->si.tc4_ctr = (bts->si.tc4_ctr + 1) % tc4_cnt; + + if (tc4_sub[bts->si.tc4_ctr] == SYSINFO_TYPE_2quater) + return get_si2q_inc_index(bts); + + return GSM_BTS_SI(bts, tc4_sub[bts->si.tc4_ctr]); + } + case 5: + /* 2bis, 2ter, 2quater */ + if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2bis) && !GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter)) + return GSM_BTS_SI(bts, SYSINFO_TYPE_2bis); + + else if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter) && !GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2bis)) + return GSM_BTS_SI(bts, SYSINFO_TYPE_2ter); + + else if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2bis) && GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter)) + return GSM_BTS_SI(bts, SYSINFO_TYPE_2bis); + + else if (GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2quater) && + !GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2bis) && !GSM_BTS_HAS_SI(bts, SYSINFO_TYPE_2ter)) + return get_si2q_inc_index(bts); + + /* simply send SI2 if we have nothing else to send */ + else + return GSM_BTS_SI(bts, SYSINFO_TYPE_2); + break; + case 6: + return GSM_BTS_SI(bts, SYSINFO_TYPE_3); + case 7: + return GSM_BTS_SI(bts, SYSINFO_TYPE_4); + } + + /* this should never bve reached. We must transmit a BCCH + * message on the normal BCCH in all cases. */ + OSMO_ASSERT(0); + return 0; +} + +uint8_t num_agch(struct gsm_bts_trx *trx, const char * arg) +{ + struct gsm_bts *b = trx->bts; + struct gsm48_system_information_type_3 *si3; + if (GSM_BTS_HAS_SI(b, SYSINFO_TYPE_3)) { + si3 = GSM_BTS_SI(b, SYSINFO_TYPE_3); + return si3->control_channel_desc.bs_ag_blks_res; + } + LOGP(DL1P, LOGL_ERROR, "%s: Unable to determine actual BS_AG_BLKS_RES " + "value as SI3 is not available yet, fallback to 1\n", arg); + return 1; +} + +/* obtain the next to-be transmitted dowlink SACCH frame (L2 hdr + L3); returns pointer to lchan->si buffer */ +uint8_t *lchan_sacch_get(struct gsm_lchan *lchan) +{ + uint32_t tmp, i; + + for (i = 0; i < _MAX_SYSINFO_TYPE; i++) { + tmp = (lchan->si.last + 1 + i) % _MAX_SYSINFO_TYPE; + if (!(lchan->si.valid & (1 << tmp))) + continue; + lchan->si.last = tmp; + return GSM_LCHAN_SI(lchan, tmp); + } + LOGP(DL1P, LOGL_NOTICE, "%s SACCH no SI available\n", gsm_lchan_name(lchan)); + return NULL; +} diff --git a/src/common/tx_power.c b/src/common/tx_power.c new file mode 100644 index 0000000..e418cec --- /dev/null +++ b/src/common/tx_power.c @@ -0,0 +1,306 @@ +/* Transmit Power computation */ + +/* (C) 2014 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include + +#include +#include +#include +#include + +static int get_pa_drive_level_mdBm(const struct power_amp *pa, + int desired_p_out_mdBm, unsigned int arfcn) +{ + if (arfcn >= ARRAY_SIZE(pa->calib.delta_mdB)) + return INT_MIN; + + /* FIXME: temperature compensation */ + + return desired_p_out_mdBm - pa->nominal_gain_mdB - pa->calib.delta_mdB[arfcn]; +} + +/* maximum output power of the system */ +int get_p_max_out_mdBm(struct gsm_bts_trx *trx) +{ + struct trx_power_params *tpp = &trx->power_params; + /* Add user gain, internal and external PA gain to TRX output power */ + return tpp->trx_p_max_out_mdBm + tpp->user_gain_mdB + + tpp->pa.nominal_gain_mdB + tpp->user_pa.nominal_gain_mdB; +} + +/* nominal output power, i.e. OML-reduced maximum output power */ +int get_p_nominal_mdBm(struct gsm_bts_trx *trx) +{ + /* P_max_out subtracted by OML maximum power reduction IE */ + return get_p_max_out_mdBm(trx) - to_mdB(trx->max_power_red); +} + +/* calculate the target total output power required, reduced by both + * OML and RSL, but ignoring the attenuation required for power ramping and + * thermal management */ +int get_p_target_mdBm(struct gsm_bts_trx *trx, uint8_t bs_power_ie) +{ + /* Pn subtracted by RSL BS Power IE (in 2 dB steps) */ + return get_p_nominal_mdBm(trx) - to_mdB(bs_power_ie * 2); +} +int get_p_target_mdBm_lchan(struct gsm_lchan *lchan) +{ + return get_p_target_mdBm(lchan->ts->trx, lchan->bs_power); +} + +/* calculate the actual total output power required, taking into account the + * attenuation required for power ramping but not thermal management */ +int get_p_actual_mdBm(struct gsm_bts_trx *trx, int p_target_mdBm) +{ + struct trx_power_params *tpp = &trx->power_params; + + /* P_target subtracted by ramp attenuation */ + return p_target_mdBm - tpp->ramp.attenuation_mdB; +} + +/* calculate the effective total output power required, taking into account the + * attenuation required for power ramping and thermal management */ +int get_p_eff_mdBm(struct gsm_bts_trx *trx, int p_target_mdBm) +{ + struct trx_power_params *tpp = &trx->power_params; + + /* P_target subtracted by ramp attenuation */ + return p_target_mdBm - tpp->ramp.attenuation_mdB - tpp->thermal_attenuation_mdB; +} + +/* calculate effect TRX output power required, taking into account the + * attenuations required for power ramping and thermal management */ +int get_p_trxout_eff_mdBm(struct gsm_bts_trx *trx, int p_target_mdBm) +{ + struct trx_power_params *tpp = &trx->power_params; + int p_actual_mdBm, user_pa_drvlvl_mdBm, pa_drvlvl_mdBm; + unsigned int arfcn = trx->arfcn; + + /* P_actual subtracted by any bulk gain added by the user */ + p_actual_mdBm = get_p_eff_mdBm(trx, p_target_mdBm) - tpp->user_gain_mdB; + + /* determine input drive level required at input to user PA */ + user_pa_drvlvl_mdBm = get_pa_drive_level_mdBm(&tpp->user_pa, p_actual_mdBm, arfcn); + + /* determine input drive level required at input to internal PA */ + pa_drvlvl_mdBm = get_pa_drive_level_mdBm(&tpp->pa, user_pa_drvlvl_mdBm, arfcn); + + /* internal PA input drive level is TRX output power */ + return pa_drvlvl_mdBm; +} + +/* calculate target TRX output power required, ignoring the + * attenuations required for power ramping but not thermal management */ +int get_p_trxout_target_mdBm(struct gsm_bts_trx *trx, uint8_t bs_power_ie) +{ + struct trx_power_params *tpp = &trx->power_params; + int p_target_mdBm, user_pa_drvlvl_mdBm, pa_drvlvl_mdBm; + unsigned int arfcn = trx->arfcn; + + /* P_target subtracted by any bulk gain added by the user */ + p_target_mdBm = get_p_target_mdBm(trx, bs_power_ie) - tpp->user_gain_mdB; + + /* determine input drive level required at input to user PA */ + user_pa_drvlvl_mdBm = get_pa_drive_level_mdBm(&tpp->user_pa, p_target_mdBm, arfcn); + + /* determine input drive level required at input to internal PA */ + pa_drvlvl_mdBm = get_pa_drive_level_mdBm(&tpp->pa, user_pa_drvlvl_mdBm, arfcn); + + /* internal PA input drive level is TRX output power */ + return pa_drvlvl_mdBm; +} +int get_p_trxout_target_mdBm_lchan(struct gsm_lchan *lchan) +{ + return get_p_trxout_target_mdBm(lchan->ts->trx, lchan->bs_power); +} + + +/* output power ramping code */ + +/* The idea here is to avoid a hard switch from 0 to 100, but to actually + * slowly and gradually ramp up or down the power. This is needed on the + * one hand side to avoid very fast dynamic load changes towards the PA power + * supply, but is also needed in order to avoid a DoS by too many subscriber + * attempting to register at the same time. Rather, grow the cell slowly in + * radius than start with the full radius at once. */ + +static int we_are_ramping_up(struct gsm_bts_trx *trx) +{ + struct trx_power_params *tpp = &trx->power_params; + + if (tpp->p_total_tgt_mdBm > tpp->p_total_cur_mdBm) + return 1; + else + return 0; +} + +static void power_ramp_do_step(struct gsm_bts_trx *trx, int first); + +/* timer call-back for the ramp timer */ +static void power_ramp_timer_cb(void *_trx) +{ + struct gsm_bts_trx *trx = _trx; + struct trx_power_params *tpp = &trx->power_params; + int p_trxout_eff_mdBm; + + /* compute new actual total output power (= minus ramp attenuation) */ + tpp->p_total_cur_mdBm = get_p_actual_mdBm(trx, tpp->p_total_tgt_mdBm); + + /* compute new effective (= minus ramp and thermal attenuation) TRX output required */ + p_trxout_eff_mdBm = get_p_trxout_eff_mdBm(trx, tpp->p_total_tgt_mdBm); + + LOGP(DL1C, LOGL_DEBUG, "ramp_timer_cb(cur_pout=%d, tgt_pout=%d, " + "ramp_att=%d, therm_att=%d, user_gain=%d)\n", + tpp->p_total_cur_mdBm, tpp->p_total_tgt_mdBm, + tpp->ramp.attenuation_mdB, tpp->thermal_attenuation_mdB, + tpp->user_gain_mdB); + + LOGP(DL1C, LOGL_INFO, + "ramping TRX board output power to %d mdBm.\n", p_trxout_eff_mdBm); + + /* Instruct L1 to apply new effective TRX output power required */ + bts_model_change_power(trx, p_trxout_eff_mdBm); +} + +/* BTS model call-back once one a call to bts_model_change_power() + * completes, indicating actual L1 transmit power */ +void power_trx_change_compl(struct gsm_bts_trx *trx, int p_trxout_cur_mdBm) +{ + struct trx_power_params *tpp = &trx->power_params; + int p_trxout_should_mdBm; + + p_trxout_should_mdBm = get_p_trxout_eff_mdBm(trx, tpp->p_total_tgt_mdBm); + + /* for now we simply write an error message, but in the future + * we might use the value (again) as part of our math? */ + if (p_trxout_cur_mdBm != p_trxout_should_mdBm) { + LOGP(DL1C, LOGL_ERROR, "bts_model notifies us of %u mdBm TRX " + "output power. However, it should be %u mdBm!\n", + p_trxout_cur_mdBm, p_trxout_should_mdBm); + } + + /* and do another step... */ + power_ramp_do_step(trx, 0); +} + +static void power_ramp_do_step(struct gsm_bts_trx *trx, int first) +{ + struct trx_power_params *tpp = &trx->power_params; + + /* we had finished in last loop iteration */ + if (!first && tpp->ramp.attenuation_mdB == 0) + return; + + if (we_are_ramping_up(trx)) { + /* ramp up power -> ramp down attenuation */ + tpp->ramp.attenuation_mdB -= tpp->ramp.step_size_mdB; + if (tpp->ramp.attenuation_mdB <= 0) { + /* we are done */ + tpp->ramp.attenuation_mdB = 0; + } + } else { + /* ramp down power -> ramp up attenuation */ + tpp->ramp.attenuation_mdB += tpp->ramp.step_size_mdB; + if (tpp->ramp.attenuation_mdB >= 0) { + /* we are done */ + tpp->ramp.attenuation_mdB = 0; + } + } + + /* schedule timer for the next step */ + tpp->ramp.step_timer.data = trx; + tpp->ramp.step_timer.cb = power_ramp_timer_cb; + osmo_timer_schedule(&tpp->ramp.step_timer, tpp->ramp.step_interval_sec, 0); +} + + +int power_ramp_start(struct gsm_bts_trx *trx, int p_total_tgt_mdBm, int bypass) +{ + struct trx_power_params *tpp = &trx->power_params; + + /* The input to this function is the actual desired output power, i.e. + * the maximum total system power subtracted by OML as well as RSL + * reductions */ + + LOGP(DL1C, LOGL_INFO, "power_ramp_start(cur=%d, tgt=%d)\n", + tpp->p_total_cur_mdBm, p_total_tgt_mdBm); + + if (!bypass && (p_total_tgt_mdBm > get_p_nominal_mdBm(trx))) { + LOGP(DL1C, LOGL_ERROR, "Asked to ramp power up to " + "%d mdBm, which exceeds P_max_out (%d)\n", + p_total_tgt_mdBm, get_p_nominal_mdBm(trx)); + return -ERANGE; + } + + /* Cancel any pending request */ + osmo_timer_del(&tpp->ramp.step_timer); + + /* set the new target */ + tpp->p_total_tgt_mdBm = p_total_tgt_mdBm; + + if (we_are_ramping_up(trx)) { + if (tpp->p_total_tgt_mdBm <= tpp->ramp.max_initial_pout_mdBm) { + LOGP(DL1C, LOGL_INFO, + "target_power(%d) is below max.initial power\n", + tpp->p_total_tgt_mdBm); + /* new setting is below the maximum initial output + * power, so we can directly jump to this level */ + tpp->p_total_cur_mdBm = tpp->p_total_tgt_mdBm; + tpp->ramp.attenuation_mdB = 0; + power_ramp_timer_cb(trx); + } else { + /* We need to step it up. Start from the current value */ + /* Set attenuation to cause no power change right now */ + tpp->ramp.attenuation_mdB = tpp->p_total_tgt_mdBm - tpp->p_total_cur_mdBm; + + /* start with the first step */ + power_ramp_do_step(trx, 1); + } + } else { + /* Set ramp attenuation to negative value, and increase that by + * steps until it reaches 0 */ + tpp->ramp.attenuation_mdB = tpp->p_total_tgt_mdBm - tpp->p_total_cur_mdBm; + + /* start with the first step */ + power_ramp_do_step(trx, 1); + } + + return 0; +} + +/* determine the initial transceiver output power at start-up time */ +int power_ramp_initial_power_mdBm(struct gsm_bts_trx *trx) +{ + struct trx_power_params *tpp = &trx->power_params; + int pout_mdBm; + + /* this is the maximum initial output on the antenna connector + * towards the antenna */ + pout_mdBm = tpp->ramp.max_initial_pout_mdBm; + + /* use this as input to compute transceiver board power + * (reflecting gains in internal/external amplifiers */ + return get_p_trxout_eff_mdBm(trx, pout_mdBm); +} diff --git a/src/common/vty.c b/src/common/vty.c new file mode 100644 index 0000000..2716a7a --- /dev/null +++ b/src/common/vty.c @@ -0,0 +1,1598 @@ +/* OsmoBTS VTY interface */ + +/* (C) 2011-2014 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include "btsconfig.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define VTY_STR "Configure the VTY\n" + +#define BTS_NR_STR "BTS Number\n" +#define TRX_NR_STR "TRX Number\n" +#define TS_NR_STR "Timeslot 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 + +int g_vty_port_num = OSMO_VTY_PORT_BTS; + +struct phy_instance *vty_get_phy_instance(struct vty *vty, int phy_nr, int inst_nr) +{ + struct phy_link *plink = phy_link_by_num(phy_nr); + struct phy_instance *pinst; + + if (!plink) { + vty_out(vty, "Cannot find PHY link number %d%s", + phy_nr, VTY_NEWLINE); + return NULL; + } + + pinst = phy_instance_by_num(plink, inst_nr); + if (!pinst) { + vty_out(vty, "Cannot find PHY instance number %d%s", + inst_nr, VTY_NEWLINE); + return NULL; + } + return pinst; +} + +int bts_vty_go_parent(struct vty *vty) +{ + switch (vty->node) { + case PHY_INST_NODE: + vty->node = PHY_NODE; + { + struct phy_instance *pinst = vty->index; + vty->index = pinst->phy_link; + } + break; + case TRX_NODE: + vty->node = BTS_NODE; + { + struct gsm_bts_trx *trx = vty->index; + vty->index = trx->bts; + } + break; + case PHY_NODE: + default: + vty->node = CONFIG_NODE; + } + return vty->node; +} + +int bts_vty_is_config_node(struct vty *vty, int node) +{ + switch (node) { + case TRX_NODE: + case BTS_NODE: + case PHY_NODE: + case PHY_INST_NODE: + return 1; + default: + return 0; + } +} + +gDEFUN(ournode_exit, ournode_exit_cmd, "exit", + "Exit current node, go down to provious node") +{ + switch (vty->node) { + case PHY_INST_NODE: + vty->node = PHY_NODE; + { + struct phy_instance *pinst = vty->index; + vty->index = pinst->phy_link; + } + break; + case PHY_NODE: + vty->node = CONFIG_NODE; + vty->index = NULL; + break; + case TRX_NODE: + vty->node = BTS_NODE; + { + struct gsm_bts_trx *trx = vty->index; + vty->index = trx->bts; + } + break; + default: + break; + } + return CMD_SUCCESS; +} + +gDEFUN(ournode_end, ournode_end_cmd, "end", + "End current mode and change to enable mode") +{ + switch (vty->node) { + default: + vty_config_unlock(vty); + vty->node = ENABLE_NODE; + vty->index = NULL; + vty->index_sub = NULL; + break; + } + return CMD_SUCCESS; +} + +static const char osmobts_copyright[] = + "Copyright (C) 2010, 2011 by Harald Welte, Andreas Eversberg and On-Waves\r\n" + "License AGPLv3+: GNU AGPL version 3 or later \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"; + +struct vty_app_info bts_vty_info = { + .name = "OsmoBTS", + .version = PACKAGE_VERSION, + .copyright = osmobts_copyright, + .go_parent_cb = bts_vty_go_parent, + .is_config_node = bts_vty_is_config_node, +}; + +extern struct gsm_network bts_gsmnet; + +struct gsm_network *gsmnet_from_vty(struct vty *v) +{ + return &bts_gsmnet; +} + +static struct cmd_node bts_node = { + BTS_NODE, + "%s(bts)# ", + 1, +}; + +static struct cmd_node trx_node = { + TRX_NODE, + "%s(trx)# ", + 1, +}; + +gDEFUN(cfg_bts_auto_band, cfg_bts_auto_band_cmd, + "auto-band", + "Automatically select band for ARFCN based on configured band\n") +{ + struct gsm_bts *bts = vty->index; + + bts->auto_band = 1; + return CMD_SUCCESS; +} + +gDEFUN(cfg_bts_no_auto_band, cfg_bts_no_auto_band_cmd, + "no auto-band", + NO_STR "Automatically select band for ARFCN based on configured band\n") +{ + struct gsm_bts *bts = vty->index; + + bts->auto_band = 0; + return CMD_SUCCESS; +} + + +DEFUN(cfg_bts_trx, cfg_bts_trx_cmd, + "trx <0-254>", + "Select a TRX to configure\n" "TRX number\n") +{ + int trx_nr = atoi(argv[0]); + struct gsm_bts *bts = vty->index; + struct gsm_bts_trx *trx; + + trx = gsm_bts_trx_num(bts, trx_nr); + if (!trx) { + vty_out(vty, "Unknown TRX %u. Available TRX are: 0..%u%s", + trx_nr, bts->num_trx - 1, VTY_NEWLINE); + vty_out(vty, "Hint: Check if commandline option -t matches the" + "number of available transceivers!%s", VTY_NEWLINE); + return CMD_WARNING; + } + + vty->index = trx; + vty->index_sub = &trx->description; + vty->node = TRX_NODE; + + return CMD_SUCCESS; +} + +static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + char buf_casecnvt[256]; + int i; + + vty_out(vty, "bts %u%s", bts->nr, 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); + if (bts->auto_band) + vty_out(vty, " auto-band%s", VTY_NEWLINE); + vty_out(vty, " ipa unit-id %u %u%s", + bts->ip_access.site_id, bts->ip_access.bts_id, VTY_NEWLINE); + vty_out(vty, " oml remote-ip %s%s", bts->bsc_oml_host, VTY_NEWLINE); + vty_out(vty, " rtp jitter-buffer %u", bts->rtp_jitter_buf_ms); + if (bts->rtp_jitter_adaptive) + vty_out(vty, " adaptive"); + vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, " paging queue-size %u%s", paging_get_queue_max(bts->paging_state), + VTY_NEWLINE); + vty_out(vty, " paging lifetime %u%s", paging_get_lifetime(bts->paging_state), + VTY_NEWLINE); + vty_out(vty, " uplink-power-target %d%s", bts->ul_power_target, VTY_NEWLINE); + if (bts->agch_queue.thresh_level != GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DEFAULT + || bts->agch_queue.low_level != GSM_BTS_AGCH_QUEUE_LOW_LEVEL_DEFAULT + || bts->agch_queue.high_level != GSM_BTS_AGCH_QUEUE_HIGH_LEVEL_DEFAULT) + vty_out(vty, " agch-queue-mgmt threshold %d low %d high %d%s", + bts->agch_queue.thresh_level, bts->agch_queue.low_level, + bts->agch_queue.high_level, VTY_NEWLINE); + + for (i = 0; i < 32; i++) { + if (gsmtap_sapi_mask & (1 << i)) { + osmo_str2lower(buf_casecnvt, get_value_string(gsmtap_sapi_names, i)); + vty_out(vty, " gsmtap-sapi %s%s", buf_casecnvt, VTY_NEWLINE); + } + } + if (gsmtap_sapi_acch) { + osmo_str2lower(buf_casecnvt, get_value_string(gsmtap_sapi_names, GSMTAP_CHANNEL_ACCH)); + vty_out(vty, " gsmtap-sapi %s%s", buf_casecnvt, VTY_NEWLINE); + } + vty_out(vty, " min-qual-rach %.0f%s", bts->min_qual_rach * 10.0f, + VTY_NEWLINE); + vty_out(vty, " min-qual-norm %.0f%s", bts->min_qual_norm * 10.0f, + VTY_NEWLINE); + vty_out(vty, " max-ber10k-rach %u%s", bts->max_ber10k_rach, + VTY_NEWLINE); + if (strcmp(bts->pcu.sock_path, PCU_SOCK_DEFAULT)) + vty_out(vty, " pcu-socket %s%s", bts->pcu.sock_path, VTY_NEWLINE); + if (bts->supp_meas_toa256) + vty_out(vty, " supp-meas-info toa256%s", VTY_NEWLINE); + + bts_model_config_write_bts(vty, bts); + + llist_for_each_entry(trx, &bts->trx_list, list) { + struct trx_power_params *tpp = &trx->power_params; + struct phy_instance *pinst = trx_phy_instance(trx); + vty_out(vty, " trx %u%s", trx->nr, VTY_NEWLINE); + + if (trx->power_params.user_gain_mdB) + vty_out(vty, " user-gain %u mdB%s", + tpp->user_gain_mdB, VTY_NEWLINE); + vty_out(vty, " power-ramp max-initial %d mdBm%s", + tpp->ramp.max_initial_pout_mdBm, VTY_NEWLINE); + vty_out(vty, " power-ramp step-size %d mdB%s", + tpp->ramp.step_size_mdB, VTY_NEWLINE); + vty_out(vty, " power-ramp step-interval %d%s", + tpp->ramp.step_interval_sec, VTY_NEWLINE); + vty_out(vty, " ms-power-control %s%s", + trx->ms_power_control == 0 ? "dsp" : "osmo", + VTY_NEWLINE); + vty_out(vty, " phy %u instance %u%s", pinst->phy_link->num, + pinst->num, VTY_NEWLINE); + + bts_model_config_write_trx(vty, trx); + } +} + +static int config_write_bts(struct vty *vty) +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_bts *bts; + + llist_for_each_entry(bts, &net->bts_list, list) + config_write_bts_single(vty, bts); + + return CMD_SUCCESS; +} + +static void config_write_phy_single(struct vty *vty, struct phy_link *plink) +{ + int i; + + vty_out(vty, "phy %u%s", plink->num, VTY_NEWLINE); + bts_model_config_write_phy(vty, plink); + + for (i = 0; i < 255; i++) { + struct phy_instance *pinst = phy_instance_by_num(plink, i); + if (!pinst) + break; + vty_out(vty, " instance %u%s", pinst->num, VTY_NEWLINE); + bts_model_config_write_phy_inst(vty, pinst); + } +} + +static int config_write_phy(struct vty *vty) +{ + int i; + + for (i = 0; i < 255; i++) { + struct phy_link *plink = phy_link_by_num(i); + if (!plink) + break; + config_write_phy_single(vty, plink); + } + + return CMD_SUCCESS; +} + +static int config_write_dummy(struct vty *vty) +{ + return CMD_SUCCESS; +} + +DEFUN(cfg_vty_telnet_port, cfg_vty_telnet_port_cmd, + "vty telnet-port <0-65535>", + VTY_STR "Set the VTY telnet port\n" + "TCP Port number\n") +{ + g_vty_port_num = atoi(argv[0]); + return CMD_SUCCESS; +} + +/* per-BTS configuration */ +DEFUN(cfg_bts, + cfg_bts_cmd, + "bts BTS_NR", + "Select a BTS to configure\n" + "BTS Number\n") +{ + 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, "%% Unknown BTS number %u (num %u)%s", + bts_nr, gsmnet->num_bts, VTY_NEWLINE); + return CMD_WARNING; + } else + bts = gsm_bts_num(gsmnet, bts_nr); + + vty->index = bts; + vty->index_sub = &bts->description; + vty->node = BTS_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_unit_id, + cfg_bts_unit_id_cmd, + "ipa unit-id <0-65534> <0-255>", + "ip.access RSL commands\n" + "Set the Unit ID of this BTS\n" + "Site ID\n" "Unit ID\n") +{ + struct gsm_bts *bts = vty->index; + int site_id = atoi(argv[0]); + int bts_id = atoi(argv[1]); + + bts->ip_access.site_id = site_id; + bts->ip_access.bts_id = bts_id; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_band, + cfg_bts_band_cmd, + "band (450|GSM450|480|GSM480|750|GSM750|810|GSM810|850|GSM850|900|GSM900|1800|DCS1800|1900|PCS1900)", + "Set the frequency band of this BTS\n" + "Alias for GSM450\n450Mhz\n" + "Alias for GSM480\n480Mhz\n" + "Alias for GSM750\n750Mhz\n" + "Alias for GSM810\n810Mhz\n" + "Alias for GSM850\n850Mhz\n" + "Alias for GSM900\n900Mhz\n" + "Alias for DCS1800\n1800Mhz\n" + "Alias for PCS1900\n1900Mhz\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_oml_ip, + cfg_bts_oml_ip_cmd, + "oml remote-ip A.B.C.D", + "OML Parameters\n" "OML IP Address\n" "OML IP Address\n") +{ + struct gsm_bts *bts = vty->index; + + if (bts->bsc_oml_host) + talloc_free(bts->bsc_oml_host); + + bts->bsc_oml_host = talloc_strdup(bts, argv[0]); + + return CMD_SUCCESS; +} + +#define RTP_STR "RTP parameters\n" + +DEFUN_HIDDEN(cfg_bts_rtp_bind_ip, + cfg_bts_rtp_bind_ip_cmd, + "rtp bind-ip A.B.C.D", + RTP_STR "RTP local bind IP Address\n" "RTP local bind IP Address\n") +{ + vty_out(vty, "%% rtp bind-ip is now deprecated%s", VTY_NEWLINE); + + return CMD_WARNING; +} + +DEFUN(cfg_bts_rtp_jitbuf, + cfg_bts_rtp_jitbuf_cmd, + "rtp jitter-buffer <0-10000> [adaptive]", + RTP_STR "RTP jitter buffer\n" "jitter buffer in ms\n") +{ + struct gsm_bts *bts = vty->index; + + bts->rtp_jitter_buf_ms = atoi(argv[0]); + if (argc > 1) + bts->rtp_jitter_adaptive = true; + + return CMD_SUCCESS; +} + +#define PAG_STR "Paging related parameters\n" + +DEFUN(cfg_bts_paging_queue_size, + cfg_bts_paging_queue_size_cmd, + "paging queue-size <1-1024>", + PAG_STR "Maximum length of BTS-internal paging queue\n" + "Maximum length of BTS-internal paging queue\n") +{ + struct gsm_bts *bts = vty->index; + + paging_set_queue_max(bts->paging_state, atoi(argv[0])); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_paging_lifetime, + cfg_bts_paging_lifetime_cmd, + "paging lifetime <0-60>", + PAG_STR "Maximum lifetime of a paging record\n" + "Maximum lifetime of a paging record (secods)\n") +{ + struct gsm_bts *bts = vty->index; + + paging_set_lifetime(bts->paging_state, atoi(argv[0])); + + return CMD_SUCCESS; +} + +#define AGCH_QUEUE_STR "AGCH queue mgmt\n" + +DEFUN(cfg_bts_agch_queue_mgmt_params, + cfg_bts_agch_queue_mgmt_params_cmd, + "agch-queue-mgmt threshold <0-100> low <0-100> high <0-100000>", + AGCH_QUEUE_STR + "Threshold to start cleanup\nin %% of the maximum queue length\n" + "Low water mark for cleanup\nin %% of the maximum queue length\n" + "High water mark for cleanup\nin %% of the maximum queue length\n") +{ + struct gsm_bts *bts = vty->index; + + bts->agch_queue.thresh_level = atoi(argv[0]); + bts->agch_queue.low_level = atoi(argv[1]); + bts->agch_queue.high_level = atoi(argv[2]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_agch_queue_mgmt_default, + cfg_bts_agch_queue_mgmt_default_cmd, + "agch-queue-mgmt default", + AGCH_QUEUE_STR + "Reset clean parameters to default values\n") +{ + struct gsm_bts *bts = vty->index; + + bts->agch_queue.thresh_level = GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DEFAULT; + bts->agch_queue.low_level = GSM_BTS_AGCH_QUEUE_LOW_LEVEL_DEFAULT; + bts->agch_queue.high_level = GSM_BTS_AGCH_QUEUE_HIGH_LEVEL_DEFAULT; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_ul_power_target, cfg_bts_ul_power_target_cmd, + "uplink-power-target <-110-0>", + "Set the nominal target Rx Level for uplink power control loop\n" + "Target uplink Rx level in dBm\n") +{ + struct gsm_bts *bts = vty->index; + + bts->ul_power_target = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_min_qual_rach, cfg_bts_min_qual_rach_cmd, + "min-qual-rach <-100-100>", + "Set the minimum quality level of RACH burst to be accpeted\n" + "C/I level in tenth of dB\n") +{ + struct gsm_bts *bts = vty->index; + + bts->min_qual_rach = strtof(argv[0], NULL) / 10.0f; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_min_qual_norm, cfg_bts_min_qual_norm_cmd, + "min-qual-norm <-100-100>", + "Set the minimum quality level of normal burst to be accpeted\n" + "C/I level in tenth of dB\n") +{ + struct gsm_bts *bts = vty->index; + + bts->min_qual_norm = strtof(argv[0], NULL) / 10.0f; + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_max_ber_rach, cfg_bts_max_ber_rach_cmd, + "max-ber10k-rach <0-10000>", + "Set the maximum BER for valid RACH requests\n" + "BER in 1/10000 units (0=no BER; 100=1% BER)\n") +{ + struct gsm_bts *bts = vty->index; + + bts->max_ber10k_rach = strtoul(argv[0], NULL, 10); + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_pcu_sock, cfg_bts_pcu_sock_cmd, + "pcu-socket PATH", + "Configure the PCU socket file/path name\n") +{ + struct gsm_bts *bts = vty->index; + + if (bts->pcu.sock_path) { + /* FIXME: close the interface? */ + talloc_free(bts->pcu.sock_path); + } + bts->pcu.sock_path = talloc_strdup(bts, argv[0]); + /* FIXME: re-open the interface? */ + + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_supp_meas_toa256, cfg_bts_supp_meas_toa256_cmd, + "supp-meas-info toa256", + "Configure the RSL Supplementary Measurement Info\n" + "Report the TOA in 1/256th symbol periods\n") +{ + struct gsm_bts *bts = vty->index; + + bts->supp_meas_toa256 = true; + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_no_supp_meas_toa256, cfg_bts_no_supp_meas_toa256_cmd, + "no supp-meas-info toa256", + NO_STR "Configure the RSL Supplementary Measurement Info\n" + "Report the TOA in 1/256th symbol periods\n") +{ + struct gsm_bts *bts = vty->index; + + bts->supp_meas_toa256 = false; + return CMD_SUCCESS; +} + + +#define DB_DBM_STR \ + "Unit is dB (decibels)\n" \ + "Unit is mdB (milli-decibels, or rather 1/10000 bel)\n" + +static int parse_mdbm(const char *valstr, const char *unit) +{ + int val = atoi(valstr); + + if (!strcmp(unit, "dB") || !strcmp(unit, "dBm")) + return val * 1000; + else + return val; +} + +DEFUN(cfg_trx_user_gain, + cfg_trx_user_gain_cmd, + "user-gain <-100000-100000> (dB|mdB)", + "Inform BTS about additional, user-provided gain or attenuation at TRX output\n" + "Value of user-provided external gain(+)/attenuation(-)\n" DB_DBM_STR) +{ + struct gsm_bts_trx *trx = vty->index; + + trx->power_params.user_gain_mdB = parse_mdbm(argv[0], argv[1]); + + return CMD_SUCCESS; +} + +#define PR_STR "Power-Ramp settings" +DEFUN(cfg_trx_pr_max_initial, cfg_trx_pr_max_initial_cmd, + "power-ramp max-initial <0-100000> (dBm|mdBm)", + PR_STR "Maximum initial power\n" + "Value\n" DB_DBM_STR) +{ + struct gsm_bts_trx *trx = vty->index; + + trx->power_params.ramp.max_initial_pout_mdBm = + parse_mdbm(argv[0], argv[1]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_pr_step_size, cfg_trx_pr_step_size_cmd, + "power-ramp step-size <1-100000> (dB|mdB)", + PR_STR "Power increase by step\n" + "Step size\n" DB_DBM_STR) +{ + struct gsm_bts_trx *trx = vty->index; + + trx->power_params.ramp.step_size_mdB = + parse_mdbm(argv[0], argv[1]); + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_pr_step_interval, cfg_trx_pr_step_interval_cmd, + "power-ramp step-interval <1-100>", + PR_STR "Power increase by step\n" + "Step time in seconds\n") +{ + struct gsm_bts_trx *trx = vty->index; + + trx->power_params.ramp.step_interval_sec = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_ms_power_control, cfg_trx_ms_power_control_cmd, + "ms-power-control (dsp|osmo)", + "Mobile Station Power Level Control (change requires restart)\n" + "Handled by DSP\n" "Handled by OsmoBTS\n") +{ + struct gsm_bts_trx *trx = vty->index; + + trx->ms_power_control = argv[0][0] == 'd' ? 0 : 1; + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_phy, cfg_trx_phy_cmd, + "phy <0-255> instance <0-255>", + "Configure PHY Link+Instance for this TRX\n" + "PHY Link number\n" "PHY instance\n" "PHY Instance number") +{ + struct gsm_bts_trx *trx = vty->index; + struct phy_link *plink = phy_link_by_num(atoi(argv[0])); + struct phy_instance *pinst; + + if (!plink) { + vty_out(vty, "phy%s does not exist%s", + argv[0], VTY_NEWLINE); + return CMD_WARNING; + } + + pinst = phy_instance_by_num(plink, atoi(argv[1])); + if (!pinst) { + vty_out(vty, "phy%s instance %s does not exit%s", + argv[0], argv[1], VTY_NEWLINE); + return CMD_WARNING; + } + + trx->role_bts.l1h = pinst; + pinst->trx = trx; + + return CMD_SUCCESS; +} + +/* ====================================================================== + * SHOW + * ======================================================================*/ + +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 unsigned int llist_length(struct llist_head *list) +{ + unsigned int len = 0; + struct llist_head *pos; + + llist_for_each(pos, list) + len++; + + return len; +} + +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 (gsm_bts_has_feature(bts, i)) { + vty_out(vty, " %03u ", i); + vty_out(vty, "%-40s%s", get_value_string(gsm_bts_features_descs, 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 gsm_bts_trx *trx; + + vty_out(vty, "BTS %u is of %s type in band %s, has CI %u LAC %u, " + "BSIC %u and %u TRX%s", + bts->nr, "FIXME", gsm_band_name(bts->band), + bts->cell_identity, + bts->location_area_code, bts->bsic, + bts->num_trx, VTY_NEWLINE); + vty_out(vty, " Description: %s%s", + bts->description ? bts->description : "(null)", VTY_NEWLINE); + 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); + 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 (strnlen(bts->pcu_version, MAX_VERSION_LENGTH)) + vty_out(vty, " PCU version %s connected%s", + bts->pcu_version, VTY_NEWLINE); + vty_out(vty, " Paging: Queue size %u, occupied %u, lifetime %us%s", + paging_get_queue_max(bts->paging_state), paging_queue_length(bts->paging_state), + paging_get_lifetime(bts->paging_state), VTY_NEWLINE); + vty_out(vty, " AGCH: Queue limit %u, occupied %d, " + "dropped %"PRIu64", merged %"PRIu64", rejected %"PRIu64", " + "ag-res %"PRIu64", non-res %"PRIu64"%s", + bts->agch_queue.max_length, bts->agch_queue.length, + bts->agch_queue.dropped_msgs, bts->agch_queue.merged_msgs, + bts->agch_queue.rejected_msgs, bts->agch_queue.agch_msgs, + bts->agch_queue.pch_msgs, + VTY_NEWLINE); + vty_out(vty, " CBCH backlog queue length: %u%s", + llist_length(&bts->smscb_state.queue), VTY_NEWLINE); + vty_out(vty, " Paging: queue length %d, buffer space %d%s", + paging_queue_length(bts->paging_state), paging_buffer_space(bts->paging_state), + VTY_NEWLINE); + vty_out(vty, " OML Link state: %s.%s", + bts->oml_link ? "connected" : "disconnected", VTY_NEWLINE); + + llist_for_each_entry(trx, &bts->trx_list, list) { + struct phy_instance *pinst = trx_phy_instance(trx); + vty_out(vty, " TRX %u%s", trx->nr, VTY_NEWLINE); + if (pinst) { + vty_out(vty, " phy %d %s", pinst->num, pinst->version); + if (pinst->description) + vty_out(vty, " (%s)", pinst->description); + vty_out(vty, "%s", VTY_NEWLINE); + } + } + + bts_dump_vty_features(vty, bts); + vty_out_rate_ctr_group(vty, " ", bts->ctrs); +} + + +DEFUN(show_bts, show_bts_cmd, "show bts <0-255>", + SHOW_STR "Display information about a BTS\n" + BTS_NR_STR) +{ + 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; +} + +static void trx_dump_vty(struct vty *vty, struct gsm_bts_trx *trx) +{ + vty_out(vty, "TRX %u of BTS %u is on ARFCN %u%s", + trx->nr, trx->bts->nr, trx->arfcn, VTY_NEWLINE); + vty_out(vty, "Description: %s%s", + trx->description ? trx->description : "(null)", VTY_NEWLINE); + vty_out(vty, " RF Nominal Power: %d dBm, reduced by %u dB, " + "resulting BS power: %d dBm%s", + trx->nominal_power, trx->max_power_red, + trx->nominal_power - trx->max_power_red, VTY_NEWLINE); + vty_out(vty, " NM State: "); + net_dump_nmstate(vty, &trx->mo.nm_state); + vty_out(vty, " RSL State: %s%s", trx->rsl_link? "connected" : "disconnected", VTY_NEWLINE); + vty_out(vty, " Baseband Transceiver NM State: "); + net_dump_nmstate(vty, &trx->bb_transc.mo.nm_state); + vty_out(vty, " IPA stream ID: 0x%02x%s", trx->rsl_tei, VTY_NEWLINE); +} + +static inline void print_all_trx(struct vty *vty, const struct gsm_bts *bts) +{ + uint8_t trx_nr; + for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) + trx_dump_vty(vty, gsm_bts_trx_num(bts, trx_nr)); +} + +DEFUN(show_trx, + show_trx_cmd, + "show trx [<0-255>] [<0-255>]", + SHOW_STR "Display information about a TRX\n" + BTS_TRX_STR) +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_bts *bts = NULL; + int bts_nr, trx_nr; + + if (argc >= 1) { + /* use the BTS number that the user has specified */ + bts_nr = atoi(argv[0]); + if (bts_nr >= net->num_bts) { + vty_out(vty, "%% can't find BTS '%s'%s", argv[0], + VTY_NEWLINE); + return CMD_WARNING; + } + bts = gsm_bts_num(net, bts_nr); + } + if (argc >= 2) { + trx_nr = atoi(argv[1]); + if (trx_nr >= bts->num_trx) { + vty_out(vty, "%% can't find TRX '%s'%s", argv[1], + VTY_NEWLINE); + return CMD_WARNING; + } + trx_dump_vty(vty, gsm_bts_trx_num(bts, trx_nr)); + return CMD_SUCCESS; + } + if (bts) { + /* print all TRX in this BTS */ + print_all_trx(vty, bts); + return CMD_SUCCESS; + } + + for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) + print_all_trx(vty, gsm_bts_num(net, bts_nr)); + + return CMD_SUCCESS; +} + + +static void ts_dump_vty(struct vty *vty, struct gsm_bts_trx_ts *ts) +{ + vty_out(vty, "BTS %u, TRX %u, Timeslot %u, phys cfg %s, TSC %u", + ts->trx->bts->nr, ts->trx->nr, ts->nr, + gsm_pchan_name(ts->pchan), gsm_ts_tsc(ts)); + if (ts->pchan == GSM_PCHAN_TCH_F_PDCH) + vty_out(vty, " (%s mode)", + ts->flags & TS_F_PDCH_ACTIVE ? "PDCH" : "TCH/F"); + vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, " NM State: "); + net_dump_nmstate(vty, &ts->mo.nm_state); +} + +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; +} + +/* FIXME: move this to libosmogsm */ +static const struct value_string gsm48_cmode_names[] = { + { GSM48_CMODE_SIGN, "signalling" }, + { GSM48_CMODE_SPEECH_V1, "FR or HR" }, + { GSM48_CMODE_SPEECH_EFR, "EFR" }, + { GSM48_CMODE_SPEECH_AMR, "AMR" }, + { GSM48_CMODE_DATA_14k5, "CSD(14k5)" }, + { GSM48_CMODE_DATA_12k0, "CSD(12k0)" }, + { GSM48_CMODE_DATA_6k0, "CSD(6k0)" }, + { GSM48_CMODE_DATA_3k6, "CSD(3k6)" }, + { 0, NULL } +}; + +/* call vty_out() to print a string like " as TCH/H" for dynamic timeslots. + * Don't do anything if the ts is not dynamic. */ +static void vty_out_dyn_ts_status(struct vty *vty, struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + if (ts->dyn.pchan_is == ts->dyn.pchan_want) + vty_out(vty, " as %s", + gsm_pchan_name(ts->dyn.pchan_is)); + else + vty_out(vty, " switching %s -> %s", + gsm_pchan_name(ts->dyn.pchan_is), + gsm_pchan_name(ts->dyn.pchan_want)); + break; + case GSM_PCHAN_TCH_F_PDCH: + if ((ts->flags & TS_F_PDCH_PENDING_MASK) == 0) + vty_out(vty, " as %s", + (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH" + : "TCH/F"); + else + vty_out(vty, " switching %s -> %s", + (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH" + : "TCH/F", + (ts->flags & TS_F_PDCH_ACT_PENDING)? "PDCH" + : "TCH/F"); + break; + default: + /* no dyn ts */ + break; + } +} + +static void lchan_dump_full_vty(struct vty *vty, struct gsm_lchan *lchan) +{ + struct in_addr ia; + + vty_out(vty, "BTS %u, TRX %u, Timeslot %u, Lchan %u: Type %s%s", + lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr, + lchan->nr, gsm_lchant_name(lchan->type), VTY_NEWLINE); + /* show dyn TS details, if applicable */ + switch (lchan->ts->pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + vty_out(vty, " Osmocom Dyn TS:"); + vty_out_dyn_ts_status(vty, lchan->ts); + vty_out(vty, VTY_NEWLINE); + break; + case GSM_PCHAN_TCH_F_PDCH: + vty_out(vty, " IPACC Dyn PDCH TS:"); + vty_out_dyn_ts_status(vty, lchan->ts); + vty_out(vty, VTY_NEWLINE); + break; + default: + /* no dyn ts */ + break; + } + vty_out(vty, " State: %s%s%s%s", + gsm_lchans_name(lchan->state), + lchan->state == LCHAN_S_BROKEN ? " Error reason: " : "", + lchan->state == LCHAN_S_BROKEN ? lchan->broken_reason : "", + VTY_NEWLINE); + vty_out(vty, " BS Power: %d dBm, MS Power: %u dBm%s", + lchan->ts->trx->nominal_power - lchan->ts->trx->max_power_red + - lchan->bs_power*2, + ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power), + VTY_NEWLINE); + vty_out(vty, " Channel Mode / Codec: %s%s", + get_value_string(gsm48_cmode_names, lchan->tch_mode), + VTY_NEWLINE); + + if (lchan->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%02u%s", + inet_ntoa(ia), lchan->abis_ip.connect_port, + lchan->abis_ip.rtp_payload, lchan->abis_ip.speech_mode, + VTY_NEWLINE); + } +#define LAPDM_ESTABLISHED(link, sapi_idx) \ + (link).datalink[sapi_idx].dl.state == LAPD_STATE_MF_EST + vty_out(vty, " LAPDm SAPIs: DCCH %c%c, SACCH %c%c%s", + LAPDM_ESTABLISHED(lchan->lapdm_ch.lapdm_dcch, DL_SAPI0) ? '0' : '-', + LAPDM_ESTABLISHED(lchan->lapdm_ch.lapdm_dcch, DL_SAPI3) ? '3' : '-', + LAPDM_ESTABLISHED(lchan->lapdm_ch.lapdm_acch, DL_SAPI0) ? '0' : '-', + LAPDM_ESTABLISHED(lchan->lapdm_ch.lapdm_acch, DL_SAPI3) ? '3' : '-', + VTY_NEWLINE); +#undef LAPDM_ESTABLISHED + vty_out(vty, " Valid System Information: 0x%08x%s", + lchan->si.valid, VTY_NEWLINE); + /* TODO: L1 SAPI (but those are BTS speific :( */ + /* TODO: AMR bits */ + vty_out(vty, " MS Timing Offset: %d, propagation delay: %d symbols %s", + lchan->ms_t_offs, lchan->p_offs, VTY_NEWLINE); + if (lchan->encr.alg_id) { + vty_out(vty, " Ciphering A5/%u State: %s, N(S)=%u%s", + lchan->encr.alg_id-1, lchan_ciph_state_name(lchan->ciph_state), + lchan->ciph_ns, VTY_NEWLINE); + } + if (lchan->loopback) + vty_out(vty, " RTP/PDCH Loopback Enabled%s", VTY_NEWLINE); + vty_out(vty, " Radio Link Failure Counter 'S': %d%s", lchan->s, VTY_NEWLINE); + /* TODO: MS Power Control */ +} + +static void lchan_dump_short_vty(struct vty *vty, struct gsm_lchan *lchan) +{ + const struct gsm_meas_rep_unidir *mru = &lchan->meas.ul_res; + + vty_out(vty, "BTS %u, TRX %u, Timeslot %u %s", + lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr, + gsm_pchan_name(lchan->ts->pchan)); + vty_out_dyn_ts_status(vty, lchan->ts); + vty_out(vty, ", Lchan %u, Type %s, State %s - " + "RXL-FULL-ul: %4d dBm%s", + lchan->nr, + gsm_lchant_name(lchan->type), gsm_lchans_name(lchan->state), + rxlev2dbm(mru->full.rx_lev), + VTY_NEWLINE); +} + +static int dump_lchan_trx_ts(struct gsm_bts_trx_ts *ts, struct vty *vty, + void (*dump_cb)(struct vty *, struct gsm_lchan *)) +{ + int lchan_nr; + for (lchan_nr = 0; lchan_nr < TS_MAX_LCHAN; lchan_nr++) { + struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; + if (lchan->state == LCHAN_S_NONE) + continue; + dump_cb(vty, lchan); + } + + return CMD_SUCCESS; +} + +static int dump_lchan_trx(struct gsm_bts_trx *trx, struct vty *vty, + void (*dump_cb)(struct vty *, struct gsm_lchan *)) +{ + int ts_nr; + + for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) { + struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; + dump_lchan_trx_ts(ts, vty, dump_cb); + } + + return CMD_SUCCESS; +} + +static int dump_lchan_bts(struct gsm_bts *bts, struct vty *vty, + void (*dump_cb)(struct vty *, struct gsm_lchan *)) +{ + int trx_nr; + + for (trx_nr = 0; trx_nr < bts->num_trx; trx_nr++) { + struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, trx_nr); + dump_lchan_trx(trx, vty, dump_cb); + } + + return CMD_SUCCESS; +} + +static int lchan_summary(struct vty *vty, int argc, const char **argv, + void (*dump_cb)(struct vty *, struct gsm_lchan *)) +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_bts *bts; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + 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 struct gsm_lchan *resolve_lchan(struct gsm_network *net, + const char **argv, int idx) +{ + int bts_nr = atoi(argv[idx+0]); + int trx_nr = atoi(argv[idx+1]); + int ts_nr = atoi(argv[idx+2]); + int lchan_nr = atoi(argv[idx+3]); + struct gsm_bts *bts; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + + bts = gsm_bts_num(net, bts_nr); + if (!bts) + return NULL; + + trx = gsm_bts_trx_num(bts, trx_nr); + if (!trx) + return NULL; + + if (ts_nr >= ARRAY_SIZE(trx->ts)) + return NULL; + ts = &trx->ts[ts_nr]; + + if (lchan_nr >= ARRAY_SIZE(ts->lchan)) + return NULL; + + return &ts->lchan[lchan_nr]; +} + +#define BTS_T_T_L_STR \ + "BTS related commands\n" \ + "BTS number\n" \ + "TRX related commands\n" \ + "TRX number\n" \ + "timeslot related commands\n" \ + "timeslot number\n" \ + "logical channel commands\n" \ + "logical channel number\n" + +DEFUN(cfg_trx_gsmtap_sapi, cfg_trx_gsmtap_sapi_cmd, + "HIDDEN", "HIDDEN") +{ + int sapi; + + sapi = get_string_value(gsmtap_sapi_names, argv[0]); + OSMO_ASSERT(sapi >= 0); + + if (sapi == GSMTAP_CHANNEL_ACCH) + gsmtap_sapi_acch = 1; + else + gsmtap_sapi_mask |= (1 << sapi); + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_no_gsmtap_sapi, cfg_trx_no_gsmtap_sapi_cmd, + "HIDDEN", "HIDDEN") +{ + int sapi; + + sapi = get_string_value(gsmtap_sapi_names, argv[0]); + OSMO_ASSERT(sapi >= 0); + + if (sapi == GSMTAP_CHANNEL_ACCH) + gsmtap_sapi_acch = 0; + else + gsmtap_sapi_mask &= ~(1 << sapi); + + return CMD_SUCCESS; +} + +static struct cmd_node phy_node = { + PHY_NODE, + "%s(phy)# ", + 1, +}; + +static struct cmd_node phy_inst_node = { + PHY_INST_NODE, + "%s(phy-inst)# ", + 1, +}; + +DEFUN(cfg_phy, cfg_phy_cmd, + "phy <0-255>", + "Select a PHY to configure\n" "PHY number\n") +{ + int phy_nr = atoi(argv[0]); + struct phy_link *plink; + + plink = phy_link_by_num(phy_nr); + if (!plink) + plink = phy_link_create(tall_bts_ctx, phy_nr); + if (!plink) + return CMD_WARNING; + + vty->index = plink; + vty->index_sub = &plink->description; + vty->node = PHY_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_inst, cfg_phy_inst_cmd, + "instance <0-255>", + "Select a PHY instance to configure\n" "PHY Instance number\n") +{ + int inst_nr = atoi(argv[0]); + struct phy_link *plink = vty->index; + struct phy_instance *pinst; + + pinst = phy_instance_by_num(plink, inst_nr); + if (!pinst) { + pinst = phy_instance_create(plink, inst_nr); + if (!pinst) { + vty_out(vty, "Unable to create phy%u instance %u%s", + plink->num, inst_nr, VTY_NEWLINE); + return CMD_WARNING; + } + } + + vty->index = pinst; + vty->index_sub = &pinst->description; + vty->node = PHY_INST_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_no_inst, cfg_phy_no_inst_cmd, + "no instance <0-255>" + NO_STR "Select a PHY instance to remove\n", "PHY Instance number\n") +{ + int inst_nr = atoi(argv[0]); + struct phy_link *plink = vty->index; + struct phy_instance *pinst; + + pinst = phy_instance_by_num(plink, inst_nr); + if (!pinst) { + vty_out(vty, "No such instance %u%s", inst_nr, VTY_NEWLINE); + return CMD_WARNING; + } + + phy_instance_destroy(pinst); + + return CMD_SUCCESS; +} + +#if 0 +DEFUN(cfg_phy_type, cfg_phy_type_cmd, + "type (sysmobts|osmo-trx|virtual)", + "configure the type of the PHY\n" + "sysmocom sysmoBTS PHY\n" + "OsmoTRX based PHY\n" + "Virtual PHY (GSMTAP based)\n") +{ + struct phy_link *plink = vty->index; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Cannot change type of active PHY%s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(argv[0], "sysmobts")) + plink->type = PHY_LINK_T_SYSMOBTS; + else if (!strcmp(argv[0], "osmo-trx")) + plink->type = PHY_LINK_T_OSMOTRX; + else if (!strcmp(argv[0], "virtual")) + plink->type = PHY_LINK_T_VIRTUAL; +} +#endif + +DEFUN(bts_t_t_l_jitter_buf, + bts_t_t_l_jitter_buf_cmd, + "bts <0-0> trx <0-0> ts <0-7> lchan <0-1> rtp jitter-buffer <0-10000>", + BTS_T_T_L_STR "RTP settings\n" + "Jitter buffer\n" "Size of jitter buffer in (ms)\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_lchan *lchan; + int jitbuf_ms = atoi(argv[4]), rc; + + lchan = resolve_lchan(net, argv, 0); + if (!lchan) { + vty_out(vty, "%% can't find BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + if (!lchan->abis_ip.rtp_socket) { + vty_out(vty, "%% this channel has no active RTP stream%s", + VTY_NEWLINE); + return CMD_WARNING; + } + rc = osmo_rtp_socket_set_param(lchan->abis_ip.rtp_socket, + lchan->ts->trx->bts->rtp_jitter_adaptive ? + OSMO_RTP_P_JIT_ADAP : OSMO_RTP_P_JITBUF, + jitbuf_ms); + if (rc < 0) + vty_out(vty, "%% error setting jitter parameters: %s%s", + strerror(-rc), VTY_NEWLINE); + else + vty_out(vty, "%% jitter parameters set: %d%s", rc, VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(bts_t_t_l_loopback, + bts_t_t_l_loopback_cmd, + "bts <0-0> trx <0-0> ts <0-7> lchan <0-1> loopback", + BTS_T_T_L_STR "Set loopback\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_lchan *lchan; + + lchan = resolve_lchan(net, argv, 0); + if (!lchan) { + vty_out(vty, "%% can't find BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + lchan->loopback = 1; + + return CMD_SUCCESS; +} + +DEFUN(no_bts_t_t_l_loopback, + no_bts_t_t_l_loopback_cmd, + "no bts <0-0> trx <0-0> ts <0-7> lchan <0-1> loopback", + NO_STR BTS_T_T_L_STR "Set loopback\n") +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_lchan *lchan; + + lchan = resolve_lchan(net, argv, 0); + if (!lchan) { + vty_out(vty, "%% can't find BTS%s", VTY_NEWLINE); + return CMD_WARNING; + } + lchan->loopback = 0; + + return CMD_SUCCESS; +} + +int bts_vty_init(struct gsm_bts *bts, const struct log_info *cat) +{ + cfg_trx_gsmtap_sapi_cmd.string = vty_cmd_string_from_valstr(bts, gsmtap_sapi_names, + "gsmtap-sapi (", + "|",")", VTY_DO_LOWER); + cfg_trx_gsmtap_sapi_cmd.doc = vty_cmd_string_from_valstr(bts, gsmtap_sapi_names, + "GSMTAP SAPI\n", + "\n", "", 0); + + cfg_trx_no_gsmtap_sapi_cmd.string = vty_cmd_string_from_valstr(bts, gsmtap_sapi_names, + "no gsmtap-sapi (", + "|",")", VTY_DO_LOWER); + cfg_trx_no_gsmtap_sapi_cmd.doc = vty_cmd_string_from_valstr(bts, gsmtap_sapi_names, + NO_STR "GSMTAP SAPI\n", + "\n", "", 0); + + install_element_ve(&show_bts_cmd); + install_element_ve(&show_trx_cmd); + install_element_ve(&show_ts_cmd); + install_element_ve(&show_lchan_cmd); + install_element_ve(&show_lchan_summary_cmd); + + logging_vty_add_cmds(cat); + osmo_talloc_vty_add_cmds(); + + install_node(&bts_node, config_write_bts); + install_element(CONFIG_NODE, &cfg_bts_cmd); + install_element(CONFIG_NODE, &cfg_vty_telnet_port_cmd); + install_element(BTS_NODE, &cfg_bts_unit_id_cmd); + install_element(BTS_NODE, &cfg_bts_oml_ip_cmd); + install_element(BTS_NODE, &cfg_bts_rtp_bind_ip_cmd); + install_element(BTS_NODE, &cfg_bts_rtp_jitbuf_cmd); + install_element(BTS_NODE, &cfg_bts_band_cmd); + install_element(BTS_NODE, &cfg_description_cmd); + install_element(BTS_NODE, &cfg_no_description_cmd); + install_element(BTS_NODE, &cfg_bts_paging_queue_size_cmd); + install_element(BTS_NODE, &cfg_bts_paging_lifetime_cmd); + install_element(BTS_NODE, &cfg_bts_agch_queue_mgmt_default_cmd); + install_element(BTS_NODE, &cfg_bts_agch_queue_mgmt_params_cmd); + install_element(BTS_NODE, &cfg_bts_ul_power_target_cmd); + install_element(BTS_NODE, &cfg_bts_min_qual_rach_cmd); + install_element(BTS_NODE, &cfg_bts_min_qual_norm_cmd); + install_element(BTS_NODE, &cfg_bts_max_ber_rach_cmd); + install_element(BTS_NODE, &cfg_bts_pcu_sock_cmd); + install_element(BTS_NODE, &cfg_bts_supp_meas_toa256_cmd); + install_element(BTS_NODE, &cfg_bts_no_supp_meas_toa256_cmd); + + install_element(BTS_NODE, &cfg_trx_gsmtap_sapi_cmd); + install_element(BTS_NODE, &cfg_trx_no_gsmtap_sapi_cmd); + + /* add and link to TRX config node */ + install_element(BTS_NODE, &cfg_bts_trx_cmd); + install_node(&trx_node, config_write_dummy); + + install_element(TRX_NODE, &cfg_trx_user_gain_cmd); + install_element(TRX_NODE, &cfg_trx_pr_max_initial_cmd); + install_element(TRX_NODE, &cfg_trx_pr_step_size_cmd); + install_element(TRX_NODE, &cfg_trx_pr_step_interval_cmd); + install_element(TRX_NODE, &cfg_trx_ms_power_control_cmd); + install_element(TRX_NODE, &cfg_trx_phy_cmd); + + install_element(ENABLE_NODE, &bts_t_t_l_jitter_buf_cmd); + install_element(ENABLE_NODE, &bts_t_t_l_loopback_cmd); + install_element(ENABLE_NODE, &no_bts_t_t_l_loopback_cmd); + + install_element(CONFIG_NODE, &cfg_phy_cmd); + install_node(&phy_node, config_write_phy); + install_element(PHY_NODE, &cfg_phy_inst_cmd); + install_element(PHY_NODE, &cfg_phy_no_inst_cmd); + + install_node(&phy_inst_node, config_write_dummy); + + return 0; +} diff --git a/src/osmo-bts-litecell15/Makefile.am b/src/osmo-bts-litecell15/Makefile.am new file mode 100644 index 0000000..f30320f --- /dev/null +++ b/src/osmo-bts-litecell15/Makefile.am @@ -0,0 +1,38 @@ +AUTOMAKE_OPTIONS = subdir-objects + +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include $(LITECELL15_INCDIR) +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS) $(ORTP_CFLAGS) $(LIBSYSTEMD_CFLAGS) +COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) $(ORTP_LIBS) + +AM_CFLAGS += -DENABLE_LC15BTS + +EXTRA_DIST = misc/lc15bts_mgr.h misc/lc15bts_misc.h misc/lc15bts_par.h misc/lc15bts_led.h \ + misc/lc15bts_temp.h misc/lc15bts_power.h misc/lc15bts_clock.h \ + misc/lc15bts_bid.h misc/lc15bts_nl.h misc/lc15bts_bts.h misc/lc15bts_swd.h \ + hw_misc.h l1_if.h l1_transp.h lc15bts.h oml_router.h utils.h + +bin_PROGRAMS = osmo-bts-lc15 lc15bts-mgr lc15bts-util + +COMMON_SOURCES = main.c lc15bts.c l1_if.c oml.c lc15bts_vty.c tch.c hw_misc.c calib_file.c \ + utils.c misc/lc15bts_par.c misc/lc15bts_bid.c oml_router.c + +osmo_bts_lc15_SOURCES = $(COMMON_SOURCES) l1_transp_hw.c +osmo_bts_lc15_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) + +lc15bts_mgr_SOURCES = \ + misc/lc15bts_mgr.c misc/lc15bts_misc.c \ + misc/lc15bts_par.c misc/lc15bts_nl.c \ + misc/lc15bts_temp.c misc/lc15bts_power.c \ + misc/lc15bts_clock.c misc/lc15bts_bid.c \ + misc/lc15bts_mgr_vty.c \ + misc/lc15bts_mgr_nl.c \ + misc/lc15bts_mgr_temp.c \ + misc/lc15bts_mgr_calib.c \ + misc/lc15bts_led.c \ + misc/lc15bts_bts.c \ + misc/lc15bts_swd.c + +lc15bts_mgr_LDADD = $(top_builddir)/src/common/libbts.a $(LIBGPS_LIBS) $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCTRL_LIBS) $(LIBSYSTEMD_LIBS) $(COMMON_LDADD) + +lc15bts_util_SOURCES = misc/lc15bts_util.c misc/lc15bts_par.c +lc15bts_util_LDADD = $(LIBOSMOCORE_LIBS) diff --git a/src/osmo-bts-litecell15/calib_file.c b/src/osmo-bts-litecell15/calib_file.c new file mode 100644 index 0000000..b7049df --- /dev/null +++ b/src/osmo-bts-litecell15/calib_file.c @@ -0,0 +1,456 @@ +/* NuRAN Wireless Litecell 1.5 BTS L1 calibration file routines*/ + +/* Copyright (C) 2015 by Yves Godin + * Copyright (C) 2016 by Harald Welte + * + * Based on sysmoBTS: + * (C) 2012 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +#include "l1_if.h" +#include "lc15bts.h" +#include "utils.h" + +/* Maximum calibration data chunk size */ +#define MAX_CALIB_TBL_SIZE 65536 +/* Calibration header version */ +#define CALIB_HDR_V1 0x01 + +struct calib_file_desc { + const char *fname; + int rx; + int trx; + int rxpath; +}; + +static const struct calib_file_desc calib_files[] = { + { + .fname = "calib_rx0a.conf", + .rx = 1, + .trx = 0, + .rxpath = 0, + }, { + .fname = "calib_rx0b.conf", + .rx = 1, + .trx = 0, + .rxpath = 1, + }, { + .fname = "calib_rx1a.conf", + .rx = 1, + .trx = 1, + .rxpath = 0, + }, { + .fname = "calib_rx1b.conf", + .rx = 1, + .trx = 1, + .rxpath = 1, + }, { + .fname = "calib_tx0.conf", + .rx = 0, + .trx = 0, + }, { + .fname = "calib_tx1.conf", + .rx = 0, + .trx = 1, + }, +}; + +struct calTbl_t +{ + union + { + struct + { + uint8_t u8Version; /* Header version (1) */ + uint8_t u8Parity; /* Parity byte (xor) */ + uint8_t u8Type; /* Table type (0:TX Downlink, 1:RX-A Uplink, 2:RX-B Uplink) */ + uint8_t u8Band; /* GSM Band (0:GSM-850, 1:EGSM-900, 2:DCS-1800, 3:PCS-1900) */ + uint32_t u32Len; /* Table length in bytes including the header */ + struct + { + uint32_t u32DescOfst; /* Description section offset */ + uint32_t u32DateOfst; /* Date section offset */ + uint32_t u32StationOfst; /* Calibration test station section offset */ + uint32_t u32FpgaFwVerOfst; /* Calibration FPGA firmware version section offset */ + uint32_t u32DspFwVerOfst; /* Calibration DSP firmware section offset */ + uint32_t u32DataOfst; /* Calibration data section offset */ + } toc; + } v1; + } hdr; + + uint8_t u8RawData[MAX_CALIB_TBL_SIZE - 32]; +}; + + +static int calib_file_send(struct lc15l1_hdl *fl1h, + const struct calib_file_desc *desc); +static int calib_verify(struct lc15l1_hdl *fl1h, + const struct calib_file_desc *desc); + +/* determine next calibration file index based on supported bands */ +static int get_next_calib_file_idx(struct lc15l1_hdl *fl1h, int last_idx) +{ + struct phy_link *plink = fl1h->phy_inst->phy_link; + int i; + + for (i = last_idx+1; i < ARRAY_SIZE(calib_files); i++) { + if (calib_files[i].trx == plink->num) + return i; + } + return -1; +} + +static int calib_file_open(struct lc15l1_hdl *fl1h, + const struct calib_file_desc *desc) +{ + struct calib_send_state *st = &fl1h->st; + char *calib_path = fl1h->phy_inst->u.lc15.calib_path; + char fname[PATH_MAX]; + + if (st->fp) { + LOGP(DL1C, LOGL_NOTICE, "L1 calibration file was left opened !!\n"); + fclose(st->fp); + st->fp = NULL; + } + + fname[0] = '\0'; + snprintf(fname, sizeof(fname)-1, "%s/%s", calib_path, desc->fname); + fname[sizeof(fname)-1] = '\0'; + + st->fp = fopen(fname, "rb"); + if (!st->fp) { + LOGP(DL1C, LOGL_ERROR, + "Failed to open '%s' for calibration data.\n", fname); + return -1; + } + return 0; +} + +static int calib_file_close(struct lc15l1_hdl *fl1h) +{ + struct calib_send_state *st = &fl1h->st; + + if (st->fp) { + fclose(st->fp); + st->fp = NULL; + } + return 0; +} + +/* iteratively download the calibration data into the L1 */ + +static int calib_send_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data); + +/* send a chunk of calibration tabledata for a single specified file */ +static int calib_file_send_next_chunk(struct lc15l1_hdl *fl1h) +{ + struct calib_send_state *st = &fl1h->st; + Litecell15_Prim_t *prim; + struct msgb *msg; + size_t n; + + msg = sysp_msgb_alloc(); + prim = msgb_sysprim(msg); + + prim->id = Litecell15_PrimId_SetCalibTblReq; + prim->u.setCalibTblReq.offset = (uint32_t)ftell(st->fp); + n = fread(prim->u.setCalibTblReq.u8Data, 1, + sizeof(prim->u.setCalibTblReq.u8Data), st->fp); + prim->u.setCalibTblReq.length = n; + + + if (n == 0) { + /* The table data has been completely sent and acknowledged */ + LOGP(DL1C, LOGL_NOTICE, "L1 calibration table %s loaded\n", + calib_files[st->last_file_idx].fname); + + calib_file_close(fl1h); + + msgb_free(msg); + + /* Send the next one if any */ + st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx); + if (st->last_file_idx >= 0) { + return calib_file_send(fl1h, + &calib_files[st->last_file_idx]); + } + + LOGP(DL1C, LOGL_INFO, "L1 calibration table loading complete!\n"); + return 0; + } + + return l1if_req_compl(fl1h, msg, calib_send_compl_cb, NULL); +} + +/* send the calibration table for a single specified file */ +static int calib_file_send(struct lc15l1_hdl *fl1h, + const struct calib_file_desc *desc) +{ + struct calib_send_state *st = &fl1h->st; + int rc; + + rc = calib_file_open(fl1h, desc); + if (rc < 0) { + /* still, we'd like to continue trying to load + * calibration for all other bands */ + st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx); + if (st->last_file_idx >= 0) + return calib_file_send(fl1h, + &calib_files[st->last_file_idx]); + + LOGP(DL1C, LOGL_INFO, "L1 calibration table loading complete!\n"); + return 0; + } + + rc = calib_verify(fl1h, desc); + if ( rc < 0 ) { + LOGP(DL1C, LOGL_ERROR, "Verify L1 calibration table %s -> failed (%d)\n", desc->fname, rc); + st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx); + + if (st->last_file_idx >= 0) + return calib_file_send(fl1h, + &calib_files[st->last_file_idx]); + return 0; + + } + + LOGP(DL1C, LOGL_INFO, "Verify L1 calibration table %s -> done\n", desc->fname); + + return calib_file_send_next_chunk(fl1h); +} + +/* completion callback after every SetCalibTbl is confirmed */ +static int calib_send_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + struct calib_send_state *st = &fl1h->st; + Litecell15_Prim_t *prim = msgb_sysprim(l1_msg); + + if (prim->u.setCalibTblCnf.status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_ERROR, "L1 rejected calibration table\n"); + + msgb_free(l1_msg); + + calib_file_close(fl1h); + + /* Skip this one and try the next one */ + st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx); + if (st->last_file_idx >= 0) { + return calib_file_send(fl1h, + &calib_files[st->last_file_idx]); + } + + LOGP(DL1C, LOGL_INFO, "L1 calibration table loading complete!\n"); + return 0; + } + + msgb_free(l1_msg); + + /* Keep sending the calibration file data */ + return calib_file_send_next_chunk(fl1h); +} + +int calib_load(struct lc15l1_hdl *fl1h) +{ + int rc; + struct calib_send_state *st = &fl1h->st; + char *calib_path = fl1h->phy_inst->u.lc15.calib_path; + + if (!calib_path) { + LOGP(DL1C, LOGL_ERROR, "Calibration file path not specified\n"); + return -1; + } + + rc = get_next_calib_file_idx(fl1h, -1); + if (rc < 0) { + return -1; + } + st->last_file_idx = rc; + + return calib_file_send(fl1h, &calib_files[st->last_file_idx]); +} + + +static int calib_verify(struct lc15l1_hdl *fl1h, const struct calib_file_desc *desc) +{ + int rc, sz; + struct calib_send_state *st = &fl1h->st; + struct phy_link *plink = fl1h->phy_inst->phy_link; + char *rbuf; + struct calTbl_t *calTbl; + char calChkSum ; + + /* calculate file size in bytes */ + fseek(st->fp, 0L, SEEK_END); + sz = ftell(st->fp); + + /* rewind read poiner */ + fseek(st->fp, 0L, SEEK_SET); + + /* read file */ + rbuf = (char *) malloc( sizeof(char) * sz ); + + rc = fread(rbuf, 1, sizeof(char) * sz, st->fp); + if ( rc != sz) { + + LOGP(DL1C, LOGL_ERROR, "%s reading error\n", desc->fname); + free(rbuf); + + /* close file */ + rc = calib_file_close(fl1h); + if (rc < 0 ) { + LOGP(DL1C, LOGL_ERROR, "%s can not close\n", desc->fname); + return rc; + } + + return -2; + } + + calTbl = (struct calTbl_t*) rbuf; + /* calculate file checksum */ + calChkSum = 0; + while ( sz-- ) { + calChkSum ^= rbuf[sz]; + } + + /* validate Tx calibration parity */ + if ( calChkSum ) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid checksum %x.\n", desc->fname, calChkSum); + return -4; + } + + /* validate Tx calibration header */ + if ( calTbl->hdr.v1.u8Version != CALIB_HDR_V1 ) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid header version %u.\n", desc->fname, calTbl->hdr.v1.u8Version); + return -5; + } + + /* validate calibration description */ + if ( calTbl->hdr.v1.toc.u32DescOfst == 0xFFFFFFFF ) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration description offset.\n", desc->fname); + return -6; + } + + /* validate calibration date */ + if ( calTbl->hdr.v1.toc.u32DateOfst == 0xFFFFFFFF ) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration date offset.\n", desc->fname); + return -7; + } + + LOGP(DL1C, LOGL_INFO, "L1 calibration table %s created on %s\n", + desc->fname, + calTbl->u8RawData + calTbl->hdr.v1.toc.u32DateOfst); + + /* validate calibration station */ + if ( calTbl->hdr.v1.toc.u32StationOfst == 0xFFFFFFFF ) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration station ID offset.\n", desc->fname); + return -8; + } + + /* validate FPGA FW version */ + if ( calTbl->hdr.v1.toc.u32FpgaFwVerOfst == 0xFFFFFFFF ) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid FPGA FW version offset.\n", desc->fname); + return -9; + } + + /* validate DSP FW version */ + if ( calTbl->hdr.v1.toc.u32DspFwVerOfst == 0xFFFFFFFF ) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid DSP FW version offset.\n", desc->fname); + return -10; + } + + /* validate Tx calibration data offset */ + if ( calTbl->hdr.v1.toc.u32DataOfst == 0xFFFFFFFF ) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration data offset.\n", desc->fname); + return -11; + } + + if ( !desc->rx ) { + + /* parse min/max Tx power */ + fl1h->phy_inst->u.lc15.minTxPower = calTbl->u8RawData[calTbl->hdr.v1.toc.u32DataOfst + (5 << 2)]; + fl1h->phy_inst->u.lc15.maxTxPower = calTbl->u8RawData[calTbl->hdr.v1.toc.u32DataOfst + (6 << 2)]; + + /* override nominal Tx power of given TRX if needed */ + if ( fl1h->phy_inst->trx->nominal_power > fl1h->phy_inst->u.lc15.maxTxPower) { + LOGP(DL1C, LOGL_INFO, "Set TRX %u nominal Tx power to %d dBm (%d)\n", + plink->num, + fl1h->phy_inst->u.lc15.maxTxPower, + fl1h->phy_inst->trx->nominal_power); + + fl1h->phy_inst->trx->nominal_power = fl1h->phy_inst->u.lc15.maxTxPower; + } + + if ( fl1h->phy_inst->trx->nominal_power < fl1h->phy_inst->u.lc15.minTxPower) { + LOGP(DL1C, LOGL_INFO, "Set TRX %u nominal Tx power to %d dBm (%d)\n", + plink->num, + fl1h->phy_inst->u.lc15.minTxPower, + fl1h->phy_inst->trx->nominal_power); + + fl1h->phy_inst->trx->nominal_power = fl1h->phy_inst->u.lc15.minTxPower; + } + + if ( fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm > to_mdB(fl1h->phy_inst->u.lc15.maxTxPower) ) { + LOGP(DL1C, LOGL_INFO, "Set TRX %u Tx power parameter to %d dBm (%d)\n", + plink->num, + to_mdB(fl1h->phy_inst->u.lc15.maxTxPower), + fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm); + + fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm = to_mdB(fl1h->phy_inst->u.lc15.maxTxPower); + } + + if ( fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm < to_mdB(fl1h->phy_inst->u.lc15.minTxPower) ) { + LOGP(DL1C, LOGL_INFO, "Set TRX %u Tx power parameter to %d dBm (%d)\n", + plink->num, + to_mdB(fl1h->phy_inst->u.lc15.minTxPower), + fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm); + + fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm = to_mdB(fl1h->phy_inst->u.lc15.minTxPower); + } + + LOGP(DL1C, LOGL_DEBUG, "%s: minTxPower=%d, maxTxPower=%d\n", + desc->fname, + fl1h->phy_inst->u.lc15.minTxPower, + fl1h->phy_inst->u.lc15.maxTxPower ); + } + + /* rewind read pointer for subsequence tasks */ + fseek(st->fp, 0L, SEEK_SET); + free(rbuf); + + return 0; +} + diff --git a/src/osmo-bts-litecell15/hw_misc.c b/src/osmo-bts-litecell15/hw_misc.c new file mode 100644 index 0000000..9f070bb --- /dev/null +++ b/src/osmo-bts-litecell15/hw_misc.c @@ -0,0 +1,88 @@ +/* Misc HW routines for NuRAN Wireless Litecell 1.5 BTS */ + +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * (C) 2012 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "hw_misc.h" + +int lc15bts_led_set(enum lc15bts_led_color c) +{ + int fd, rc; + uint8_t cmd[2]; + + switch (c) { + case LED_OFF: + cmd[0] = 0; + cmd[1] = 0; + break; + case LED_RED: + cmd[0] = 1; + cmd[1] = 0; + break; + case LED_GREEN: + cmd[0] = 0; + cmd[1] = 1; + break; + case LED_ORANGE: + cmd[0] = 1; + cmd[1] = 1; + break; + default: + return -EINVAL; + } + + fd = open("/var/lc15/leds/led0/brightness", O_WRONLY); + if (fd < 0) + return -ENODEV; + + rc = write(fd, cmd[0] ? "1" : "0", 2); + if (rc != 2) { + close(fd); + return -1; + } + close(fd); + + fd = open("/var/lc15/leds/led1/brightness", O_WRONLY); + if (fd < 0) + return -ENODEV; + + rc = write(fd, cmd[1] ? "1" : "0", 2); + if (rc != 2) { + close(fd); + return -1; + } + close(fd); + return 0; +} diff --git a/src/osmo-bts-litecell15/hw_misc.h b/src/osmo-bts-litecell15/hw_misc.h new file mode 100644 index 0000000..59ed04b --- /dev/null +++ b/src/osmo-bts-litecell15/hw_misc.h @@ -0,0 +1,13 @@ +#ifndef _HW_MISC_H +#define _HW_MISC_H + +enum lc15bts_led_color { + LED_OFF, + LED_RED, + LED_GREEN, + LED_ORANGE, +}; + +int lc15bts_led_set(enum lc15bts_led_color c); + +#endif diff --git a/src/osmo-bts-litecell15/l1_if.c b/src/osmo-bts-litecell15/l1_if.c new file mode 100644 index 0000000..e6cdfd4 --- /dev/null +++ b/src/osmo-bts-litecell15/l1_if.c @@ -0,0 +1,1582 @@ +/* Interface handler for NuRAN Wireless Litecell 1.5 L1 */ + +/* Copyright (C) 2015 by Yves Godin + * Copyright (C) 2016 by Harald Welte + * + * Based on sysmoBTS: + * (C) 2011-2014 by Harald Welte + * (C) 2014 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "lc15bts.h" +#include "l1_if.h" +#include "l1_transp.h" +#include "hw_misc.h" +#include "misc/lc15bts_par.h" +#include "misc/lc15bts_bid.h" +#include "utils.h" + +extern unsigned int dsp_trace; + +struct wait_l1_conf { + struct llist_head list; /* internal linked list */ + struct osmo_timer_list timer; /* timer for L1 timeout */ + unsigned int conf_prim_id; /* primitive we expect in response */ + HANDLE conf_hLayer3; /* layer 3 handle we expect in response */ + unsigned int is_sys_prim; /* is this a system (1) or L1 (0) primitive */ + l1if_compl_cb *cb; + void *cb_data; +}; + +static void release_wlc(struct wait_l1_conf *wlc) +{ + osmo_timer_del(&wlc->timer); + talloc_free(wlc); +} + +static void l1if_req_timeout(void *data) +{ + struct wait_l1_conf *wlc = data; + + if (wlc->is_sys_prim) + LOGP(DL1C, LOGL_FATAL, "Timeout waiting for SYS primitive %s\n", + get_value_string(lc15bts_sysprim_names, wlc->conf_prim_id)); + else + LOGP(DL1C, LOGL_FATAL, "Timeout waiting for L1 primitive %s\n", + get_value_string(lc15bts_l1prim_names, wlc->conf_prim_id)); + exit(23); +} + +static HANDLE l1p_get_hLayer3(GsmL1_Prim_t *prim) +{ + switch (prim->id) { + case GsmL1_PrimId_MphInitReq: + return prim->u.mphInitReq.hLayer3; + case GsmL1_PrimId_MphCloseReq: + return prim->u.mphCloseReq.hLayer3; + case GsmL1_PrimId_MphConnectReq: + return prim->u.mphConnectReq.hLayer3; + case GsmL1_PrimId_MphDisconnectReq: + return prim->u.mphDisconnectReq.hLayer3; + case GsmL1_PrimId_MphActivateReq: + return prim->u.mphActivateReq.hLayer3; + case GsmL1_PrimId_MphDeactivateReq: + return prim->u.mphDeactivateReq.hLayer3; + case GsmL1_PrimId_MphConfigReq: + return prim->u.mphConfigReq.hLayer3; + case GsmL1_PrimId_MphMeasureReq: + return prim->u.mphMeasureReq.hLayer3; + case GsmL1_PrimId_MphInitCnf: + return prim->u.mphInitCnf.hLayer3; + case GsmL1_PrimId_MphCloseCnf: + return prim->u.mphCloseCnf.hLayer3; + case GsmL1_PrimId_MphConnectCnf: + return prim->u.mphConnectCnf.hLayer3; + case GsmL1_PrimId_MphDisconnectCnf: + return prim->u.mphDisconnectCnf.hLayer3; + case GsmL1_PrimId_MphActivateCnf: + return prim->u.mphActivateCnf.hLayer3; + case GsmL1_PrimId_MphDeactivateCnf: + return prim->u.mphDeactivateCnf.hLayer3; + case GsmL1_PrimId_MphConfigCnf: + return prim->u.mphConfigCnf.hLayer3; + case GsmL1_PrimId_MphMeasureCnf: + return prim->u.mphMeasureCnf.hLayer3; + case GsmL1_PrimId_MphTimeInd: + case GsmL1_PrimId_MphSyncInd: + case GsmL1_PrimId_PhEmptyFrameReq: + case GsmL1_PrimId_PhDataReq: + case GsmL1_PrimId_PhConnectInd: + case GsmL1_PrimId_PhReadyToSendInd: + case GsmL1_PrimId_PhDataInd: + case GsmL1_PrimId_PhRaInd: + break; + default: + LOGP(DL1C, LOGL_ERROR, "unknown L1 primitive %u\n", prim->id); + break; + } + return 0; +} + +static int _l1if_req_compl(struct lc15l1_hdl *fl1h, struct msgb *msg, + int is_system_prim, l1if_compl_cb *cb, void *data) +{ + struct wait_l1_conf *wlc; + struct osmo_wqueue *wqueue; + unsigned int timeout_secs; + + /* allocate new wsc and store reference to mutex and conf_id */ + wlc = talloc_zero(fl1h, struct wait_l1_conf); + wlc->cb = cb; + wlc->cb_data = data; + + /* Make sure we actually have received a REQUEST type primitive */ + if (is_system_prim == 0) { + GsmL1_Prim_t *l1p = msgb_l1prim(msg); + + LOGP(DL1P, LOGL_INFO, "Tx L1 prim %s\n", + get_value_string(lc15bts_l1prim_names, l1p->id)); + + if (lc15bts_get_l1prim_type(l1p->id) != L1P_T_REQ) { + LOGP(DL1C, LOGL_ERROR, "L1 Prim %s is not a Request!\n", + get_value_string(lc15bts_l1prim_names, l1p->id)); + talloc_free(wlc); + return -EINVAL; + } + wlc->is_sys_prim = 0; + wlc->conf_prim_id = lc15bts_get_l1prim_conf(l1p->id); + wlc->conf_hLayer3 = l1p_get_hLayer3(l1p); + wqueue = &fl1h->write_q[MQ_L1_WRITE]; + timeout_secs = 30; + } else { + Litecell15_Prim_t *sysp = msgb_sysprim(msg); + + LOGP(DL1C, LOGL_INFO, "Tx SYS prim %s\n", + get_value_string(lc15bts_sysprim_names, sysp->id)); + + if (lc15bts_get_sysprim_type(sysp->id) != L1P_T_REQ) { + LOGP(DL1C, LOGL_ERROR, "SYS Prim %s is not a Request!\n", + get_value_string(lc15bts_sysprim_names, sysp->id)); + talloc_free(wlc); + return -EINVAL; + } + wlc->is_sys_prim = 1; + wlc->conf_prim_id = lc15bts_get_sysprim_conf(sysp->id); + wqueue = &fl1h->write_q[MQ_SYS_WRITE]; + timeout_secs = 30; + } + + /* enqueue the message in the queue and add wsc to list */ + if (osmo_wqueue_enqueue(wqueue, msg) != 0) { + /* So we will get a timeout but the log message might help */ + LOGP(DL1C, LOGL_ERROR, "Write queue for %s full. dropping msg.\n", + is_system_prim ? "system primitive" : "gsm"); + msgb_free(msg); + } + llist_add(&wlc->list, &fl1h->wlc_list); + + /* schedule a timer for timeout_secs seconds. If DSP fails to respond, we terminate */ + wlc->timer.data = wlc; + wlc->timer.cb = l1if_req_timeout; + osmo_timer_schedule(&wlc->timer, timeout_secs, 0); + + return 0; +} + +/* send a request primitive to the L1 and schedule completion call-back */ +int l1if_req_compl(struct lc15l1_hdl *fl1h, struct msgb *msg, + l1if_compl_cb *cb, void *data) +{ + return _l1if_req_compl(fl1h, msg, 1, cb, data); +} + +int l1if_gsm_req_compl(struct lc15l1_hdl *fl1h, struct msgb *msg, + l1if_compl_cb *cb, void *data) +{ + return _l1if_req_compl(fl1h, msg, 0, cb, data); +} + +/* allocate a msgb containing a GsmL1_Prim_t */ +struct msgb *l1p_msgb_alloc(void) +{ + struct msgb *msg = msgb_alloc(sizeof(GsmL1_Prim_t), "l1_prim"); + + if (msg) + msg->l1h = msgb_put(msg, sizeof(GsmL1_Prim_t)); + + return msg; +} + +/* allocate a msgb containing a Litecell15_Prim_t */ +struct msgb *sysp_msgb_alloc(void) +{ + struct msgb *msg = msgb_alloc(sizeof(Litecell15_Prim_t), "sys_prim"); + + if (msg) + msg->l1h = msgb_put(msg, sizeof(Litecell15_Prim_t)); + + return msg; +} + +static GsmL1_PhDataReq_t * +data_req_from_rts_ind(GsmL1_Prim_t *l1p, + const GsmL1_PhReadyToSendInd_t *rts_ind) +{ + GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq; + + l1p->id = GsmL1_PrimId_PhDataReq; + + /* copy fields from PH-RSS.ind */ + data_req->hLayer1 = rts_ind->hLayer1; + data_req->u8Tn = rts_ind->u8Tn; + data_req->u32Fn = rts_ind->u32Fn; + data_req->sapi = rts_ind->sapi; + data_req->subCh = rts_ind->subCh; + data_req->u8BlockNbr = rts_ind->u8BlockNbr; + + return data_req; +} + +static GsmL1_PhEmptyFrameReq_t * +empty_req_from_rts_ind(GsmL1_Prim_t *l1p, + const GsmL1_PhReadyToSendInd_t *rts_ind) +{ + GsmL1_PhEmptyFrameReq_t *empty_req = &l1p->u.phEmptyFrameReq; + + l1p->id = GsmL1_PrimId_PhEmptyFrameReq; + + empty_req->hLayer1 = rts_ind->hLayer1; + empty_req->u8Tn = rts_ind->u8Tn; + empty_req->u32Fn = rts_ind->u32Fn; + empty_req->sapi = rts_ind->sapi; + empty_req->subCh = rts_ind->subCh; + empty_req->u8BlockNbr = rts_ind->u8BlockNbr; + + return empty_req; +} + +/* fill PH-DATA.req from l1sap primitive */ +static GsmL1_PhDataReq_t * +data_req_from_l1sap(GsmL1_Prim_t *l1p, struct lc15l1_hdl *fl1, + uint8_t tn, uint32_t fn, uint8_t sapi, uint8_t sub_ch, + uint8_t block_nr, uint8_t len) +{ + GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq; + + l1p->id = GsmL1_PrimId_PhDataReq; + + /* copy fields from PH-RSS.ind */ + data_req->hLayer1 = (HANDLE)fl1->hLayer1; + data_req->u8Tn = tn; + data_req->u32Fn = fn; + data_req->sapi = sapi; + data_req->subCh = sub_ch; + data_req->u8BlockNbr = block_nr; + + data_req->msgUnitParam.u8Size = len; + + return data_req; +} + +/* fill PH-EMPTY_FRAME.req from l1sap primitive */ +static GsmL1_PhEmptyFrameReq_t * +empty_req_from_l1sap(GsmL1_Prim_t *l1p, struct lc15l1_hdl *fl1, + uint8_t tn, uint32_t fn, uint8_t sapi, + uint8_t subch, uint8_t block_nr) +{ + GsmL1_PhEmptyFrameReq_t *empty_req = &l1p->u.phEmptyFrameReq; + + l1p->id = GsmL1_PrimId_PhEmptyFrameReq; + + empty_req->hLayer1 = (HANDLE)fl1->hLayer1; + empty_req->u8Tn = tn; + empty_req->u32Fn = fn; + empty_req->sapi = sapi; + empty_req->subCh = subch; + empty_req->u8BlockNbr = block_nr; + + return empty_req; +} + +static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap, bool use_cache) +{ + struct lc15l1_hdl *fl1 = trx_lc15l1_hdl(trx); + struct msgb *l1msg = l1p_msgb_alloc(); + struct gsm_lchan *lchan; + uint32_t u32Fn; + uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi = 0; + uint8_t chan_nr, link_id; + int len; + + if (!msg) { + LOGPFN(DL1C, LOGL_FATAL, l1sap->u.data.fn, "PH-DATA.req without msg. Please fix!\n"); + abort(); + } + + len = msgb_l2len(msg); + + chan_nr = l1sap->u.data.chan_nr; + link_id = l1sap->u.data.link_id; + u32Fn = l1sap->u.data.fn; + u8Tn = L1SAP_CHAN2TS(chan_nr); + subCh = 0x1f; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + if (L1SAP_IS_LINK_SACCH(link_id)) { + sapi = GsmL1_Sapi_Sacch; + if (!L1SAP_IS_CHAN_TCHF(chan_nr) && !L1SAP_IS_CHAN_PDCH(chan_nr)) + subCh = l1sap_chan2ss(chan_nr); + } else if (L1SAP_IS_CHAN_TCHF(chan_nr) || L1SAP_IS_CHAN_PDCH(chan_nr)) { + if (ts_is_pdch(&trx->ts[u8Tn])) { + if (L1SAP_IS_PTCCH(u32Fn)) { + sapi = GsmL1_Sapi_Ptcch; + u8BlockNbr = L1SAP_FN2PTCCHBLOCK(u32Fn); + } else { + sapi = GsmL1_Sapi_Pdtch; + u8BlockNbr = L1SAP_FN2MACBLOCK(u32Fn); + } + } else { + sapi = GsmL1_Sapi_FacchF; + u8BlockNbr = (u32Fn % 13) >> 2; + } + } else if (L1SAP_IS_CHAN_TCHH(chan_nr)) { + subCh = L1SAP_CHAN2SS_TCHH(chan_nr); + sapi = GsmL1_Sapi_FacchH; + u8BlockNbr = (u32Fn % 26) >> 3; + } else if (L1SAP_IS_CHAN_SDCCH4(chan_nr)) { + subCh = L1SAP_CHAN2SS_SDCCH4(chan_nr); + sapi = GsmL1_Sapi_Sdcch; + } else if (L1SAP_IS_CHAN_SDCCH8(chan_nr)) { + subCh = L1SAP_CHAN2SS_SDCCH8(chan_nr); + sapi = GsmL1_Sapi_Sdcch; + } else if (L1SAP_IS_CHAN_BCCH(chan_nr)) { + sapi = GsmL1_Sapi_Bcch; + } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) { + /* The sapi depends on DSP configuration, not + * on the actual SYSTEM INFORMATION 3. */ + u8BlockNbr = L1SAP_FN2CCCHBLOCK(u32Fn); + if (u8BlockNbr >= num_agch(trx, "PH-DATA-REQ")) + sapi = GsmL1_Sapi_Pch; + else + sapi = GsmL1_Sapi_Agch; + } else { + LOGPFN(DL1C, LOGL_NOTICE, u32Fn, "unknown prim %d op %d " + "chan_nr %d link_id %d\n", l1sap->oph.primitive, + l1sap->oph.operation, chan_nr, link_id); + msgb_free(l1msg); + return -EINVAL; + } + + /* convert l1sap message to GsmL1 primitive, keep payload */ + if (len) { + /* data request */ + GsmL1_Prim_t *l1p = msgb_l1prim(l1msg); + data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr, len); + if (use_cache) + memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer, + lchan->tch.dtx.facch, msgb_l2len(msg)); + else if (dtx_dl_amr_enabled(lchan) && + ((lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F) || + (lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F) || + (lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F))) { + if (sapi == GsmL1_Sapi_FacchF) { + sapi = GsmL1_Sapi_TchF; + } + if (sapi == GsmL1_Sapi_FacchH) { + sapi = GsmL1_Sapi_TchH; + subCh = L1SAP_CHAN2SS_TCHH(chan_nr); + u8BlockNbr = (u32Fn % 13) >> 2; + } + if (sapi == GsmL1_Sapi_TchH || sapi == GsmL1_Sapi_TchF) { + /* FACCH interruption of DTX silence */ + /* cache FACCH data */ + memcpy(lchan->tch.dtx.facch, msg->l2h, + msgb_l2len(msg)); + /* prepare ONSET or INH message */ + if(lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F) + l1p->u.phDataReq.msgUnitParam.u8Buffer[0] = + GsmL1_TchPlType_Amr_Onset; + else if(lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F) + l1p->u.phDataReq.msgUnitParam.u8Buffer[0] = + GsmL1_TchPlType_Amr_SidUpdateInH; + else if(lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F) + l1p->u.phDataReq.msgUnitParam.u8Buffer[0] = + GsmL1_TchPlType_Amr_SidFirstInH; + /* ignored CMR/CMI pair */ + l1p->u.phDataReq.msgUnitParam.u8Buffer[1] = 0; + l1p->u.phDataReq.msgUnitParam.u8Buffer[2] = 0; + /* update length */ + data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, + subCh, u8BlockNbr, 3); + /* update FN so it can be checked by TCH silence + resume handler */ + lchan->tch.dtx.fn = LCHAN_FN_DUMMY; + } + } else if (dtx_dl_amr_enabled(lchan) && + lchan->tch.dtx.dl_amr_fsm->state == ST_FACCH) { + /* update FN so it can be checked by TCH silence + resume handler */ + lchan->tch.dtx.fn = LCHAN_FN_DUMMY; + } + else { + OSMO_ASSERT(msgb_l2len(msg) <= sizeof(l1p->u.phDataReq.msgUnitParam.u8Buffer)); + memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer, msg->l2h, + msgb_l2len(msg)); + } + LOGPFN(DL1P, LOGL_DEBUG, u32Fn, "PH-DATA.req(%s)\n", + osmo_hexdump(l1p->u.phDataReq.msgUnitParam.u8Buffer, + l1p->u.phDataReq.msgUnitParam.u8Size)); + } else { + /* empty frame */ + GsmL1_Prim_t *l1p = msgb_l1prim(l1msg); + + empty_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr); + } + + /* send message to DSP's queue */ + if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], l1msg) != 0) { + LOGPFN(DL1P, LOGL_ERROR, u32Fn, "MQ_L1_WRITE queue full. Dropping msg.\n"); + msgb_free(l1msg); + } else + dtx_int_signal(lchan); + + if (dtx_recursion(lchan)) + ph_data_req(trx, msg, l1sap, true); + return 0; +} + +static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap, bool use_cache, bool marker) +{ + struct lc15l1_hdl *fl1 = trx_lc15l1_hdl(trx); + struct gsm_lchan *lchan; + uint32_t u32Fn; + uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi; + uint8_t chan_nr; + GsmL1_Prim_t *l1p; + struct msgb *nmsg = NULL; + int rc = -1; + + chan_nr = l1sap->u.tch.chan_nr; + u32Fn = l1sap->u.tch.fn; + u8Tn = L1SAP_CHAN2TS(chan_nr); + u8BlockNbr = (u32Fn % 13) >> 2; + if (L1SAP_IS_CHAN_TCHH(chan_nr)) { + subCh = L1SAP_CHAN2SS_TCHH(chan_nr); + sapi = GsmL1_Sapi_TchH; + } else { + subCh = 0x1f; + sapi = GsmL1_Sapi_TchF; + } + + lchan = get_lchan_by_chan_nr(trx, chan_nr); + + /* create new message and fill data */ + if (msg) { + msgb_pull(msg, sizeof(*l1sap)); + /* create new message */ + nmsg = l1p_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + l1p = msgb_l1prim(nmsg); + rc = l1if_tch_encode(lchan, + l1p->u.phDataReq.msgUnitParam.u8Buffer, + &l1p->u.phDataReq.msgUnitParam.u8Size, + msg->data, msg->len, u32Fn, use_cache, + l1sap->u.tch.marker); + if (rc < 0) { + /* no data encoded for L1: smth will be generated below */ + msgb_free(nmsg); + nmsg = NULL; + } + } + + /* no message/data, we might generate an empty traffic msg or re-send + cached SID in case of DTX */ + if (!nmsg) + nmsg = gen_empty_tch_msg(lchan, u32Fn); + + /* no traffic message, we generate an empty msg */ + if (!nmsg) { + nmsg = l1p_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + } + + l1p = msgb_l1prim(nmsg); + + /* if we provide data, or if data is already in nmsg */ + if (l1p->u.phDataReq.msgUnitParam.u8Size) { + /* data request */ + data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, + u8BlockNbr, + l1p->u.phDataReq.msgUnitParam.u8Size); + } else { + /* empty frame */ + if (trx->bts->dtxd && trx != trx->bts->c0) + lchan->tch.dtx.dl_active = true; + empty_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr); + } + /* send message to DSP's queue */ + osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], nmsg); + if (dtx_is_first_p1(lchan)) + dtx_dispatch(lchan, E_FIRST); + else + dtx_int_signal(lchan); + + if (dtx_recursion(lchan)) /* DTX: send voice after ONSET was sent */ + return ph_tch_req(trx, l1sap->oph.msg, l1sap, true, false); + + return 0; +} + +static int mph_info_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap) +{ + struct lc15l1_hdl *fl1 = trx_lc15l1_hdl(trx); + uint8_t chan_nr; + struct gsm_lchan *lchan; + int rc = 0; + + switch (l1sap->u.info.type) { + case PRIM_INFO_ACT_CIPH: + chan_nr = l1sap->u.info.u.ciph_req.chan_nr; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + if (l1sap->u.info.u.ciph_req.uplink) { + l1if_set_ciphering(fl1, lchan, 0); + lchan->ciph_state = LCHAN_CIPH_RX_REQ; + } + if (l1sap->u.info.u.ciph_req.downlink) { + l1if_set_ciphering(fl1, lchan, 1); + lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ; + } + if (l1sap->u.info.u.ciph_req.downlink + && l1sap->u.info.u.ciph_req.uplink) + lchan->ciph_state = LCHAN_CIPH_RXTX_REQ; + break; + case PRIM_INFO_ACTIVATE: + case PRIM_INFO_DEACTIVATE: + case PRIM_INFO_MODIFY: + chan_nr = l1sap->u.info.u.act_req.chan_nr; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + if (l1sap->u.info.type == PRIM_INFO_ACTIVATE) + l1if_rsl_chan_act(lchan); + else if (l1sap->u.info.type == PRIM_INFO_MODIFY) { + if (lchan->ho.active == HANDOVER_WAIT_FRAME) + l1if_rsl_chan_mod(lchan); + else + l1if_rsl_mode_modify(lchan); + } else if (l1sap->u.info.u.act_req.sacch_only) + l1if_rsl_deact_sacch(lchan); + else + l1if_rsl_chan_rel(lchan); + break; + default: + LOGP(DL1C, LOGL_NOTICE, "unknown MPH-INFO.req %d\n", + l1sap->u.info.type); + rc = -EINVAL; + } + + return rc; +} + +/* primitive from common part */ +int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) +{ + struct msgb *msg = l1sap->oph.msg; + int rc = 0; + + /* called functions MUST NOT take ownership of msgb, as it is + * free()d below */ + switch (OSMO_PRIM_HDR(&l1sap->oph)) { + case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST): + rc = ph_data_req(trx, msg, l1sap, false); + break; + case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST): + rc = ph_tch_req(trx, msg, l1sap, false, l1sap->u.tch.marker); + break; + case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST): + rc = mph_info_req(trx, msg, l1sap); + break; + default: + LOGP(DL1C, LOGL_NOTICE, "unknown prim %d op %d\n", + l1sap->oph.primitive, l1sap->oph.operation); + rc = -EINVAL; + } + + msgb_free(msg); + + return rc; +} + +static int handle_mph_time_ind(struct lc15l1_hdl *fl1, + GsmL1_MphTimeInd_t *time_ind, + struct msgb *msg) +{ + struct gsm_bts_trx *trx = lc15l1_hdl_trx(fl1); + struct gsm_bts *bts = trx->bts; + struct osmo_phsap_prim l1sap; + uint32_t fn; + + /* increment the primitive count for the alive timer */ + fl1->alive_prim_cnt++; + + /* ignore every time indication, except for c0 */ + if (trx != bts->c0) { + msgb_free(msg); + return 0; + } + + fn = time_ind->u32Fn; + + memset(&l1sap, 0, sizeof(l1sap)); + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, + PRIM_OP_INDICATION, NULL); + l1sap.u.info.type = PRIM_INFO_TIME; + l1sap.u.info.u.time_ind.fn = fn; + + msgb_free(msg); + + return l1sap_up(trx, &l1sap); +} + +static enum gsm_phys_chan_config pick_pchan(struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_PDCH: + if (ts->flags & TS_F_PDCH_ACTIVE) + return GSM_PCHAN_PDCH; + return GSM_PCHAN_TCH_F; + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + return ts->dyn.pchan_is; + default: + return ts->pchan; + } +} + +static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts, + GsmL1_Sapi_t sapi, GsmL1_SubCh_t subCh, + uint8_t u8Tn, uint32_t u32Fn) +{ + uint8_t cbits = 0; + enum gsm_phys_chan_config pchan = pick_pchan(ts); + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH); + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH); + + switch (sapi) { + case GsmL1_Sapi_Bcch: + cbits = 0x10; + break; + case GsmL1_Sapi_Sacch: + switch(pchan) { + case GSM_PCHAN_TCH_F: + cbits = 0x01; + break; + case GSM_PCHAN_TCH_H: + cbits = 0x02 + subCh; + break; + case GSM_PCHAN_CCCH_SDCCH4: + cbits = 0x04 + subCh; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + cbits = 0x08 + subCh; + break; + default: + LOGP(DL1C, LOGL_ERROR, "SACCH for pchan %d?\n", + pchan); + return 0; + } + break; + case GsmL1_Sapi_Sdcch: + switch(pchan) { + case GSM_PCHAN_CCCH_SDCCH4: + cbits = 0x04 + subCh; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + cbits = 0x08 + subCh; + break; + default: + LOGP(DL1C, LOGL_ERROR, "SDCCH for pchan %d?\n", + pchan); + return 0; + } + break; + case GsmL1_Sapi_Agch: + case GsmL1_Sapi_Pch: + cbits = 0x12; + break; + case GsmL1_Sapi_Pdtch: + case GsmL1_Sapi_Pacch: + switch(pchan) { + case GSM_PCHAN_PDCH: + cbits = 0x01; + break; + default: + LOGP(DL1C, LOGL_ERROR, "PDTCH for pchan %d?\n", + pchan); + return 0; + } + break; + case GsmL1_Sapi_TchF: + cbits = 0x01; + break; + case GsmL1_Sapi_TchH: + cbits = 0x02 + subCh; + break; + case GsmL1_Sapi_FacchF: + cbits = 0x01; + break; + case GsmL1_Sapi_FacchH: + cbits = 0x02 + subCh; + break; + case GsmL1_Sapi_Ptcch: + if (!L1SAP_IS_PTCCH(u32Fn)) { + LOGP(DL1C, LOGL_FATAL, "Not expecting PTCCH at frame " + "number other than 12, got it at %u (%u). " + "Please fix!\n", u32Fn % 52, u32Fn); + abort(); + } + switch(pchan) { + case GSM_PCHAN_PDCH: + cbits = 0x01; + break; + default: + LOGP(DL1C, LOGL_ERROR, "PTCCH for pchan %d?\n", + pchan); + return 0; + } + break; + default: + return 0; + } + + /* not reached due to default case above */ + return (cbits << 3) | u8Tn; +} + +static int handle_ph_readytosend_ind(struct lc15l1_hdl *fl1, + GsmL1_PhReadyToSendInd_t *rts_ind, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = lc15l1_hdl_trx(fl1); + struct gsm_bts *bts = trx->bts; + struct msgb *resp_msg; + GsmL1_PhDataReq_t *data_req; + GsmL1_MsgUnitParam_t *msu_param; + struct gsm_time g_time; + uint32_t t3p; + int rc; + struct osmo_phsap_prim *l1sap; + uint8_t chan_nr, link_id; + uint32_t fn; + + /* check if primitive should be handled by common part */ + chan_nr = chan_nr_by_sapi(&trx->ts[rts_ind->u8Tn], rts_ind->sapi, + rts_ind->subCh, rts_ind->u8Tn, rts_ind->u32Fn); + if (chan_nr) { + fn = rts_ind->u32Fn; + if (rts_ind->sapi == GsmL1_Sapi_Sacch) + link_id = LID_SACCH; + else + link_id = LID_DEDIC; + /* recycle the msgb and use it for the L1 primitive, + * which means that we (or our caller) must not free it */ + rc = msgb_trim(l1p_msg, sizeof(*l1sap)); + if (rc < 0) + MSGB_ABORT(l1p_msg, "No room for primitive\n"); + l1sap = msgb_l1sap_prim(l1p_msg); + if (rts_ind->sapi == GsmL1_Sapi_TchF + || rts_ind->sapi == GsmL1_Sapi_TchH) { + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH_RTS, + PRIM_OP_INDICATION, l1p_msg); + l1sap->u.tch.chan_nr = chan_nr; + l1sap->u.tch.fn = fn; + } else { + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RTS, + PRIM_OP_INDICATION, l1p_msg); + l1sap->u.data.link_id = link_id; + l1sap->u.data.chan_nr = chan_nr; + l1sap->u.data.fn = fn; + } + + return l1sap_up(trx, l1sap); + } + + gsm_fn2gsmtime(&g_time, rts_ind->u32Fn); + + DEBUGPGT(DL1P, &g_time, "Rx PH-RTS.ind SAPI=%s\n", + get_value_string(lc15bts_l1sapi_names, rts_ind->sapi)); + + /* in all other cases, we need to allocate a new PH-DATA.ind + * primitive msgb and start to fill it */ + resp_msg = l1p_msgb_alloc(); + data_req = data_req_from_rts_ind(msgb_l1prim(resp_msg), rts_ind); + msu_param = &data_req->msgUnitParam; + + /* set default size */ + msu_param->u8Size = GSM_MACBLOCK_LEN; + + switch (rts_ind->sapi) { + case GsmL1_Sapi_Sch: + /* compute T3prime */ + t3p = (g_time.t3 - 1) / 10; + /* fill SCH burst with data */ + msu_param->u8Size = 4; + msu_param->u8Buffer[0] = (bts->bsic << 2) | (g_time.t1 >> 9); + msu_param->u8Buffer[1] = (g_time.t1 >> 1); + msu_param->u8Buffer[2] = (g_time.t1 << 7) | (g_time.t2 << 2) | (t3p >> 1); + msu_param->u8Buffer[3] = (t3p & 1); + break; + case GsmL1_Sapi_Prach: + goto empty_frame; + break; + case GsmL1_Sapi_Cbch: + /* get them from bts->si_buf[] */ + bts_cbch_get(bts, msu_param->u8Buffer, &g_time); + break; + default: + memcpy(msu_param->u8Buffer, fill_frame, GSM_MACBLOCK_LEN); + break; + } +tx: + + /* transmit */ + if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], resp_msg) != 0) { + LOGPGT(DL1C, LOGL_ERROR, &g_time, "MQ_L1_WRITE queue full. Dropping msg.\n"); + msgb_free(resp_msg); + } + + /* free the msgb, as we have not handed it to l1sap and thus + * need to release its memory */ + msgb_free(l1p_msg); + return 0; + +empty_frame: + /* in case we decide to send an empty frame... */ + empty_req_from_rts_ind(msgb_l1prim(resp_msg), rts_ind); + + goto tx; +} + +static void dump_meas_res(int ll, GsmL1_MeasParam_t *m) +{ + LOGPC(DL1C, ll, ", Meas: RSSI %-3.2f dBm, Qual %-3.2f dB, " + "BER %-3.2f, Timing %d\n", m->fRssi, m->fLinkQuality, + m->fBer, m->i16BurstTiming); +} + +static int process_meas_res(struct gsm_bts_trx *trx, uint8_t chan_nr, + GsmL1_MeasParam_t *m, uint32_t fn) +{ + struct osmo_phsap_prim l1sap; + memset(&l1sap, 0, sizeof(l1sap)); + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, + PRIM_OP_INDICATION, NULL); + l1sap.u.info.type = PRIM_INFO_MEAS; + l1sap.u.info.u.meas_ind.chan_nr = chan_nr; + l1sap.u.info.u.meas_ind.ta_offs_256bits = m->i16BurstTiming*64; + l1sap.u.info.u.meas_ind.ber10k = (unsigned int) (m->fBer * 10000); + l1sap.u.info.u.meas_ind.inv_rssi = (uint8_t) (m->fRssi * -1); + l1sap.u.info.u.meas_ind.fn = fn; + + /* l1sap wants to take msgb ownership. However, as there is no + * msg, it will msgb_free(l1sap.oph.msg == NULL) */ + return l1sap_up(trx, &l1sap); +} + +static int handle_ph_data_ind(struct lc15l1_hdl *fl1, GsmL1_PhDataInd_t *data_ind, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = lc15l1_hdl_trx(fl1); + uint8_t chan_nr, link_id; + struct osmo_phsap_prim *l1sap; + uint32_t fn; + struct gsm_time g_time; + uint8_t *data, len; + int rc = 0; + int8_t rssi; + + chan_nr = chan_nr_by_sapi(&trx->ts[data_ind->u8Tn], data_ind->sapi, + data_ind->subCh, data_ind->u8Tn, data_ind->u32Fn); + fn = data_ind->u32Fn; + link_id = (data_ind->sapi == GsmL1_Sapi_Sacch) ? LID_SACCH : LID_DEDIC; + gsm_fn2gsmtime(&g_time, fn); + + if (!chan_nr) { + LOGPGT(DL1C, LOGL_ERROR, &g_time, "PH-DATA-INDICATION for unknown sapi %s (%d)\n", + get_value_string(lc15bts_l1sapi_names, data_ind->sapi), data_ind->sapi); + msgb_free(l1p_msg); + return ENOTSUP; + } + + process_meas_res(trx, chan_nr, &data_ind->measParam, fn); + + + DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind %s (hL2 %08x): %s\n", + get_value_string(lc15bts_l1sapi_names, data_ind->sapi), (uint32_t)data_ind->hLayer2, + osmo_hexdump(data_ind->msgUnitParam.u8Buffer, data_ind->msgUnitParam.u8Size)); + dump_meas_res(LOGL_DEBUG, &data_ind->measParam); + + /* check for TCH */ + if (data_ind->sapi == GsmL1_Sapi_TchF + || data_ind->sapi == GsmL1_Sapi_TchH) { + /* TCH speech frame handling */ + rc = l1if_tch_rx(trx, chan_nr, l1p_msg); + msgb_free(l1p_msg); + return rc; + } + + /* get rssi */ + rssi = (int8_t) (data_ind->measParam.fRssi); + /* get data pointer and length */ + data = data_ind->msgUnitParam.u8Buffer; + len = data_ind->msgUnitParam.u8Size; + /* pull lower header part before data */ + msgb_pull(l1p_msg, data - l1p_msg->data); + /* trim remaining data to it's size, to get rid of upper header part */ + rc = msgb_trim(l1p_msg, len); + if (rc < 0) + MSGB_ABORT(l1p_msg, "No room for primitive data\n"); + l1p_msg->l2h = l1p_msg->data; + /* push new l1 header */ + l1p_msg->l1h = msgb_push(l1p_msg, sizeof(*l1sap)); + /* fill header */ + l1sap = msgb_l1sap_prim(l1p_msg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA, + PRIM_OP_INDICATION, l1p_msg); + l1sap->u.data.link_id = link_id; + l1sap->u.data.chan_nr = chan_nr; + l1sap->u.data.fn = fn; + l1sap->u.data.rssi = rssi; + if (!pcu_direct) { + l1sap->u.data.ber10k = data_ind->measParam.fBer * 10000; + l1sap->u.data.ta_offs_256bits = data_ind->measParam.i16BurstTiming*64; + l1sap->u.data.lqual_cb = data_ind->measParam.fLinkQuality * 10; + } + return l1sap_up(trx, l1sap); +} + +static int handle_ph_ra_ind(struct lc15l1_hdl *fl1, GsmL1_PhRaInd_t *ra_ind, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = lc15l1_hdl_trx(fl1); + struct gsm_bts *bts = trx->bts; + struct gsm_lchan *lchan; + struct osmo_phsap_prim *l1sap; + int rc; + struct ph_rach_ind_param rach_ind_param; + + /* FIXME: this should be deprecated/obsoleted as it bypasses rach.busy counting */ + if (ra_ind->measParam.fLinkQuality < bts->min_qual_rach) { + msgb_free(l1p_msg); + return 0; + } + + dump_meas_res(LOGL_DEBUG, &ra_ind->measParam); + + if ((ra_ind->msgUnitParam.u8Size != 1) && + (ra_ind->msgUnitParam.u8Size != 2)) { + LOGPFN(DL1P, LOGL_ERROR, ra_ind->u32Fn, "PH-RACH-INDICATION has %d bits\n", ra_ind->sapi); + msgb_free(l1p_msg); + return 0; + } + + /* We need to evaluate ra_ind before below msgb_trim(), since that invalidates *ra_ind. */ + rach_ind_param = (struct ph_rach_ind_param) { + /* .chan_nr set below */ + /* .ra set below */ + .acc_delay = 0, + .fn = ra_ind->u32Fn, + /* .is_11bit set below */ + /* .burst_type set below */ + .rssi = (int8_t) ra_ind->measParam.fRssi, + .ber10k = (unsigned int) (ra_ind->measParam.fBer * 10000.0), + .acc_delay_256bits = ra_ind->measParam.i16BurstTiming * 64, + }; + + lchan = l1if_hLayer_to_lchan(trx, (uint32_t)ra_ind->hLayer2); + if (!lchan || lchan->ts->pchan == GSM_PCHAN_CCCH || + lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4 || + lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH) + rach_ind_param.chan_nr = 0x88; + else + rach_ind_param.chan_nr = gsm_lchan2chan_nr(lchan); + + if (ra_ind->msgUnitParam.u8Size == 2) { + uint16_t temp; + uint16_t ra = ra_ind->msgUnitParam.u8Buffer[0]; + ra = ra << 3; + temp = (ra_ind->msgUnitParam.u8Buffer[1] & 0x7); + ra = ra | temp; + rach_ind_param.is_11bit = 1; + rach_ind_param.ra = ra; + } else { + rach_ind_param.is_11bit = 0; + rach_ind_param.ra = ra_ind->msgUnitParam.u8Buffer[0]; + } + + /* the old legacy full-bits acc_delay cannot express negative values */ + if (ra_ind->measParam.i16BurstTiming > 0) + rach_ind_param.acc_delay = ra_ind->measParam.i16BurstTiming >> 2; + + /* mapping of the burst type, the values are specific to + * osmo-bts-litecell15 */ + switch (ra_ind->burstType) { + case GsmL1_BurstType_Access_0: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_ACCESS_0; + break; + case GsmL1_BurstType_Access_1: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_ACCESS_1; + break; + case GsmL1_BurstType_Access_2: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_ACCESS_2; + break; + default: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_NONE; + break; + } + + /* msgb_trim() invalidates ra_ind, make that abundantly clear: */ + ra_ind = NULL; + rc = msgb_trim(l1p_msg, sizeof(*l1sap)); + if (rc < 0) + MSGB_ABORT(l1p_msg, "No room for primitive data\n"); + l1sap = msgb_l1sap_prim(l1p_msg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, + l1p_msg); + l1sap->u.rach_ind = rach_ind_param; + + return l1sap_up(trx, l1sap); +} + +/* handle any random indication from the L1 */ +static int l1if_handle_ind(struct lc15l1_hdl *fl1, struct msgb *msg) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(msg); + int rc = 0; + + /* all the below called functions must take ownership of the msgb */ + switch (l1p->id) { + case GsmL1_PrimId_MphTimeInd: + rc = handle_mph_time_ind(fl1, &l1p->u.mphTimeInd, msg); + break; + case GsmL1_PrimId_MphSyncInd: + msgb_free(msg); + break; + case GsmL1_PrimId_PhConnectInd: + msgb_free(msg); + break; + case GsmL1_PrimId_PhReadyToSendInd: + rc = handle_ph_readytosend_ind(fl1, &l1p->u.phReadyToSendInd, + msg); + break; + case GsmL1_PrimId_PhDataInd: + rc = handle_ph_data_ind(fl1, &l1p->u.phDataInd, msg); + break; + case GsmL1_PrimId_PhRaInd: + rc = handle_ph_ra_ind(fl1, &l1p->u.phRaInd, msg); + break; + default: + msgb_free(msg); + } + + return rc; +} + +static inline int is_prim_compat(GsmL1_Prim_t *l1p, struct wait_l1_conf *wlc) +{ + if (wlc->is_sys_prim != 0) + return 0; + if (l1p->id != wlc->conf_prim_id) + return 0; + if (l1p_get_hLayer3(l1p) != wlc->conf_hLayer3) + return 0; + return 1; +} + +int l1if_handle_l1prim(int wq, struct lc15l1_hdl *fl1h, struct msgb *msg) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(msg); + struct wait_l1_conf *wlc; + int rc; + + switch (l1p->id) { + case GsmL1_PrimId_MphTimeInd: + /* silent, don't clog the log file */ + break; + default: + LOGP(DL1P, LOGL_DEBUG, "Rx L1 prim %s on queue %d\n", + get_value_string(lc15bts_l1prim_names, l1p->id), wq); + } + + /* check if this is a resposne to a sync-waiting request */ + llist_for_each_entry(wlc, &fl1h->wlc_list, list) { + if (is_prim_compat(l1p, wlc)) { + llist_del(&wlc->list); + if (wlc->cb) { + /* call-back function must take + * ownership of msgb */ + rc = wlc->cb(lc15l1_hdl_trx(fl1h), msg, + wlc->cb_data); + } else { + rc = 0; + msgb_free(msg); + } + release_wlc(wlc); + return rc; + } + } + + /* if we reach here, it is not a Conf for a pending Req */ + return l1if_handle_ind(fl1h, msg); +} + +int l1if_handle_sysprim(struct lc15l1_hdl *fl1h, struct msgb *msg) +{ + Litecell15_Prim_t *sysp = msgb_sysprim(msg); + struct wait_l1_conf *wlc; + int rc; + + LOGP(DL1P, LOGL_DEBUG, "Rx SYS prim %s\n", + get_value_string(lc15bts_sysprim_names, sysp->id)); + + /* check if this is a resposne to a sync-waiting request */ + llist_for_each_entry(wlc, &fl1h->wlc_list, list) { + /* the limitation here is that we cannot have multiple callers + * sending the same primitive */ + if (wlc->is_sys_prim && sysp->id == wlc->conf_prim_id) { + llist_del(&wlc->list); + if (wlc->cb) { + /* call-back function must take + * ownership of msgb */ + rc = wlc->cb(lc15l1_hdl_trx(fl1h), msg, + wlc->cb_data); + } else { + rc = 0; + msgb_free(msg); + } + release_wlc(wlc); + return rc; + } + } + /* if we reach here, it is not a Conf for a pending Req */ + return l1if_handle_ind(fl1h, msg); +} + +static int activate_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + Litecell15_Prim_t *sysp = msgb_sysprim(resp); + GsmL1_Status_t status; + int on = 0; + unsigned int i; + + if (sysp->id == Litecell15_PrimId_ActivateRfCnf) + on = 1; + + if (on) + status = sysp->u.activateRfCnf.status; + else + status = sysp->u.deactivateRfCnf.status; + + LOGP(DL1C, LOGL_INFO, "Rx RF-%sACT.conf (status=%s)\n", on ? "" : "DE", + get_value_string(lc15bts_l1status_names, status)); + + + if (on) { + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_FATAL, "RF-ACT.conf with status %s\n", + get_value_string(lc15bts_l1status_names, status)); + bts_shutdown(trx->bts, "RF-ACT failure"); + } else + bts_update_status(BTS_STATUS_RF_ACTIVE, 1); + + /* signal availability */ + oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK); + oml_mo_tx_sw_act_rep(&trx->mo); + oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK); + oml_mo_tx_sw_act_rep(&trx->bb_transc.mo); + + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) + oml_mo_state_chg(&trx->ts[i].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY); + } else { + bts_update_status(BTS_STATUS_RF_ACTIVE, 0); + oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE); + oml_mo_state_chg(&trx->bb_transc.mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE); + } + + msgb_free(resp); + + return 0; +} + +/* activate or de-activate the entire RF-Frontend */ +int l1if_activate_rf(struct lc15l1_hdl *hdl, int on) +{ + struct msgb *msg = sysp_msgb_alloc(); + Litecell15_Prim_t *sysp = msgb_sysprim(msg); + + if (on) { + sysp->id = Litecell15_PrimId_ActivateRfReq; + sysp->u.activateRfReq.msgq.u8UseTchMsgq = 0; + sysp->u.activateRfReq.msgq.u8UsePdtchMsgq = pcu_direct; + + sysp->u.activateRfReq.u8UnusedTsMode = 0; + sysp->u.activateRfReq.u8McCorrMode = 0; + + /* maximum cell size in quarter-bits, 90 == 12.456 km */ + sysp->u.activateRfReq.u8MaxCellSize = 90; + } else { + sysp->id = Litecell15_PrimId_DeactivateRfReq; + } + + return l1if_req_compl(hdl, msg, activate_rf_compl_cb, NULL); +} + +static void mute_handle_ts(struct gsm_bts_trx_ts *ts, int is_muted) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ts->lchan); i++) { + struct gsm_lchan *lchan = &ts->lchan[i]; + + if (!is_muted) + continue; + + if (lchan->state != LCHAN_S_ACTIVE) + continue; + + /* skip channels that might be active for another reason */ + if (lchan->type == GSM_LCHAN_CCCH) + continue; + if (lchan->type == GSM_LCHAN_PDTCH) + continue; + + if (lchan->s <= 0) + continue; + + lchan->s = 0; + rsl_tx_conn_fail(lchan, RSL_ERR_RADIO_LINK_FAIL); + } +} + +static int mute_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + Litecell15_Prim_t *sysp = msgb_sysprim(resp); + GsmL1_Status_t status; + + status = sysp->u.muteRfCnf.status; + + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_ERROR, "Rx RF-MUTE.conf with status %s\n", + get_value_string(lc15bts_l1status_names, status)); + oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 0); + } else { + int i; + + LOGP(DL1C, LOGL_INFO, "Rx RF-MUTE.conf with status=%s\n", + get_value_string(lc15bts_l1status_names, status)); + bts_update_status(BTS_STATUS_RF_MUTE, fl1h->last_rf_mute[0]); + oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 1); + + osmo_static_assert( + ARRAY_SIZE(trx->ts) >= ARRAY_SIZE(fl1h->last_rf_mute), + ts_array_size); + + for (i = 0; i < ARRAY_SIZE(fl1h->last_rf_mute); ++i) + mute_handle_ts(&trx->ts[i], fl1h->last_rf_mute[i]); + } + + msgb_free(resp); + + return 0; +} + +/* mute/unmute RF time slots */ +int l1if_mute_rf(struct lc15l1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb) +{ + struct msgb *msg = sysp_msgb_alloc(); + Litecell15_Prim_t *sysp = msgb_sysprim(msg); + + LOGP(DL1C, LOGL_INFO, "Tx RF-MUTE.req (%d, %d, %d, %d, %d, %d, %d, %d)\n", + mute[0], mute[1], mute[2], mute[3], + mute[4], mute[5], mute[6], mute[7] + ); + + sysp->id = Litecell15_PrimId_MuteRfReq; + memcpy(sysp->u.muteRfReq.u8Mute, mute, sizeof(sysp->u.muteRfReq.u8Mute)); + /* save for later use */ + memcpy(hdl->last_rf_mute, mute, sizeof(hdl->last_rf_mute)); + + return l1if_req_compl(hdl, msg, cb ? cb : mute_rf_compl_cb, NULL); +} + +/* call-back on arrival of DSP+FPGA version + band capability */ +static int info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + Litecell15_Prim_t *sysp = msgb_sysprim(resp); + Litecell15_SystemInfoCnf_t *sic = &sysp->u.systemInfoCnf; + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + int rc; + + fl1h->hw_info.dsp_version[0] = sic->dspVersion.major; + fl1h->hw_info.dsp_version[1] = sic->dspVersion.minor; + fl1h->hw_info.dsp_version[2] = sic->dspVersion.build; + + fl1h->hw_info.fpga_version[0] = sic->fpgaVersion.major; + fl1h->hw_info.fpga_version[1] = sic->fpgaVersion.minor; + fl1h->hw_info.fpga_version[2] = sic->fpgaVersion.build; + + LOGP(DL1C, LOGL_INFO, "DSP v%u.%u.%u, FPGA v%u.%u.%u\n", + sic->dspVersion.major, sic->dspVersion.minor, + sic->dspVersion.build, sic->fpgaVersion.major, + sic->fpgaVersion.minor, sic->fpgaVersion.build); + + if (!(fl1h->hw_info.band_support & trx->bts->band)) + LOGP(DL1C, LOGL_FATAL, "BTS band %s not supported by hw\n", + gsm_band_name(trx->bts->band)); + + /* Request the activation */ + l1if_activate_rf(fl1h, 1); + + /* load calibration tables */ + rc = calib_load(fl1h); + if (rc < 0) + LOGP(DL1C, LOGL_ERROR, "Operating without calibration; " + "unable to load tables!\n"); + + msgb_free(resp); + return 0; +} + +/* request DSP+FPGA code versions */ +static int l1if_get_info(struct lc15l1_hdl *hdl) +{ + struct msgb *msg = sysp_msgb_alloc(); + Litecell15_Prim_t *sysp = msgb_sysprim(msg); + + sysp->id = Litecell15_PrimId_SystemInfoReq; + + return l1if_req_compl(hdl, msg, info_compl_cb, NULL); +} + +static int reset_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + Litecell15_Prim_t *sysp = msgb_sysprim(resp); + GsmL1_Status_t status = sysp->u.layer1ResetCnf.status; + + LOGP(DL1C, LOGL_NOTICE, "Rx L1-RESET.conf (status=%s)\n", + get_value_string(lc15bts_l1status_names, status)); + + msgb_free(resp); + + /* If we're coming out of reset .. */ + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_FATAL, "L1-RESET.conf with status %s\n", + get_value_string(lc15bts_l1status_names, status)); + bts_shutdown(trx->bts, "L1-RESET failure"); + } + + /* as we cannot get the current DSP trace flags, we simply + * set them to zero (or whatever dsp_trace_f has been initialized to */ + l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f); + + /* obtain version information on DSP/FPGA and band capabilities */ + l1if_get_info(fl1h); + + return 0; +} + +int l1if_reset(struct lc15l1_hdl *hdl) +{ + struct msgb *msg = sysp_msgb_alloc(); + Litecell15_Prim_t *sysp = msgb_sysprim(msg); + sysp->id = Litecell15_PrimId_Layer1ResetReq; + + return l1if_req_compl(hdl, msg, reset_compl_cb, NULL); +} + +/* set the trace flags within the DSP */ +int l1if_set_trace_flags(struct lc15l1_hdl *hdl, uint32_t flags) +{ + struct msgb *msg = sysp_msgb_alloc(); + Litecell15_Prim_t *sysp = msgb_sysprim(msg); + + LOGP(DL1C, LOGL_INFO, "Tx SET-TRACE-FLAGS.req (0x%08x)\n", + flags); + + sysp->id = Litecell15_PrimId_SetTraceFlagsReq; + sysp->u.setTraceFlagsReq.u32Tf = flags; + + hdl->dsp_trace_f = flags; + + /* There is no confirmation we could wait for */ + if (osmo_wqueue_enqueue(&hdl->write_q[MQ_SYS_WRITE], msg) != 0) { + LOGP(DL1C, LOGL_ERROR, "MQ_SYS_WRITE queue full. Dropping msg\n"); + msgb_free(msg); + return -EAGAIN; + } + return 0; +} + +static int get_hwinfo(struct lc15l1_hdl *fl1h) +{ + int rc; + + rc = lc15bts_rev_get(); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "Failed to obtain LC15BTS revision: %d\n", rc); + return rc; + } + fl1h->hw_info.ver_major = rc; + + rc = lc15bts_model_get(); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "Failed to obtain LC15BTS model: %d\n", rc); + return rc; + } + fl1h->hw_info.ver_minor = rc; + + rc = lc15bts_option_get(LC15BTS_OPTION_BAND); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "Failed to obtain LC15BTS_OPTION_BAND: %d\n", rc); + return rc; + } + + switch (rc) { + case LC15BTS_BAND_850: + fl1h->hw_info.band_support = GSM_BAND_850; + break; + case LC15BTS_BAND_900: + fl1h->hw_info.band_support = GSM_BAND_900; + break; + case LC15BTS_BAND_1800: + fl1h->hw_info.band_support = GSM_BAND_1800; + break; + case LC15BTS_BAND_1900: + fl1h->hw_info.band_support = GSM_BAND_1900; + break; + default: + LOGP(DL1C, LOGL_ERROR, "Unexpected LC15BTS_BAND value: %d\n", rc); + return -1; + } + + LOGP(DL1C, LOGL_INFO, "BTS hw support band %s\n", gsm_band_name(fl1h->hw_info.band_support)); + + return 0; +} + +struct lc15l1_hdl *l1if_open(struct phy_instance *pinst) +{ + struct lc15l1_hdl *fl1h; + int rc; + + LOGP(DL1C, LOGL_INFO, "Litecell 1.5 BTS L1IF compiled against API headers " + "v%u.%u.%u\n", LITECELL15_API_VERSION >> 16, + (LITECELL15_API_VERSION >> 8) & 0xff, + LITECELL15_API_VERSION & 0xff); + + fl1h = talloc_zero(pinst, struct lc15l1_hdl); + if (!fl1h) + return NULL; + INIT_LLIST_HEAD(&fl1h->wlc_list); + + fl1h->phy_inst = pinst; + fl1h->dsp_trace_f = pinst->u.lc15.dsp_trace_f; + + get_hwinfo(fl1h); + + rc = l1if_transport_open(MQ_SYS_WRITE, fl1h); + if (rc < 0) { + talloc_free(fl1h); + return NULL; + } + + rc = l1if_transport_open(MQ_L1_WRITE, fl1h); + if (rc < 0) { + l1if_transport_close(MQ_SYS_WRITE, fl1h); + talloc_free(fl1h); + return NULL; + } + + return fl1h; +} + +int l1if_close(struct lc15l1_hdl *fl1h) +{ + l1if_transport_close(MQ_L1_WRITE, fl1h); + l1if_transport_close(MQ_SYS_WRITE, fl1h); + return 0; +} + +int bts_model_phy_link_open(struct phy_link *plink) +{ + struct phy_instance *pinst = phy_instance_by_num(plink, 0); + + OSMO_ASSERT(pinst); + + if (!pinst->trx) { + LOGP(DL1C, LOGL_NOTICE, "Ignoring phy link %d instance %d " + "because no TRX is associated with it\n", plink->num, pinst->num); + return 0; + } + phy_link_state_set(plink, PHY_LINK_CONNECTING); + + pinst->u.lc15.hdl = l1if_open(pinst); + if (!pinst->u.lc15.hdl) { + LOGP(DL1C, LOGL_FATAL, "Cannot open L1 interface\n"); + return -EIO; + } + + + struct lc15l1_hdl *fl1h = pinst->u.lc15.hdl; + fl1h->dsp_trace_f = dsp_trace; + + l1if_reset(pinst->u.lc15.hdl); + + phy_link_state_set(plink, PHY_LINK_CONNECTED); + + return 0; +} diff --git a/src/osmo-bts-litecell15/l1_if.h b/src/osmo-bts-litecell15/l1_if.h new file mode 100644 index 0000000..7feee56 --- /dev/null +++ b/src/osmo-bts-litecell15/l1_if.h @@ -0,0 +1,133 @@ +#ifndef _L1_IF_H +#define _L1_IF_H + +#include +#include +#include +#include +#include + +#include + +#include + +#include + +enum { + MQ_SYS_READ, + MQ_L1_READ, + MQ_TCH_READ, + MQ_PDTCH_READ, + _NUM_MQ_READ +}; + +enum { + MQ_SYS_WRITE, + MQ_L1_WRITE, + MQ_TCH_WRITE, + MQ_PDTCH_WRITE, + _NUM_MQ_WRITE +}; + +struct calib_send_state { + FILE *fp; + const char *path; + int last_file_idx; +}; + +struct lc15l1_hdl { + struct gsm_time gsm_time; + uint32_t hLayer1; /* handle to the L1 instance in the DSP */ + uint32_t dsp_trace_f; /* currently operational DSP trace flags */ + struct llist_head wlc_list; + + struct phy_instance *phy_inst; + + struct osmo_timer_list alive_timer; + unsigned int alive_prim_cnt; + + struct osmo_fd read_ofd[_NUM_MQ_READ]; /* osmo file descriptors */ + struct osmo_wqueue write_q[_NUM_MQ_WRITE]; + + struct { + /* from DSP/FPGA after L1 Init */ + uint8_t dsp_version[3]; + uint8_t fpga_version[3]; + uint32_t band_support; + uint8_t ver_major; + uint8_t ver_minor; + } hw_info; + + struct calib_send_state st; + + uint8_t last_rf_mute[8]; +}; + +#define msgb_l1prim(msg) ((GsmL1_Prim_t *)(msg)->l1h) +#define msgb_sysprim(msg) ((Litecell15_Prim_t *)(msg)->l1h) + +typedef int l1if_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, void *data); + +/* send a request primitive to the L1 and schedule completion call-back */ +int l1if_req_compl(struct lc15l1_hdl *fl1h, struct msgb *msg, + l1if_compl_cb *cb, void *cb_data); +int l1if_gsm_req_compl(struct lc15l1_hdl *fl1h, struct msgb *msg, + l1if_compl_cb *cb, void *cb_data); + +struct lc15l1_hdl *l1if_open(struct phy_instance *pinst); +int l1if_close(struct lc15l1_hdl *hdl); +int l1if_reset(struct lc15l1_hdl *hdl); +int l1if_activate_rf(struct lc15l1_hdl *hdl, int on); +int l1if_set_trace_flags(struct lc15l1_hdl *hdl, uint32_t flags); +int l1if_set_txpower(struct lc15l1_hdl *fl1h, float tx_power); +int l1if_mute_rf(struct lc15l1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb); + +struct msgb *l1p_msgb_alloc(void); +struct msgb *sysp_msgb_alloc(void); + +uint32_t l1if_lchan_to_hLayer(struct gsm_lchan *lchan); +struct gsm_lchan *l1if_hLayer_to_lchan(struct gsm_bts_trx *trx, uint32_t hLayer); + +/* tch.c */ +int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len, + const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn, + bool use_cache, bool marker); +int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg); +int l1if_tch_fill(struct gsm_lchan *lchan, uint8_t *l1_buffer); +struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan, uint32_t fn); + +/* ciphering */ +int l1if_set_ciphering(struct lc15l1_hdl *fl1h, + struct gsm_lchan *lchan, + int dir_downlink); + +/* channel control */ +int l1if_rsl_chan_act(struct gsm_lchan *lchan); +int l1if_rsl_chan_rel(struct gsm_lchan *lchan); +int l1if_rsl_chan_mod(struct gsm_lchan *lchan); +int l1if_rsl_deact_sacch(struct gsm_lchan *lchan); +int l1if_rsl_mode_modify(struct gsm_lchan *lchan); + +/* calibration loading */ +int calib_load(struct lc15l1_hdl *fl1h); + +/* public helpers for test */ +int bts_check_for_ciph_cmd(struct lc15l1_hdl *fl1h, + struct msgb *msg, struct gsm_lchan *lchan); +int l1if_ms_pwr_ctrl(struct gsm_lchan *lchan, const int uplink_target, + const uint8_t ms_power, const float rxLevel); + +static inline struct lc15l1_hdl *trx_lc15l1_hdl(struct gsm_bts_trx *trx) +{ + struct phy_instance *pinst = trx_phy_instance(trx); + OSMO_ASSERT(pinst); + return pinst->u.lc15.hdl; +} + +static inline struct gsm_bts_trx *lc15l1_hdl_trx(struct lc15l1_hdl *fl1h) +{ + OSMO_ASSERT(fl1h->phy_inst); + return fl1h->phy_inst->trx; +} + +#endif /* _L1_IF_H */ diff --git a/src/osmo-bts-litecell15/l1_transp.h b/src/osmo-bts-litecell15/l1_transp.h new file mode 100644 index 0000000..7d6772e --- /dev/null +++ b/src/osmo-bts-litecell15/l1_transp.h @@ -0,0 +1,14 @@ +#ifndef _L1_TRANSP_H +#define _L1_TRANSP_H + +#include + +/* functions a transport calls on arrival of primitive from BTS */ +int l1if_handle_l1prim(int wq, struct lc15l1_hdl *fl1h, struct msgb *msg); +int l1if_handle_sysprim(struct lc15l1_hdl *fl1h, struct msgb *msg); + +/* functions exported by a transport */ +int l1if_transport_open(int q, struct lc15l1_hdl *fl1h); +int l1if_transport_close(int q, struct lc15l1_hdl *fl1h); + +#endif /* _L1_TRANSP_H */ diff --git a/src/osmo-bts-litecell15/l1_transp_hw.c b/src/osmo-bts-litecell15/l1_transp_hw.c new file mode 100644 index 0000000..c8972be --- /dev/null +++ b/src/osmo-bts-litecell15/l1_transp_hw.c @@ -0,0 +1,326 @@ +/* Interface handler for Nuran Wireless Litecell 1.5 L1 (real hardware) */ + +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * (C) 2011 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "lc15bts.h" +#include "l1_if.h" +#include "l1_transp.h" + + +#define DEV_SYS_DSP2ARM_NAME "/dev/msgq/litecell15_dsp2arm_trx" +#define DEV_SYS_ARM2DSP_NAME "/dev/msgq/litecell15_arm2dsp_trx" +#define DEV_L1_DSP2ARM_NAME "/dev/msgq/gsml1_sig_dsp2arm_trx" +#define DEV_L1_ARM2DSP_NAME "/dev/msgq/gsml1_sig_arm2dsp_trx" + +#define DEV_TCH_DSP2ARM_NAME "/dev/msgq/gsml1_tch_dsp2arm_trx" +#define DEV_TCH_ARM2DSP_NAME "/dev/msgq/gsml1_tch_arm2dsp_trx" +#define DEV_PDTCH_DSP2ARM_NAME "/dev/msgq/gsml1_pdtch_dsp2arm_trx" +#define DEV_PDTCH_ARM2DSP_NAME "/dev/msgq/gsml1_pdtch_arm2dsp_trx" + +static const char *rd_devnames[] = { + [MQ_SYS_READ] = DEV_SYS_DSP2ARM_NAME, + [MQ_L1_READ] = DEV_L1_DSP2ARM_NAME, + [MQ_TCH_READ] = DEV_TCH_DSP2ARM_NAME, + [MQ_PDTCH_READ] = DEV_PDTCH_DSP2ARM_NAME, +}; + +static const char *wr_devnames[] = { + [MQ_SYS_WRITE] = DEV_SYS_ARM2DSP_NAME, + [MQ_L1_WRITE] = DEV_L1_ARM2DSP_NAME, + [MQ_TCH_WRITE] = DEV_TCH_ARM2DSP_NAME, + [MQ_PDTCH_WRITE]= DEV_PDTCH_ARM2DSP_NAME, +}; + +/* + * Make sure that all structs we read fit into the LC15BTS_PRIM_SIZE + */ +osmo_static_assert(sizeof(GsmL1_Prim_t) + 128 <= LC15BTS_PRIM_SIZE, l1_prim) +osmo_static_assert(sizeof(Litecell15_Prim_t) + 128 <= LC15BTS_PRIM_SIZE, super_prim) + +static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what) +{ + struct osmo_wqueue *queue; + + queue = container_of(fd, struct osmo_wqueue, bfd); + + if (what & BSC_FD_READ) + queue->read_cb(fd); + + if (what & BSC_FD_EXCEPT) + queue->except_cb(fd); + + if (what & BSC_FD_WRITE) { + struct iovec iov[5]; + struct msgb *msg, *tmp; + int written, count = 0; + + fd->when &= ~BSC_FD_WRITE; + + llist_for_each_entry(msg, &queue->msg_queue, list) { + /* more writes than we have */ + if (count >= ARRAY_SIZE(iov)) + break; + + iov[count].iov_base = msg->l1h; + iov[count].iov_len = msgb_l1len(msg); + count += 1; + } + + /* TODO: check if all lengths are the same. */ + + + /* Nothing scheduled? This should not happen. */ + if (count == 0) { + if (!llist_empty(&queue->msg_queue)) + fd->when |= BSC_FD_WRITE; + return 0; + } + + written = writev(fd->fd, iov, count); + if (written < 0) { + /* nothing written?! */ + if (!llist_empty(&queue->msg_queue)) + fd->when |= BSC_FD_WRITE; + return 0; + } + + /* now delete the written entries */ + written = written / iov[0].iov_len; + count = 0; + llist_for_each_entry_safe(msg, tmp, &queue->msg_queue, list) { + queue->current_length -= 1; + + llist_del(&msg->list); + msgb_free(msg); + + count += 1; + if (count >= written) + break; + } + + if (!llist_empty(&queue->msg_queue)) + fd->when |= BSC_FD_WRITE; + } + + return 0; +} + +static int prim_size_for_queue(int queue) +{ + switch (queue) { + case MQ_SYS_WRITE: + return sizeof(Litecell15_Prim_t); + case MQ_L1_WRITE: + case MQ_TCH_WRITE: + case MQ_PDTCH_WRITE: + return sizeof(GsmL1_Prim_t); + default: + /* The compiler can't know that priv_nr is an enum. Assist. */ + LOGP(DL1C, LOGL_FATAL, "writing on a wrong queue: %d\n", + queue); + assert(false); + break; + } +} + +/* callback when there's something to read from the l1 msg_queue */ +static int read_dispatch_one(struct lc15l1_hdl *fl1h, struct msgb *msg, int queue) +{ + switch (queue) { + case MQ_SYS_WRITE: + return l1if_handle_sysprim(fl1h, msg); + case MQ_L1_WRITE: + case MQ_TCH_WRITE: + case MQ_PDTCH_WRITE: + return l1if_handle_l1prim(queue, fl1h, msg); + default: + /* The compiler can't know that priv_nr is an enum. Assist. */ + LOGP(DL1C, LOGL_FATAL, "writing on a wrong queue: %d\n", + queue); + assert(false); + break; + } +}; + +static int l1if_fd_cb(struct osmo_fd *ofd, unsigned int what) +{ + int i, rc; + + const uint32_t prim_size = prim_size_for_queue(ofd->priv_nr); + uint32_t count; + + struct iovec iov[3]; + struct msgb *msg[ARRAY_SIZE(iov)]; + + for (i = 0; i < ARRAY_SIZE(iov); ++i) { + msg[i] = msgb_alloc_headroom(prim_size + 128, 128, "1l_fd"); + msg[i]->l1h = msg[i]->data; + + iov[i].iov_base = msg[i]->l1h; + iov[i].iov_len = msgb_tailroom(msg[i]); + } + + rc = readv(ofd->fd, iov, ARRAY_SIZE(iov)); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "failed to read from fd: %s\n", strerror(errno)); + /* N. B: we do not abort to let the cycle below cleanup allocated memory properly, + the return value is ignored by the caller anyway. + TODO: use libexplain's explain_readv() to provide detailed error description */ + count = 0; + } else + count = rc / prim_size; + + for (i = 0; i < count; ++i) { + msgb_put(msg[i], prim_size); + read_dispatch_one(ofd->data, msg[i], ofd->priv_nr); + } + + for (i = count; i < ARRAY_SIZE(iov); ++i) + msgb_free(msg[i]); + + return 1; +} + +/* callback when we can write to one of the l1 msg_queue devices */ +static int l1fd_write_cb(struct osmo_fd *ofd, struct msgb *msg) +{ + int rc; + + rc = write(ofd->fd, msg->l1h, msgb_l1len(msg)); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "error writing to L1 msg_queue: %s\n", + strerror(errno)); + return rc; + } else if (rc < msg->len) { + LOGP(DL1C, LOGL_ERROR, "short write to L1 msg_queue: " + "%u < %u\n", rc, msg->len); + return -EIO; + } + + return 0; +} + +int l1if_transport_open(int q, struct lc15l1_hdl *hdl) +{ + struct phy_link *plink = hdl->phy_inst->phy_link; + int rc; + char buf[PATH_MAX]; + + /* Step 1: Open all msg_queue file descriptors */ + struct osmo_fd *read_ofd = &hdl->read_ofd[q]; + struct osmo_wqueue *wq = &hdl->write_q[q]; + struct osmo_fd *write_ofd = &hdl->write_q[q].bfd; + + snprintf(buf, sizeof(buf)-1, "%s%d", rd_devnames[q], plink->num); + buf[sizeof(buf)-1] = '\0'; + + rc = open(buf, O_RDONLY); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "unable to open msg_queue %s: %s\n", + buf, strerror(errno)); + return rc; + } + read_ofd->fd = rc; + read_ofd->priv_nr = q; + read_ofd->data = hdl; + read_ofd->cb = l1if_fd_cb; + read_ofd->when = BSC_FD_READ; + rc = osmo_fd_register(read_ofd); + if (rc < 0) { + close(read_ofd->fd); + read_ofd->fd = -1; + return rc; + } + + snprintf(buf, sizeof(buf)-1, "%s%d", wr_devnames[q], plink->num); + buf[sizeof(buf)-1] = '\0'; + + rc = open(buf, O_WRONLY); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "unable to open msg_queue %s: %s\n", + buf, strerror(errno)); + goto out_read; + } + osmo_wqueue_init(wq, 10); + wq->write_cb = l1fd_write_cb; + write_ofd->cb = wqueue_vector_cb; + write_ofd->fd = rc; + write_ofd->priv_nr = q; + write_ofd->data = hdl; + write_ofd->when = BSC_FD_WRITE; + rc = osmo_fd_register(write_ofd); + if (rc < 0) { + close(write_ofd->fd); + write_ofd->fd = -1; + goto out_read; + } + + return 0; + +out_read: + close(hdl->read_ofd[q].fd); + osmo_fd_unregister(&hdl->read_ofd[q]); + + return rc; +} + +int l1if_transport_close(int q, struct lc15l1_hdl *hdl) +{ + struct osmo_fd *read_ofd = &hdl->read_ofd[q]; + struct osmo_fd *write_ofd = &hdl->write_q[q].bfd; + + osmo_fd_unregister(read_ofd); + close(read_ofd->fd); + read_ofd->fd = -1; + + osmo_fd_unregister(write_ofd); + close(write_ofd->fd); + write_ofd->fd = -1; + + return 0; +} diff --git a/src/osmo-bts-litecell15/lc15bts.c b/src/osmo-bts-litecell15/lc15bts.c new file mode 100644 index 0000000..172a7e4 --- /dev/null +++ b/src/osmo-bts-litecell15/lc15bts.c @@ -0,0 +1,332 @@ +/* NuRAN Wireless Litecell 1.5 L1 API related definitions */ + +/* Copyright (C) 2015 by Yves Godin + * based on: + * sysmobts.c + * (C) 2011 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include "lc15bts.h" + +enum l1prim_type lc15bts_get_l1prim_type(GsmL1_PrimId_t id) +{ + switch (id) { + case GsmL1_PrimId_MphInitReq: return L1P_T_REQ; + case GsmL1_PrimId_MphCloseReq: return L1P_T_REQ; + case GsmL1_PrimId_MphConnectReq: return L1P_T_REQ; + case GsmL1_PrimId_MphDisconnectReq: return L1P_T_REQ; + case GsmL1_PrimId_MphActivateReq: return L1P_T_REQ; + case GsmL1_PrimId_MphDeactivateReq: return L1P_T_REQ; + case GsmL1_PrimId_MphConfigReq: return L1P_T_REQ; + case GsmL1_PrimId_MphMeasureReq: return L1P_T_REQ; + case GsmL1_PrimId_MphInitCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphCloseCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphConnectCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphDisconnectCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphActivateCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphDeactivateCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphConfigCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphMeasureCnf: return L1P_T_CONF; + case GsmL1_PrimId_PhEmptyFrameReq: return L1P_T_REQ; + case GsmL1_PrimId_PhDataReq: return L1P_T_REQ; + case GsmL1_PrimId_MphTimeInd: return L1P_T_IND; + case GsmL1_PrimId_MphSyncInd: return L1P_T_IND; + case GsmL1_PrimId_PhConnectInd: return L1P_T_IND; + case GsmL1_PrimId_PhReadyToSendInd: return L1P_T_IND; + case GsmL1_PrimId_PhDataInd: return L1P_T_IND; + case GsmL1_PrimId_PhRaInd: return L1P_T_IND; + default: return L1P_T_INVALID; + } +} + +const struct value_string lc15bts_l1prim_names[GsmL1_PrimId_NUM+1] = { + { GsmL1_PrimId_MphInitReq, "MPH-INIT.req" }, + { GsmL1_PrimId_MphCloseReq, "MPH-CLOSE.req" }, + { GsmL1_PrimId_MphConnectReq, "MPH-CONNECT.req" }, + { GsmL1_PrimId_MphDisconnectReq,"MPH-DISCONNECT.req" }, + { GsmL1_PrimId_MphActivateReq, "MPH-ACTIVATE.req" }, + { GsmL1_PrimId_MphDeactivateReq,"MPH-DEACTIVATE.req" }, + { GsmL1_PrimId_MphConfigReq, "MPH-CONFIG.req" }, + { GsmL1_PrimId_MphMeasureReq, "MPH-MEASURE.req" }, + { GsmL1_PrimId_MphInitCnf, "MPH-INIT.conf" }, + { GsmL1_PrimId_MphCloseCnf, "MPH-CLOSE.conf" }, + { GsmL1_PrimId_MphConnectCnf, "MPH-CONNECT.conf" }, + { GsmL1_PrimId_MphDisconnectCnf,"MPH-DISCONNECT.conf" }, + { GsmL1_PrimId_MphActivateCnf, "MPH-ACTIVATE.conf" }, + { GsmL1_PrimId_MphDeactivateCnf,"MPH-DEACTIVATE.conf" }, + { GsmL1_PrimId_MphConfigCnf, "MPH-CONFIG.conf" }, + { GsmL1_PrimId_MphMeasureCnf, "MPH-MEASURE.conf" }, + { GsmL1_PrimId_MphTimeInd, "MPH-TIME.ind" }, + { GsmL1_PrimId_MphSyncInd, "MPH-SYNC.ind" }, + { GsmL1_PrimId_PhEmptyFrameReq, "PH-EMPTY_FRAME.req" }, + { GsmL1_PrimId_PhDataReq, "PH-DATA.req" }, + { GsmL1_PrimId_PhConnectInd, "PH-CONNECT.ind" }, + { GsmL1_PrimId_PhReadyToSendInd,"PH-READY_TO_SEND.ind" }, + { GsmL1_PrimId_PhDataInd, "PH-DATA.ind" }, + { GsmL1_PrimId_PhRaInd, "PH-RA.ind" }, + { 0, NULL } +}; + +GsmL1_PrimId_t lc15bts_get_l1prim_conf(GsmL1_PrimId_t id) +{ + switch (id) { + case GsmL1_PrimId_MphInitReq: return GsmL1_PrimId_MphInitCnf; + case GsmL1_PrimId_MphCloseReq: return GsmL1_PrimId_MphCloseCnf; + case GsmL1_PrimId_MphConnectReq: return GsmL1_PrimId_MphConnectCnf; + case GsmL1_PrimId_MphDisconnectReq: return GsmL1_PrimId_MphDisconnectCnf; + case GsmL1_PrimId_MphActivateReq: return GsmL1_PrimId_MphActivateCnf; + case GsmL1_PrimId_MphDeactivateReq: return GsmL1_PrimId_MphDeactivateCnf; + case GsmL1_PrimId_MphConfigReq: return GsmL1_PrimId_MphConfigCnf; + case GsmL1_PrimId_MphMeasureReq: return GsmL1_PrimId_MphMeasureCnf; + default: return -1; // Weak + } +} + +enum l1prim_type lc15bts_get_sysprim_type(Litecell15_PrimId_t id) +{ + switch (id) { + case Litecell15_PrimId_SystemInfoReq: return L1P_T_REQ; + case Litecell15_PrimId_SystemInfoCnf: return L1P_T_CONF; + case Litecell15_PrimId_SystemFailureInd: return L1P_T_IND; + case Litecell15_PrimId_ActivateRfReq: return L1P_T_REQ; + case Litecell15_PrimId_ActivateRfCnf: return L1P_T_CONF; + case Litecell15_PrimId_DeactivateRfReq: return L1P_T_REQ; + case Litecell15_PrimId_DeactivateRfCnf: return L1P_T_CONF; + case Litecell15_PrimId_SetTraceFlagsReq: return L1P_T_REQ; + case Litecell15_PrimId_Layer1ResetReq: return L1P_T_REQ; + case Litecell15_PrimId_Layer1ResetCnf: return L1P_T_CONF; + case Litecell15_PrimId_SetCalibTblReq: return L1P_T_REQ; + case Litecell15_PrimId_SetCalibTblCnf: return L1P_T_CONF; + case Litecell15_PrimId_MuteRfReq: return L1P_T_REQ; + case Litecell15_PrimId_MuteRfCnf: return L1P_T_CONF; + case Litecell15_PrimId_SetRxAttenReq: return L1P_T_REQ; + case Litecell15_PrimId_SetRxAttenCnf: return L1P_T_CONF; + default: return L1P_T_INVALID; + } +} + +const struct value_string lc15bts_sysprim_names[Litecell15_PrimId_NUM+1] = { + { Litecell15_PrimId_SystemInfoReq, "SYSTEM-INFO.req" }, + { Litecell15_PrimId_SystemInfoCnf, "SYSTEM-INFO.conf" }, + { Litecell15_PrimId_SystemFailureInd, "SYSTEM-FAILURE.ind" }, + { Litecell15_PrimId_ActivateRfReq, "ACTIVATE-RF.req" }, + { Litecell15_PrimId_ActivateRfCnf, "ACTIVATE-RF.conf" }, + { Litecell15_PrimId_DeactivateRfReq, "DEACTIVATE-RF.req" }, + { Litecell15_PrimId_DeactivateRfCnf, "DEACTIVATE-RF.conf" }, + { Litecell15_PrimId_SetTraceFlagsReq, "SET-TRACE-FLAGS.req" }, + { Litecell15_PrimId_Layer1ResetReq, "LAYER1-RESET.req" }, + { Litecell15_PrimId_Layer1ResetCnf, "LAYER1-RESET.conf" }, + { Litecell15_PrimId_SetCalibTblReq, "SET-CALIB.req" }, + { Litecell15_PrimId_SetCalibTblCnf, "SET-CALIB.cnf" }, + { Litecell15_PrimId_MuteRfReq, "MUTE-RF.req" }, + { Litecell15_PrimId_MuteRfCnf, "MUTE-RF.cnf" }, + { Litecell15_PrimId_SetRxAttenReq, "SET-RX-ATTEN.req" }, + { Litecell15_PrimId_SetRxAttenCnf, "SET-RX-ATTEN-CNF.cnf" }, + { 0, NULL } +}; + +Litecell15_PrimId_t lc15bts_get_sysprim_conf(Litecell15_PrimId_t id) +{ + switch (id) { + case Litecell15_PrimId_SystemInfoReq: return Litecell15_PrimId_SystemInfoCnf; + case Litecell15_PrimId_ActivateRfReq: return Litecell15_PrimId_ActivateRfCnf; + case Litecell15_PrimId_DeactivateRfReq: return Litecell15_PrimId_DeactivateRfCnf; + case Litecell15_PrimId_Layer1ResetReq: return Litecell15_PrimId_Layer1ResetCnf; + case Litecell15_PrimId_SetCalibTblReq: return Litecell15_PrimId_SetCalibTblCnf; + case Litecell15_PrimId_MuteRfReq: return Litecell15_PrimId_MuteRfCnf; + case Litecell15_PrimId_SetRxAttenReq: return Litecell15_PrimId_SetRxAttenCnf; + default: return -1; // Weak + } +} + +const struct value_string lc15bts_l1sapi_names[GsmL1_Sapi_NUM+1] = { + { GsmL1_Sapi_Idle, "IDLE" }, + { GsmL1_Sapi_Fcch, "FCCH" }, + { GsmL1_Sapi_Sch, "SCH" }, + { GsmL1_Sapi_Sacch, "SACCH" }, + { GsmL1_Sapi_Sdcch, "SDCCH" }, + { GsmL1_Sapi_Bcch, "BCCH" }, + { GsmL1_Sapi_Pch, "PCH" }, + { GsmL1_Sapi_Agch, "AGCH" }, + { GsmL1_Sapi_Cbch, "CBCH" }, + { GsmL1_Sapi_Rach, "RACH" }, + { GsmL1_Sapi_TchF, "TCH/F" }, + { GsmL1_Sapi_FacchF, "FACCH/F" }, + { GsmL1_Sapi_TchH, "TCH/H" }, + { GsmL1_Sapi_FacchH, "FACCH/H" }, + { GsmL1_Sapi_Nch, "NCH" }, + { GsmL1_Sapi_Pdtch, "PDTCH" }, + { GsmL1_Sapi_Pacch, "PACCH" }, + { GsmL1_Sapi_Pbcch, "PBCCH" }, + { GsmL1_Sapi_Pagch, "PAGCH" }, + { GsmL1_Sapi_Ppch, "PPCH" }, + { GsmL1_Sapi_Pnch, "PNCH" }, + { GsmL1_Sapi_Ptcch, "PTCCH" }, + { GsmL1_Sapi_Prach, "PRACH" }, + { 0, NULL } +}; + +const struct value_string lc15bts_l1status_names[GSML1_STATUS_NUM+1] = { + { GsmL1_Status_Success, "Success" }, + { GsmL1_Status_Generic, "Generic error" }, + { GsmL1_Status_NoMemory, "Not enough memory" }, + { GsmL1_Status_Timeout, "Timeout" }, + { GsmL1_Status_InvalidParam, "Invalid parameter" }, + { GsmL1_Status_Busy, "Resource busy" }, + { GsmL1_Status_NoRessource, "No more resources" }, + { GsmL1_Status_Uninitialized, "Trying to use uninitialized resource" }, + { GsmL1_Status_NullInterface, "Trying to call a NULL interface" }, + { GsmL1_Status_NullFctnPtr, "Trying to call a NULL function ptr" }, + { GsmL1_Status_BadCrc, "Bad CRC" }, + { GsmL1_Status_BadUsf, "Bad USF" }, + { GsmL1_Status_InvalidCPS, "Invalid CPS field" }, + { GsmL1_Status_UnexpectedBurst, "Unexpected burst" }, + { GsmL1_Status_UnavailCodec, "AMR codec is unavailable" }, + { GsmL1_Status_CriticalError, "Critical error" }, + { GsmL1_Status_OverheatError, "Overheat error" }, + { GsmL1_Status_DeviceError, "Device error" }, + { GsmL1_Status_FacchError, "FACCH / TCH order error" }, + { GsmL1_Status_AlreadyDeactivated, "Lchan already deactivated" }, + { GsmL1_Status_TxBurstFifoOvrn, "FIFO overrun" }, + { GsmL1_Status_TxBurstFifoUndr, "FIFO underrun" }, + { GsmL1_Status_NotSynchronized, "Not synchronized" }, + { GsmL1_Status_Unsupported, "Unsupported feature" }, + { GsmL1_Status_ClockError, "System clock error" }, + { 0, NULL } +}; + +const struct value_string lc15bts_tracef_names[29] = { + { DBG_DEBUG, "DEBUG" }, + { DBG_L1WARNING, "L1_WARNING" }, + { DBG_ERROR, "ERROR" }, + { DBG_L1RXMSG, "L1_RX_MSG" }, + { DBG_L1RXMSGBYTE, "L1_RX_MSG_BYTE" }, + { DBG_L1TXMSG, "L1_TX_MSG" }, + { DBG_L1TXMSGBYTE, "L1_TX_MSG_BYTE" }, + { DBG_MPHCNF, "MPH_CNF" }, + { DBG_MPHIND, "MPH_IND" }, + { DBG_MPHREQ, "MPH_REQ" }, + { DBG_PHIND, "PH_IND" }, + { DBG_PHREQ, "PH_REQ" }, + { DBG_PHYRF, "PHY_RF" }, + { DBG_PHYRFMSGBYTE, "PHY_MSG_BYTE" }, + { DBG_MODE, "MODE" }, + { DBG_TDMAINFO, "TDMA_INFO" }, + { DBG_BADCRC, "BAD_CRC" }, + { DBG_PHINDBYTE, "PH_IND_BYTE" }, + { DBG_PHREQBYTE, "PH_REQ_BYTE" }, + { DBG_DEVICEMSG, "DEVICE_MSG" }, + { DBG_RACHINFO, "RACH_INFO" }, + { DBG_LOGCHINFO, "LOG_CH_INFO" }, + { DBG_MEMORY, "MEMORY" }, + { DBG_PROFILING, "PROFILING" }, + { DBG_TESTCOMMENT, "TEST_COMMENT" }, + { DBG_TEST, "TEST" }, + { DBG_STATUS, "STATUS" }, + { 0, NULL } +}; + +const struct value_string lc15bts_tracef_docs[29] = { + { DBG_DEBUG, "Debug Region" }, + { DBG_L1WARNING, "L1 Warning Region" }, + { DBG_ERROR, "Error Region" }, + { DBG_L1RXMSG, "L1_RX_MSG Region" }, + { DBG_L1RXMSGBYTE, "L1_RX_MSG_BYTE Region" }, + { DBG_L1TXMSG, "L1_TX_MSG Region" }, + { DBG_L1TXMSGBYTE, "L1_TX_MSG_BYTE Region" }, + { DBG_MPHCNF, "MphConfirmation Region" }, + { DBG_MPHIND, "MphIndication Region" }, + { DBG_MPHREQ, "MphRequest Region" }, + { DBG_PHIND, "PhIndication Region" }, + { DBG_PHREQ, "PhRequest Region" }, + { DBG_PHYRF, "PhyRF Region" }, + { DBG_PHYRFMSGBYTE, "PhyRF Message Region" }, + { DBG_MODE, "Mode Region" }, + { DBG_TDMAINFO, "TDMA Info Region" }, + { DBG_BADCRC, "Bad CRC Region" }, + { DBG_PHINDBYTE, "PH_IND_BYTE" }, + { DBG_PHREQBYTE, "PH_REQ_BYTE" }, + { DBG_DEVICEMSG, "Device Message Region" }, + { DBG_RACHINFO, "RACH Info" }, + { DBG_LOGCHINFO, "LOG_CH_INFO" }, + { DBG_MEMORY, "Memory Region" }, + { DBG_PROFILING, "Profiling Region" }, + { DBG_TESTCOMMENT, "Test Comments" }, + { DBG_TEST, "Test Region" }, + { DBG_STATUS, "Status Region" }, + { 0, NULL } +}; + +const struct value_string lc15bts_tch_pl_names[] = { + { GsmL1_TchPlType_NA, "N/A" }, + { GsmL1_TchPlType_Fr, "FR" }, + { GsmL1_TchPlType_Hr, "HR" }, + { GsmL1_TchPlType_Efr, "EFR" }, + { GsmL1_TchPlType_Amr, "AMR(IF2)" }, + { GsmL1_TchPlType_Amr_SidBad, "AMR(SID BAD)" }, + { GsmL1_TchPlType_Amr_Onset, "AMR(ONSET)" }, + { GsmL1_TchPlType_Amr_Ratscch, "AMR(RATSCCH)" }, + { GsmL1_TchPlType_Amr_SidUpdateInH, "AMR(SID_UPDATE INH)" }, + { GsmL1_TchPlType_Amr_SidFirstP1, "AMR(SID_FIRST P1)" }, + { GsmL1_TchPlType_Amr_SidFirstP2, "AMR(SID_FIRST P2)" }, + { GsmL1_TchPlType_Amr_SidFirstInH, "AMR(SID_FIRST INH)" }, + { GsmL1_TchPlType_Amr_RatscchMarker, "AMR(RATSCCH MARK)" }, + { GsmL1_TchPlType_Amr_RatscchData, "AMR(RATSCCH DATA)" }, + { 0, NULL } +}; + +const struct value_string lc15bts_dir_names[] = { + { GsmL1_Dir_TxDownlink, "TxDL" }, + { GsmL1_Dir_TxUplink, "TxUL" }, + { GsmL1_Dir_RxUplink, "RxUL" }, + { GsmL1_Dir_RxDownlink, "RxDL" }, + { GsmL1_Dir_TxDownlink|GsmL1_Dir_RxUplink, "BOTH" }, + { 0, NULL } +}; + +const struct value_string lc15bts_chcomb_names[] = { + { GsmL1_LogChComb_0, "dummy" }, + { GsmL1_LogChComb_I, "tch_f" }, + { GsmL1_LogChComb_II, "tch_h" }, + { GsmL1_LogChComb_IV, "ccch" }, + { GsmL1_LogChComb_V, "ccch_sdcch4" }, + { GsmL1_LogChComb_VII, "sdcch8" }, + { GsmL1_LogChComb_XIII, "pdtch" }, + { 0, NULL } +}; + +const uint8_t pdch_msu_size[_NUM_PDCH_CS] = { + [PDCH_CS_1] = 23, + [PDCH_CS_2] = 34, + [PDCH_CS_3] = 40, + [PDCH_CS_4] = 54, + [PDCH_MCS_1] = 27, + [PDCH_MCS_2] = 33, + [PDCH_MCS_3] = 42, + [PDCH_MCS_4] = 49, + [PDCH_MCS_5] = 60, + [PDCH_MCS_6] = 78, + [PDCH_MCS_7] = 118, + [PDCH_MCS_8] = 142, + [PDCH_MCS_9] = 154 +}; diff --git a/src/osmo-bts-litecell15/lc15bts.h b/src/osmo-bts-litecell15/lc15bts.h new file mode 100644 index 0000000..4c40db0 --- /dev/null +++ b/src/osmo-bts-litecell15/lc15bts.h @@ -0,0 +1,64 @@ +#ifndef LC15BTS_H +#define LC15BTS_H + +#include +#include + +#include +#include + +/* + * Depending on the firmware version either GsmL1_Prim_t or Litecell15_Prim_t + * is the bigger struct. For earlier firmware versions the GsmL1_Prim_t was the + * bigger struct. + */ +#define LC15BTS_PRIM_SIZE \ + (OSMO_MAX(sizeof(Litecell15_Prim_t), sizeof(GsmL1_Prim_t)) + 128) + +enum l1prim_type { + L1P_T_INVALID, /* this must be 0 to detect uninitialized elements */ + L1P_T_REQ, + L1P_T_CONF, + L1P_T_IND, +}; + +enum l1prim_type lc15bts_get_l1prim_type(GsmL1_PrimId_t id); +const struct value_string lc15bts_l1prim_names[GsmL1_PrimId_NUM+1]; +GsmL1_PrimId_t lc15bts_get_l1prim_conf(GsmL1_PrimId_t id); + +enum l1prim_type lc15bts_get_sysprim_type(Litecell15_PrimId_t id); +const struct value_string lc15bts_sysprim_names[Litecell15_PrimId_NUM+1]; +Litecell15_PrimId_t lc15bts_get_sysprim_conf(Litecell15_PrimId_t id); + +const struct value_string lc15bts_l1sapi_names[GsmL1_Sapi_NUM+1]; +const struct value_string lc15bts_l1status_names[GSML1_STATUS_NUM+1]; + +const struct value_string lc15bts_tracef_names[29]; +const struct value_string lc15bts_tracef_docs[29]; + +const struct value_string lc15bts_tch_pl_names[15]; + +const struct value_string lc15bts_clksrc_names[10]; + +const struct value_string lc15bts_dir_names[6]; + +enum pdch_cs { + PDCH_CS_1, + PDCH_CS_2, + PDCH_CS_3, + PDCH_CS_4, + PDCH_MCS_1, + PDCH_MCS_2, + PDCH_MCS_3, + PDCH_MCS_4, + PDCH_MCS_5, + PDCH_MCS_6, + PDCH_MCS_7, + PDCH_MCS_8, + PDCH_MCS_9, + _NUM_PDCH_CS +}; + +const uint8_t pdch_msu_size[_NUM_PDCH_CS]; + +#endif /* LC15BTS_H */ diff --git a/src/osmo-bts-litecell15/lc15bts_vty.c b/src/osmo-bts-litecell15/lc15bts_vty.c new file mode 100644 index 0000000..46f73ab --- /dev/null +++ b/src/osmo-bts-litecell15/lc15bts_vty.c @@ -0,0 +1,416 @@ +/* VTY interface for NuRAN Wireless Litecell 1.5 */ + +/* Copyright (C) 2015 by Yves Godin + * Copyright (C) 2016 by Harald Welte + * + * Based on sysmoBTS: + * (C) 2011 by Harald Welte + * (C) 2012,2013 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "lc15bts.h" +#include "l1_if.h" +#include "utils.h" + +extern int lchan_activate(struct gsm_lchan *lchan); + +#define TRX_STR "Transceiver related commands\n" "TRX number\n" + +#define SHOW_TRX_STR \ + SHOW_STR \ + TRX_STR +#define DSP_TRACE_F_STR "DSP Trace Flag\n" + +static struct gsm_bts *vty_bts; + +/* configuration */ + +DEFUN(cfg_phy_cal_path, cfg_phy_cal_path_cmd, + "trx-calibration-path PATH", + "Set the path name to TRX calibration data\n" "Path name\n") +{ + struct phy_instance *pinst = vty->index; + + if (pinst->u.lc15.calib_path) + talloc_free(pinst->u.lc15.calib_path); + + pinst->u.lc15.calib_path = talloc_strdup(pinst, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_dsp_trace_f, cfg_phy_dsp_trace_f_cmd, + "HIDDEN", TRX_STR) +{ + struct phy_instance *pinst = vty->index; + unsigned int flag; + + flag = get_string_value(lc15bts_tracef_names, argv[1]); + pinst->u.lc15.dsp_trace_f |= ~flag; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_no_dsp_trace_f, cfg_phy_no_dsp_trace_f_cmd, + "HIDDEN", NO_STR TRX_STR) +{ + struct phy_instance *pinst = vty->index; + unsigned int flag; + + flag = get_string_value(lc15bts_tracef_names, argv[1]); + pinst->u.lc15.dsp_trace_f &= ~flag; + + return CMD_SUCCESS; +} + + +/* runtime */ + +DEFUN(show_dsp_trace_f, show_dsp_trace_f_cmd, + "show trx <0-0> dsp-trace-flags", + SHOW_TRX_STR "Display the current setting of the DSP trace flags") +{ + int trx_nr = atoi(argv[0]); + struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct lc15l1_hdl *fl1h; + int i; + + if (!trx) + return CMD_WARNING; + + fl1h = trx_lc15l1_hdl(trx); + + vty_out(vty, "Litecell15 L1 DSP trace flags:%s", VTY_NEWLINE); + for (i = 0; i < ARRAY_SIZE(lc15bts_tracef_names); i++) { + const char *endis; + + if (lc15bts_tracef_names[i].value == 0 && + lc15bts_tracef_names[i].str == NULL) + break; + + if (fl1h->dsp_trace_f & lc15bts_tracef_names[i].value) + endis = "enabled"; + else + endis = "disabled"; + + vty_out(vty, "DSP Trace %-15s %s%s", + lc15bts_tracef_names[i].str, endis, + VTY_NEWLINE); + } + + return CMD_SUCCESS; + +} + +DEFUN(dsp_trace_f, dsp_trace_f_cmd, "HIDDEN", TRX_STR) +{ + int phy_nr = atoi(argv[0]); + struct phy_instance *pinst; + struct lc15l1_hdl *fl1h; + unsigned int flag ; + + pinst = vty_get_phy_instance(vty, phy_nr, 0); + if (!pinst) + return CMD_WARNING; + + fl1h = pinst->u.lc15.hdl; + flag = get_string_value(lc15bts_tracef_names, argv[1]); + l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f | flag); + + return CMD_SUCCESS; +} + +DEFUN(no_dsp_trace_f, no_dsp_trace_f_cmd, "HIDDEN", NO_STR TRX_STR) +{ + int phy_nr = atoi(argv[0]); + struct phy_instance *pinst; + struct lc15l1_hdl *fl1h; + unsigned int flag ; + + pinst = vty_get_phy_instance(vty, phy_nr, 0); + if (!pinst) + return CMD_WARNING; + + fl1h = pinst->u.lc15.hdl; + flag = get_string_value(lc15bts_tracef_names, argv[1]); + l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f & ~flag); + + return CMD_SUCCESS; +} + +DEFUN(show_sys_info, show_sys_info_cmd, + "show phy <0-1> instance <0-0> system-information", + SHOW_TRX_STR "Display information about system\n") +{ + int phy_nr = atoi(argv[0]); + int inst_nr = atoi(argv[1]); + struct phy_link *plink = phy_link_by_num(phy_nr); + struct phy_instance *pinst; + struct lc15l1_hdl *fl1h; + int i; + + if (!plink) { + vty_out(vty, "Cannot find PHY link %u%s", + phy_nr, VTY_NEWLINE); + return CMD_WARNING; + } + pinst = phy_instance_by_num(plink, inst_nr); + if (!plink) { + vty_out(vty, "Cannot find PHY instance %u%s", + phy_nr, VTY_NEWLINE); + return CMD_WARNING; + } + fl1h = pinst->u.lc15.hdl; + + vty_out(vty, "DSP Version: %u.%u.%u, FPGA Version: %u.%u.%u%s", + fl1h->hw_info.dsp_version[0], + fl1h->hw_info.dsp_version[1], + fl1h->hw_info.dsp_version[2], + fl1h->hw_info.fpga_version[0], + fl1h->hw_info.fpga_version[1], + fl1h->hw_info.fpga_version[2], VTY_NEWLINE); + + vty_out(vty, "GSM Band Support: "); + for (i = 0; i < sizeof(fl1h->hw_info.band_support); i++) { + if (fl1h->hw_info.band_support & (1 << i)) + vty_out(vty, "%s ", gsm_band_name(1 << i)); + } + vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, "Min Tx Power: %d dBm%s", fl1h->phy_inst->u.lc15.minTxPower, VTY_NEWLINE); + vty_out(vty, "Max Tx Power: %d dBm%s", fl1h->phy_inst->u.lc15.maxTxPower, VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(activate_lchan, activate_lchan_cmd, + "trx <0-0> <0-7> (activate|deactivate) <0-7>", + TRX_STR + "Timeslot number\n" + "Activate Logical Channel\n" + "Deactivate Logical Channel\n" + "Logical Channel Number\n" ) +{ + int trx_nr = atoi(argv[0]); + int ts_nr = atoi(argv[1]); + int lchan_nr = atoi(argv[3]); + struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; + struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; + + if (!strcmp(argv[2], "activate")) + lchan_activate(lchan); + else + lchan_deactivate(lchan); + + return CMD_SUCCESS; +} + +DEFUN(set_tx_power, set_tx_power_cmd, + "trx nr <0-1> tx-power <-110-100>", + TRX_STR + "TRX number \n" + "Set transmit power (override BSC)\n" + "Transmit power in dBm\n") +{ + int trx_nr = atoi(argv[0]); + int power = atoi(argv[1]); + struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + + power_ramp_start(trx, to_mdB(power), 1); + + return CMD_SUCCESS; +} + +DEFUN(loopback, loopback_cmd, + "trx <0-0> <0-7> loopback <0-1>", + TRX_STR + "Timeslot number\n" + "Set TCH loopback\n" + "Logical Channel Number\n") +{ + int trx_nr = atoi(argv[0]); + int ts_nr = atoi(argv[1]); + int lchan_nr = atoi(argv[2]); + struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; + struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; + + lchan->loopback = 1; + + return CMD_SUCCESS; +} + +DEFUN(no_loopback, no_loopback_cmd, + "no trx <0-0> <0-7> loopback <0-1>", + NO_STR TRX_STR + "Timeslot number\n" + "Set TCH loopback\n" + "Logical Channel Number\n") +{ + int trx_nr = atoi(argv[0]); + int ts_nr = atoi(argv[1]); + int lchan_nr = atoi(argv[2]); + struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; + struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; + + lchan->loopback = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_nominal_power, cfg_trx_nominal_power_cmd, + "nominal-tx-power <0-40>", + "Set the nominal transmit output power in dBm\n" + "Nominal transmit output power level in dBm\n") +{ + int nominal_power = atoi(argv[0]); + struct gsm_bts_trx *trx = vty->index; + + if (( nominal_power > 40 ) || ( nominal_power < 0 )) { + vty_out(vty, "Nominal Tx power level must be between 0 and 40 dBm (%d) %s", + nominal_power, VTY_NEWLINE); + return CMD_WARNING; + } + + trx->nominal_power = nominal_power; + trx->power_params.trx_p_max_out_mdBm = to_mdB(nominal_power); + + return CMD_SUCCESS; +} + +void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts) +{ +} + +void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx) +{ + vty_out(vty, " nominal-tx-power %d%s", trx->nominal_power,VTY_NEWLINE); +} + +void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink) +{ +} + +void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst) +{ + int i; + + for (i = 0; i < 32; i++) { + if (pinst->u.lc15.dsp_trace_f & (1 << i)) { + const char *name; + name = get_value_string(lc15bts_tracef_names, (1 << i)); + vty_out(vty, " dsp-trace-flag %s%s", name, + VTY_NEWLINE); + } + } + if (pinst->u.lc15.calib_path) + vty_out(vty, " trx-calibration-path %s%s", + pinst->u.lc15.calib_path, VTY_NEWLINE); +} + +int bts_model_vty_init(struct gsm_bts *bts) +{ + vty_bts = bts; + + /* runtime-patch the command strings with debug levels */ + dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, lc15bts_tracef_names, + "phy <0-0> dsp-trace-flag (", + "|",")", VTY_DO_LOWER); + dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, lc15bts_tracef_docs, + TRX_STR DSP_TRACE_F_STR, + "\n", "", 0); + + no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, lc15bts_tracef_names, + "no phy <0-0> dsp-trace-flag (", + "|",")", VTY_DO_LOWER); + no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, lc15bts_tracef_docs, + NO_STR TRX_STR DSP_TRACE_F_STR, + "\n", "", 0); + + cfg_phy_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, + lc15bts_tracef_names, + "dsp-trace-flag (", + "|",")", VTY_DO_LOWER); + cfg_phy_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, + lc15bts_tracef_docs, + DSP_TRACE_F_STR, + "\n", "", 0); + + cfg_phy_no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, + lc15bts_tracef_names, + "no dsp-trace-flag (", + "|",")", VTY_DO_LOWER); + cfg_phy_no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, + lc15bts_tracef_docs, + NO_STR DSP_TRACE_F_STR, + "\n", "", 0); + + install_element_ve(&show_dsp_trace_f_cmd); + install_element_ve(&show_sys_info_cmd); + install_element_ve(&dsp_trace_f_cmd); + install_element_ve(&no_dsp_trace_f_cmd); + + install_element(ENABLE_NODE, &activate_lchan_cmd); + install_element(ENABLE_NODE, &set_tx_power_cmd); + + install_element(ENABLE_NODE, &loopback_cmd); + install_element(ENABLE_NODE, &no_loopback_cmd); + + install_element(BTS_NODE, &cfg_bts_auto_band_cmd); + install_element(BTS_NODE, &cfg_bts_no_auto_band_cmd); + + install_element(TRX_NODE, &cfg_trx_nominal_power_cmd); + + install_element(PHY_INST_NODE, &cfg_phy_dsp_trace_f_cmd); + install_element(PHY_INST_NODE, &cfg_phy_no_dsp_trace_f_cmd); + install_element(PHY_INST_NODE, &cfg_phy_cal_path_cmd); + + return 0; +} + +int bts_model_ctrl_cmds_install(struct gsm_bts *bts) +{ + return 0; +} diff --git a/src/osmo-bts-litecell15/main.c b/src/osmo-bts-litecell15/main.c new file mode 100644 index 0000000..030c3ef --- /dev/null +++ b/src/osmo-bts-litecell15/main.c @@ -0,0 +1,222 @@ +/* Main program for NuRAN Wireless Litecell 1.5 BTS */ + +/* Copyright (C) 2015 by Yves Godin + * Copyright (C) 2016 by Harald Welte + * + * Based on sysmoBTS: + * (C) 2011-2013 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/*NTQD: Change how rx_nr is handle in multi-trx*/ +#define LC15BTS_RF_LOCK_PATH "/var/lock/bts_rf_lock" + +#include "utils.h" +#include "l1_if.h" +#include "hw_misc.h" +#include "oml_router.h" +#include "misc/lc15bts_bid.h" + +unsigned int dsp_trace = 0x00000000; + +int bts_model_init(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + struct stat st; + static struct osmo_fd accept_fd, read_fd; + int rc; + + bts->variant = BTS_OSMO_LITECELL15; + bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3); + + rc = oml_router_init(bts, OML_ROUTER_PATH, &accept_fd, &read_fd); + if (rc < 0) { + fprintf(stderr, "Error creating the OML router: %s rc=%d\n", + OML_ROUTER_PATH, rc); + exit(1); + } + + llist_for_each_entry(trx, &bts->trx_list, list) { + trx->nominal_power = 40; + trx->power_params.trx_p_max_out_mdBm = to_mdB(bts->c0->nominal_power); + } + + if (stat(LC15BTS_RF_LOCK_PATH, &st) == 0) { + LOGP(DL1C, LOGL_NOTICE, "Not starting BTS due to RF_LOCK file present\n"); + exit(23); + } + + gsm_bts_set_feature(bts, BTS_FEAT_GPRS); + gsm_bts_set_feature(bts, BTS_FEAT_EGPRS); + gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS); + gsm_bts_set_feature(bts, BTS_FEAT_AGCH_PCH_PROP); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_EFR); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_AMR); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_AMR); + + bts_model_vty_init(bts); + + return 0; +} + +void bts_model_phy_link_set_defaults(struct phy_link *plink) +{ +} + +void bts_model_phy_instance_set_defaults(struct phy_instance *pinst) +{ +} + +int bts_model_oml_estab(struct gsm_bts *bts) +{ + return 0; +} + +void bts_update_status(enum bts_global_status which, int on) +{ + static uint64_t states = 0; + uint64_t old_states = states; + int led_rf_active_on; + + if (on) + states |= (1ULL << which); + else + states &= ~(1ULL << which); + + led_rf_active_on = + (states & (1ULL << BTS_STATUS_RF_ACTIVE)) && + !(states & (1ULL << BTS_STATUS_RF_MUTE)); + + LOGP(DL1C, LOGL_INFO, + "Set global status #%d to %d (%04llx -> %04llx), LEDs: ACT %d\n", + which, on, + (long long)old_states, (long long)states, + led_rf_active_on); + + lc15bts_led_set(led_rf_active_on ? LED_GREEN : LED_OFF); +} + +void bts_model_print_help() +{ + printf( " -w --hw-version Print the targeted HW Version\n" + " -M --pcu-direct Force PCU to access message queue for PDCH dchannel directly\n" + " -p --dsp-trace Set DSP trace flags\n" + ); +} + +static void print_hwversion() +{ + int rev; + int model; + static char model_name[64] = {0, }; + + snprintf(model_name, sizeof(model_name), "NuRAN Litecell 1.5 BTS"); + + rev = lc15bts_rev_get(); + if (rev >= 0) { + snprintf(model_name, sizeof(model_name), "%s Rev %c", + model_name, (char)rev); + } + + model = lc15bts_model_get(); + if (model >= 0) { + snprintf(model_name, sizeof(model_name), "%s (%05X)", + model_name, model); + } + + printf(model_name); +} + +int bts_model_handle_options(int argc, char **argv) +{ + int num_errors = 0; + + while (1) { + int option_idx = 0, c; + static const struct option long_options[] = { + { "dsp-trace", 1, 0, 'p' }, + { "hw-version", 0, 0, 'w' }, + { "pcu-direct", 0, 0, 'M' }, + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "p:wM", + long_options, &option_idx); + if (c == -1) + break; + + switch (c) { + case 'p': + dsp_trace = strtoul(optarg, NULL, 16); + break; + case 'M': + pcu_direct = 1; + break; + case 'w': + print_hwversion(); + exit(0); + break; + default: + num_errors++; + break; + } + } + + return num_errors; +} + +void bts_model_abis_close(struct gsm_bts *bts) +{ + /* for now, we simply terminate the program and re-spawn */ + bts_shutdown(bts, "Abis close"); +} + +int main(int argc, char **argv) +{ + return bts_main(argc, argv); +} diff --git a/src/osmo-bts-litecell15/misc/lc15bts_bid.c b/src/osmo-bts-litecell15/misc/lc15bts_bid.c new file mode 100644 index 0000000..7f278bf --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_bid.c @@ -0,0 +1,140 @@ +/* Copyright (C) 2015 by Yves Godin + * + * 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 . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lc15bts_bid.h" + +#define BOARD_REV_SYSFS "/var/lc15/platform/revision" +#define BOARD_OPT_SYSFS "/var/lc15/platform/option" + +static const int option_type_mask[_NUM_OPTION_TYPES] = { + [LC15BTS_OPTION_OCXO] = 0x07, + [LC15BTS_OPTION_FPGA] = 0x03, + [LC15BTS_OPTION_PA] = 0x01, + [LC15BTS_OPTION_BAND] = 0x03, + [LC15BTS_OPTION_TX_ISO_BYP] = 0x01, + [LC15BTS_OPTION_RX_DUP_BYP] = 0x01, + [LC15BTS_OPTION_RX_PB_BYP] = 0x01, + [LC15BTS_OPTION_RX_DIV] = 0x01, + [LC15BTS_OPTION_RX1A] = 0x01, + [LC15BTS_OPTION_RX1B] = 0x01, + [LC15BTS_OPTION_RX2A] = 0x01, + [LC15BTS_OPTION_RX2B] = 0x01, + [LC15BTS_OPTION_DDR_32B] = 0x01, + [LC15BTS_OPTION_DDR_ECC] = 0x01, + [LC15BTS_OPTION_LOG_DET] = 0x01, + [LC15BTS_OPTION_DUAL_LOG_DET] = 0x01, +}; + +static const int option_type_shift[_NUM_OPTION_TYPES] = { + [LC15BTS_OPTION_OCXO] = 0, + [LC15BTS_OPTION_FPGA] = 3, + [LC15BTS_OPTION_PA] = 5, + [LC15BTS_OPTION_BAND] = 6, + [LC15BTS_OPTION_TX_ISO_BYP] = 8, + [LC15BTS_OPTION_RX_DUP_BYP] = 9, + [LC15BTS_OPTION_RX_PB_BYP] = 10, + [LC15BTS_OPTION_RX_DIV] = 11, + [LC15BTS_OPTION_RX1A] = 12, + [LC15BTS_OPTION_RX1B] = 13, + [LC15BTS_OPTION_RX2A] = 14, + [LC15BTS_OPTION_RX2B] = 15, + [LC15BTS_OPTION_DDR_32B] = 16, + [LC15BTS_OPTION_DDR_ECC] = 17, + [LC15BTS_OPTION_LOG_DET] = 18, + [LC15BTS_OPTION_DUAL_LOG_DET] = 19, +}; + + +static int board_rev = -1; +static int board_option = -1; + +static inline bool read_board(const char *src, const char *spec, void *dst) +{ + FILE *fp = fopen(src, "r"); + if (!fp) { + fprintf(stderr, "Failed to open %s due to '%s' error\n", src, strerror(errno)); + return false; + } + + if (fscanf(fp, spec, dst) != 1) { + fclose(fp); + fprintf(stderr, "Failed to read %s due to '%s' error\n", src, strerror(errno)); + return false; + } + fclose(fp); + return true; +} + +int lc15bts_rev_get(void) +{ + char rev; + + if (board_rev != -1) { + return board_rev; + } + + if (!read_board(BOARD_REV_SYSFS, "%c", &rev)) + return -1; + + board_rev = rev; + return board_rev; +} + +int lc15bts_model_get(void) +{ + int opt; + + if (board_option != -1) + return board_option; + + if (!read_board(BOARD_OPT_SYSFS, "%X", &opt)) + return -1; + + board_option = opt; + return board_option; +} + +int lc15bts_option_get(enum lc15bts_option_type type) +{ + int rc; + int option; + + if (type >= _NUM_OPTION_TYPES) { + return -EINVAL; + } + + if (board_option == -1) { + rc = lc15bts_model_get(); + if (rc < 0) return rc; + } + + option = (board_option >> option_type_shift[type]) + & option_type_mask[type]; + + return option; +} diff --git a/src/osmo-bts-litecell15/misc/lc15bts_bid.h b/src/osmo-bts-litecell15/misc/lc15bts_bid.h new file mode 100644 index 0000000..b320e11 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_bid.h @@ -0,0 +1,51 @@ +#ifndef _LC15BTS_BOARD_H +#define _LC15BTS_BOARD_H + +#include + +enum lc15bts_option_type { + LC15BTS_OPTION_OCXO, + LC15BTS_OPTION_FPGA, + LC15BTS_OPTION_PA, + LC15BTS_OPTION_BAND, + LC15BTS_OPTION_TX_ISO_BYP, + LC15BTS_OPTION_RX_DUP_BYP, + LC15BTS_OPTION_RX_PB_BYP, + LC15BTS_OPTION_RX_DIV, + LC15BTS_OPTION_RX1A, + LC15BTS_OPTION_RX1B, + LC15BTS_OPTION_RX2A, + LC15BTS_OPTION_RX2B, + LC15BTS_OPTION_DDR_32B, + LC15BTS_OPTION_DDR_ECC, + LC15BTS_OPTION_LOG_DET, + LC15BTS_OPTION_DUAL_LOG_DET, + _NUM_OPTION_TYPES +}; + +enum lc15bts_ocxo_type { + LC15BTS_OCXO_BILAY_NVG45AV2072, + LC15BTS_OCXO_TAITIEN_NJ26M003, + _NUM_OCXO_TYPES +}; + +enum lc15bts_fpga_type { + LC15BTS_FPGA_35T, + LC15BTS_FPGA_50T, + LC15BTS_FPGA_75T, + LC15BTS_FPGA_100T, + _NUM_FPGA_TYPES +}; + +enum lc15bts_gsm_band { + LC15BTS_BAND_850, + LC15BTS_BAND_900, + LC15BTS_BAND_1800, + LC15BTS_BAND_1900, +}; + +int lc15bts_rev_get(void); +int lc15bts_model_get(void); +int lc15bts_option_get(enum lc15bts_option_type type); + +#endif diff --git a/src/osmo-bts-litecell15/misc/lc15bts_bts.c b/src/osmo-bts-litecell15/misc/lc15bts_bts.c new file mode 100644 index 0000000..0343e93 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_bts.c @@ -0,0 +1,131 @@ +/* Copyright (C) 2016 by NuRAN Wireless + * + * 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 . + * + */ + +#include +#include +#include +#include +#include +#include +#include "lc15bts_mgr.h" +#include "lc15bts_bts.h" + +static int check_eth_status(char *dev_name) +{ + int fd, rc; + struct ifreq ifr; + + fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); + if (fd < 0) + return fd; + + memset(&ifr, 0, sizeof(ifr)); + memcpy(&ifr.ifr_name, dev_name, sizeof(ifr.ifr_name)); + rc = ioctl(fd, SIOCGIFFLAGS, &ifr); + close(fd); + + if (rc < 0) + return rc; + + if ((ifr.ifr_flags & IFF_UP) && (ifr.ifr_flags & IFF_RUNNING)) + return 0; + + return 1; +} + +void check_bts_led_pattern(uint8_t *led) +{ + FILE *fp; + char str[64] = "\0"; + int rc; + + /* check for existing of BTS state file */ + if ((fp = fopen("/var/run/osmo-bts/state", "r")) == NULL) { + led[BLINK_PATTERN_INIT] = 1; + return; + } + + /* check Ethernet interface status */ + rc = check_eth_status("eth0"); + if (rc > 0) { + LOGP(DTEMP, LOGL_DEBUG,"External link is DOWN\n"); + led[BLINK_PATTERN_EXT_LINK_MALFUNC] = 1; + fclose(fp); + return; + } + + /* check for BTS is still alive */ + if (system("pidof osmo-bts-lc15 > /dev/null")) { + LOGP(DTEMP, LOGL_DEBUG,"BTS process has stopped\n"); + led[BLINK_PATTERN_INT_PROC_MALFUNC] = 1; + fclose(fp); + return; + } + + /* check for BTS state */ + while (fgets(str, 64, fp) != NULL) { + LOGP(DTEMP, LOGL_DEBUG,"BTS state is %s\n", (strstr(str, "ABIS DOWN") != NULL) ? "DOWN" : "UP"); + if (strstr(str, "ABIS DOWN") != NULL) + led[BLINK_PATTERN_INT_PROC_MALFUNC] = 1; + } + fclose(fp); + + return; +} + +int check_sensor_led_pattern( struct lc15bts_mgr_instance *mgr, uint8_t *led) +{ + if(mgr->alarms.temp_high == 1) + led[BLINK_PATTERN_TEMP_HIGH] = 1; + + if(mgr->alarms.temp_max == 1) + led[BLINK_PATTERN_TEMP_MAX] = 1; + + if(mgr->alarms.supply_low == 1) + led[BLINK_PATTERN_SUPPLY_VOLT_LOW] = 1; + + if(mgr->alarms.supply_min == 1) + led[BLINK_PATTERN_SUPPLY_VOLT_MIN] = 1; + + if(mgr->alarms.vswr_high == 1) + led[BLINK_PATTERN_VSWR_HIGH] = 1; + + if(mgr->alarms.vswr_max == 1) + led[BLINK_PATTERN_VSWR_MAX] = 1; + + if(mgr->alarms.supply_pwr_high == 1) + led[BLINK_PATTERN_SUPPLY_PWR_HIGH] = 1; + + if(mgr->alarms.supply_pwr_max == 1) + led[BLINK_PATTERN_SUPPLY_PWR_MAX] = 1; + + if(mgr->alarms.supply_pwr_max2 == 1) + led[BLINK_PATTERN_SUPPLY_PWR_MAX2] = 1; + + if(mgr->alarms.pa_pwr_high == 1) + led[BLINK_PATTERN_PA_PWR_HIGH] = 1; + + if(mgr->alarms.pa_pwr_max == 1) + led[BLINK_PATTERN_PA_PWR_MAX] = 1; + + if(mgr->alarms.gps_fix_lost == 1) + led[BLINK_PATTERN_GPS_FIX_LOST] = 1; + + return 0; +} diff --git a/src/osmo-bts-litecell15/misc/lc15bts_bts.h b/src/osmo-bts-litecell15/misc/lc15bts_bts.h new file mode 100644 index 0000000..3918b87 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_bts.h @@ -0,0 +1,21 @@ +#ifndef _LC15BTS_BTS_H_ +#define _LC15BTS_BTS_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* public function prototypes */ +void check_bts_led_pattern(uint8_t *led); +int check_sensor_led_pattern( struct lc15bts_mgr_instance *mgr, uint8_t *led); + +#endif diff --git a/src/osmo-bts-litecell15/misc/lc15bts_clock.c b/src/osmo-bts-litecell15/misc/lc15bts_clock.c new file mode 100644 index 0000000..7170149 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_clock.c @@ -0,0 +1,260 @@ +/* Copyright (C) 2015 by Yves Godin + * + * 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 . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "lc15bts_clock.h" + +#define CLKERR_ERR_SYSFS "/var/lc15/clkerr/clkerr1_average" +#define CLKERR_ACC_SYSFS "/var/lc15/clkerr/clkerr1_average_accuracy" +#define CLKERR_INT_SYSFS "/var/lc15/clkerr/clkerr1_average_interval" +#define CLKERR_FLT_SYSFS "/var/lc15/clkerr/clkerr1_fault" +#define CLKERR_RFS_SYSFS "/var/lc15/clkerr/refresh" +#define CLKERR_RST_SYSFS "/var/lc15/clkerr/reset" + +#define OCXODAC_VAL_SYSFS "/var/lc15/ocxo/voltage" +#define OCXODAC_ROM_SYSFS "/var/lc15/ocxo/eeprom" + +/* clock error */ +static int clkerr_fd_err = -1; +static int clkerr_fd_accuracy = -1; +static int clkerr_fd_interval = -1; +static int clkerr_fd_fault = -1; +static int clkerr_fd_refresh = -1; +static int clkerr_fd_reset = -1; + +/* ocxo dac */ +static int ocxodac_fd_value = -1; +static int ocxodac_fd_save = -1; + + +static int sysfs_read_val(int fd, int *val) +{ + int rc; + char szVal[32] = {0}; + + lseek( fd, 0, SEEK_SET ); + + rc = read(fd, szVal, sizeof(szVal) - 1); + if (rc < 0) { + return -errno; + } + + rc = sscanf(szVal, "%d", val); + if (rc != 1) { + return -1; + } + + return 0; +} + +static int sysfs_write_val(int fd, int val) +{ + int n, rc; + char szVal[32] = {0}; + + n = sprintf(szVal, "%d", val); + + lseek(fd, 0, SEEK_SET); + rc = write(fd, szVal, n+1); + if (rc < 0) { + return -errno; + } + return 0; +} + +static int sysfs_write_str(int fd, const char *str) +{ + int rc; + + lseek( fd, 0, SEEK_SET ); + rc = write(fd, str, strlen(str)+1); + if (rc < 0) { + return -errno; + } + return 0; +} + + +int lc15bts_clock_err_open(void) +{ + if (clkerr_fd_err < 0) { + clkerr_fd_err = open(CLKERR_ERR_SYSFS, O_RDONLY); + if (clkerr_fd_err < 0) { + lc15bts_clock_err_close(); + return clkerr_fd_err; + } + } + + if (clkerr_fd_accuracy < 0) { + clkerr_fd_accuracy = open(CLKERR_ACC_SYSFS, O_RDONLY); + if (clkerr_fd_accuracy < 0) { + lc15bts_clock_err_close(); + return clkerr_fd_accuracy; + } + } + + if (clkerr_fd_interval < 0) { + clkerr_fd_interval = open(CLKERR_INT_SYSFS, O_RDONLY); + if (clkerr_fd_interval < 0) { + lc15bts_clock_err_close(); + return clkerr_fd_interval; + } + } + + if (clkerr_fd_fault < 0) { + clkerr_fd_fault = open(CLKERR_FLT_SYSFS, O_RDONLY); + if (clkerr_fd_fault < 0) { + lc15bts_clock_err_close(); + return clkerr_fd_fault; + } + } + + if (clkerr_fd_refresh < 0) { + clkerr_fd_refresh = open(CLKERR_RFS_SYSFS, O_WRONLY); + if (clkerr_fd_refresh < 0) { + lc15bts_clock_err_close(); + return clkerr_fd_refresh; + } + } + + if (clkerr_fd_reset < 0) { + clkerr_fd_reset = open(CLKERR_RST_SYSFS, O_WRONLY); + if (clkerr_fd_reset < 0) { + lc15bts_clock_err_close(); + return clkerr_fd_reset; + } + } + return 0; +} + +void lc15bts_clock_err_close(void) +{ + if (clkerr_fd_err >= 0) { + close(clkerr_fd_err); + clkerr_fd_err = -1; + } + + if (clkerr_fd_accuracy >= 0) { + close(clkerr_fd_accuracy); + clkerr_fd_accuracy = -1; + } + + if (clkerr_fd_interval >= 0) { + close(clkerr_fd_interval); + clkerr_fd_interval = -1; + } + + if (clkerr_fd_fault >= 0) { + close(clkerr_fd_fault); + clkerr_fd_fault = -1; + } + + if (clkerr_fd_refresh >= 0) { + close(clkerr_fd_refresh); + clkerr_fd_refresh = -1; + } + + if (clkerr_fd_reset >= 0) { + close(clkerr_fd_reset); + clkerr_fd_reset = -1; + } +} + +int lc15bts_clock_err_reset(void) +{ + return sysfs_write_val(clkerr_fd_reset, 1); +} + +int lc15bts_clock_err_get(int *fault, int *error_ppt, + int *accuracy_ppq, int *interval_sec) +{ + int rc; + + rc = sysfs_write_str(clkerr_fd_refresh, "once"); + if (rc < 0) { + return -1; + } + + rc = sysfs_read_val(clkerr_fd_fault, fault); + rc |= sysfs_read_val(clkerr_fd_err, error_ppt); + rc |= sysfs_read_val(clkerr_fd_accuracy, accuracy_ppq); + rc |= sysfs_read_val(clkerr_fd_interval, interval_sec); + if (rc) { + return -1; + } + return 0; +} + + +int lc15bts_clock_dac_open(void) +{ + if (ocxodac_fd_value < 0) { + ocxodac_fd_value = open(OCXODAC_VAL_SYSFS, O_RDWR); + if (ocxodac_fd_value < 0) { + lc15bts_clock_dac_close(); + return ocxodac_fd_value; + } + } + + if (ocxodac_fd_save < 0) { + ocxodac_fd_save = open(OCXODAC_ROM_SYSFS, O_WRONLY); + if (ocxodac_fd_save < 0) { + lc15bts_clock_dac_close(); + return ocxodac_fd_save; + } + } + return 0; +} + +void lc15bts_clock_dac_close(void) +{ + if (ocxodac_fd_value >= 0) { + close(ocxodac_fd_value); + ocxodac_fd_value = -1; + } + + if (ocxodac_fd_save >= 0) { + close(ocxodac_fd_save); + ocxodac_fd_save = -1; + } +} + +int lc15bts_clock_dac_get(int *dac_value) +{ + return sysfs_read_val(ocxodac_fd_value, dac_value); +} + +int lc15bts_clock_dac_set(int dac_value) +{ + return sysfs_write_val(ocxodac_fd_value, dac_value); +} + +int lc15bts_clock_dac_save(void) +{ + return sysfs_write_val(ocxodac_fd_save, 1); +} + + diff --git a/src/osmo-bts-litecell15/misc/lc15bts_clock.h b/src/osmo-bts-litecell15/misc/lc15bts_clock.h new file mode 100644 index 0000000..d967359 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_clock.h @@ -0,0 +1,16 @@ +#ifndef _LC15BTS_CLOCK_H +#define _LC15BTS_CLOCK_H + +int lc15bts_clock_err_open(void); +void lc15bts_clock_err_close(void); +int lc15bts_clock_err_reset(void); +int lc15bts_clock_err_get(int *fault, int *error_ppt, + int *accuracy_ppq, int *interval_sec); + +int lc15bts_clock_dac_open(void); +void lc15bts_clock_dac_close(void); +int lc15bts_clock_dac_get(int *dac_value); +int lc15bts_clock_dac_set(int dac_value); +int lc15bts_clock_dac_save(void); + +#endif diff --git a/src/osmo-bts-litecell15/misc/lc15bts_led.c b/src/osmo-bts-litecell15/misc/lc15bts_led.c new file mode 100644 index 0000000..603e0fb --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_led.c @@ -0,0 +1,333 @@ +/* Copyright (C) 2016 by NuRAN Wireless + * + * 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 . + * + */ +#include "lc15bts_led.h" +#include "lc15bts_bts.h" +#include +#include + +static struct lc15bts_led led_entries[] = { + { + .name = "led0", + .fullname = "led red", + .path = "/var/lc15/leds/led0/brightness" + }, + { + .name = "led1", + .fullname = "led green", + .path = "/var/lc15/leds/led1/brightness" + } +}; + +static const struct value_string lc15bts_led_strs[] = { + { LC15BTS_LED_RED, "LED red" }, + { LC15BTS_LED_GREEN, "LED green" }, + { LC15BTS_LED_ORANGE, "LED orange" }, + { LC15BTS_LED_OFF, "LED off" }, + { 0, NULL } +}; + +static uint8_t led_priority[] = { + BLINK_PATTERN_INIT, + BLINK_PATTERN_INT_PROC_MALFUNC, + BLINK_PATTERN_SUPPLY_PWR_MAX, + BLINK_PATTERN_PA_PWR_MAX, + BLINK_PATTERN_VSWR_MAX, + BLINK_PATTERN_SUPPLY_VOLT_MIN, + BLINK_PATTERN_TEMP_MAX, + BLINK_PATTERN_SUPPLY_PWR_MAX2, + BLINK_PATTERN_EXT_LINK_MALFUNC, + BLINK_PATTERN_SUPPLY_VOLT_LOW, + BLINK_PATTERN_TEMP_HIGH, + BLINK_PATTERN_VSWR_HIGH, + BLINK_PATTERN_SUPPLY_PWR_HIGH, + BLINK_PATTERN_PA_PWR_HIGH, + BLINK_PATTERN_GPS_FIX_LOST, + BLINK_PATTERN_NORMAL +}; + + +char *blink_pattern_command[] = BLINK_PATTERN_COMMAND; + +static int lc15bts_led_write(char *path, char *str) +{ + int fd; + + if ((fd = open(path, O_WRONLY)) == -1) + { + return 0; + } + + write(fd, str, strlen(str)+1); + close(fd); + return 1; +} + +static void led_set_red() +{ + lc15bts_led_write(led_entries[0].path, "1"); + lc15bts_led_write(led_entries[1].path, "0"); +} + +static void led_set_green() +{ + lc15bts_led_write(led_entries[0].path, "0"); + lc15bts_led_write(led_entries[1].path, "1"); +} + +static void led_set_orange() +{ + lc15bts_led_write(led_entries[0].path, "1"); + lc15bts_led_write(led_entries[1].path, "1"); +} + +static void led_set_off() +{ + lc15bts_led_write(led_entries[0].path, "0"); + lc15bts_led_write(led_entries[1].path, "0"); +} + +static void led_sleep( struct lc15bts_mgr_instance *mgr, struct lc15bts_led_timer *led_timer, void (*led_timer_cb)(void *_data)) { + /* Cancel any pending timer */ + osmo_timer_del(&led_timer->timer); + /* Start LED timer */ + led_timer->timer.cb = led_timer_cb; + led_timer->timer.data = mgr; + mgr->lc15bts_leds.active_timer = led_timer->idx; + osmo_timer_schedule(&led_timer->timer, led_timer->param.sleep_sec, led_timer->param.sleep_usec); + LOGP(DTEMP, LOGL_DEBUG,"%s timer scheduled for %d sec + %d usec\n", + get_value_string(lc15bts_led_strs, led_timer->idx), + led_timer->param.sleep_sec, + led_timer->param.sleep_usec); + + switch (led_timer->idx) { + case LC15BTS_LED_RED: + led_set_red(); + break; + case LC15BTS_LED_GREEN: + led_set_green(); + break; + case LC15BTS_LED_ORANGE: + led_set_orange(); + break; + case LC15BTS_LED_OFF: + led_set_off(); + break; + default: + led_set_off(); + } +} + +static void led_sleep_cb(void *_data) { + struct lc15bts_mgr_instance *mgr = _data; + struct lc15bts_led_timer_list *led_list; + + /* make sure the timer list is not empty */ + if (llist_empty(&mgr->lc15bts_leds.list)) + return; + + llist_for_each_entry(led_list, &mgr->lc15bts_leds.list, list) { + if (led_list->led_timer.idx == mgr->lc15bts_leds.active_timer) { + LOGP(DTEMP, LOGL_DEBUG,"Delete expired %s timer %d sec + %d usec\n", + get_value_string(lc15bts_led_strs, led_list->led_timer.idx), + led_list->led_timer.param.sleep_sec, + led_list->led_timer.param.sleep_usec); + + /* Delete current timer */ + osmo_timer_del(&led_list->led_timer.timer); + /* Rotate the timer list */ + llist_move_tail(led_list, &mgr->lc15bts_leds.list); + break; + } + } + + /* Execute next timer */ + led_list = llist_first_entry(&mgr->lc15bts_leds.list, struct lc15bts_led_timer_list, list); + if (led_list) { + LOGP(DTEMP, LOGL_DEBUG,"Execute %s timer %d sec + %d usec, total entries=%d\n", + get_value_string(lc15bts_led_strs, led_list->led_timer.idx), + led_list->led_timer.param.sleep_sec, + led_list->led_timer.param.sleep_usec, + llist_count(&mgr->lc15bts_leds.list)); + + led_sleep(mgr, &led_list->led_timer, led_sleep_cb); + } + +} + +static void delete_led_timer_entries(struct lc15bts_mgr_instance *mgr) +{ + struct lc15bts_led_timer_list *led_list, *led_list2; + + if (llist_empty(&mgr->lc15bts_leds.list)) + return; + + llist_for_each_entry_safe(led_list, led_list2, &mgr->lc15bts_leds.list, list) { + /* Delete the timer in list */ + if (led_list) { + LOGP(DTEMP, LOGL_DEBUG,"Delete %s timer entry from list, %d sec + %d usec\n", + get_value_string(lc15bts_led_strs, led_list->led_timer.idx), + led_list->led_timer.param.sleep_sec, + led_list->led_timer.param.sleep_usec); + + /* Delete current timer */ + osmo_timer_del(&led_list->led_timer.timer); + llist_del(&led_list->list); + talloc_free(led_list); + } + } + return; +} + +static int add_led_timer_entry(struct lc15bts_mgr_instance *mgr, char *cmdstr) +{ + double sec, int_sec, frac_sec; + struct lc15bts_sleep_time led_param; + + led_param.sleep_sec = 0; + led_param.sleep_usec = 0; + + if (strstr(cmdstr, "set red") != NULL) + mgr->lc15bts_leds.led_idx = LC15BTS_LED_RED; + else if (strstr(cmdstr, "set green") != NULL) + mgr->lc15bts_leds.led_idx = LC15BTS_LED_GREEN; + else if (strstr(cmdstr, "set orange") != NULL) + mgr->lc15bts_leds.led_idx = LC15BTS_LED_ORANGE; + else if (strstr(cmdstr, "set off") != NULL) + mgr->lc15bts_leds.led_idx = LC15BTS_LED_OFF; + else if (strstr(cmdstr, "sleep") != NULL) { + sec = atof(cmdstr + 6); + /* split time into integer and fractional of seconds */ + frac_sec = modf(sec, &int_sec) * 1000000.0; + led_param.sleep_sec = (int)int_sec; + led_param.sleep_usec = (int)frac_sec; + + if ((mgr->lc15bts_leds.led_idx >= LC15BTS_LED_RED) && (mgr->lc15bts_leds.led_idx < _LC15BTS_LED_MAX)) { + struct lc15bts_led_timer_list *led_list; + + /* allocate timer entry */ + led_list = talloc_zero(tall_mgr_ctx, struct lc15bts_led_timer_list); + if (led_list) { + led_list->led_timer.idx = mgr->lc15bts_leds.led_idx; + led_list->led_timer.param.sleep_sec = led_param.sleep_sec; + led_list->led_timer.param.sleep_usec = led_param.sleep_usec; + llist_add_tail(&led_list->list, &mgr->lc15bts_leds.list); + + LOGP(DTEMP, LOGL_DEBUG,"Add %s timer to list, %d sec + %d usec, total entries=%d\n", + get_value_string(lc15bts_led_strs, mgr->lc15bts_leds.led_idx), + led_list->led_timer.param.sleep_sec, + led_list->led_timer.param.sleep_usec, + llist_count(&mgr->lc15bts_leds.list)); + } + } + } else + return -1; + + return 0; +} + +static int parse_led_pattern(char *pattern, struct lc15bts_mgr_instance *mgr) +{ + char str[1024]; + char *pstr; + char *sep; + int rc = 0; + + strcpy(str, pattern); + pstr = str; + while ((sep = strsep(&pstr, ";")) != NULL) { + rc = add_led_timer_entry(mgr, sep); + if (rc < 0) { + break; + } + + } + return rc; +} + +/*** led interface ***/ + +void led_set(struct lc15bts_mgr_instance *mgr, int pattern_id) +{ + int rc; + struct lc15bts_led_timer_list *led_list; + + if (pattern_id > BLINK_PATTERN_MAX_ITEM - 1) { + LOGP(DTEMP, LOGL_ERROR, "Invalid LED pattern : %d. LED pattern must be between %d..%d\n", + pattern_id, + BLINK_PATTERN_POWER_ON, + BLINK_PATTERN_MAX_ITEM - 1); + return; + } + if (pattern_id == mgr->lc15bts_leds.last_pattern_id) + return; + + mgr->lc15bts_leds.last_pattern_id = pattern_id; + + LOGP(DTEMP, LOGL_NOTICE, "blink pattern command : %d\n", pattern_id); + LOGP(DTEMP, LOGL_NOTICE, "%s\n", blink_pattern_command[pattern_id]); + + /* Empty existing LED timer in the list */ + delete_led_timer_entries(mgr); + + /* parse LED pattern */ + rc = parse_led_pattern(blink_pattern_command[pattern_id], mgr); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR,"LED pattern not found or invalid LED pattern\n"); + return; + } + + /* make sure the timer list is not empty */ + if (llist_empty(&mgr->lc15bts_leds.list)) + return; + + /* Start the first LED timer in the list */ + led_list = llist_first_entry(&mgr->lc15bts_leds.list, struct lc15bts_led_timer_list, list); + if (led_list) { + LOGP(DTEMP, LOGL_DEBUG,"Execute timer %s for %d sec + %d usec\n", + get_value_string(lc15bts_led_strs, led_list->led_timer.idx), + led_list->led_timer.param.sleep_sec, + led_list->led_timer.param.sleep_usec); + + led_sleep(mgr, &led_list->led_timer, led_sleep_cb); + } + +} + +void select_led_pattern(struct lc15bts_mgr_instance *mgr) +{ + int i; + uint8_t led[BLINK_PATTERN_MAX_ITEM] = {0}; + + /* set normal LED pattern at first */ + led[BLINK_PATTERN_NORMAL] = 1; + + /* check on-board sensors for new LED pattern */ + check_sensor_led_pattern(mgr, led); + + /* check BTS status for new LED pattern */ + check_bts_led_pattern(led); + + /* check by priority */ + for (i = 0; i < sizeof(led_priority)/sizeof(uint8_t); i++) { + if(led[led_priority[i]] == 1) { + led_set(mgr, led_priority[i]); + break; + } + } +} diff --git a/src/osmo-bts-litecell15/misc/lc15bts_led.h b/src/osmo-bts-litecell15/misc/lc15bts_led.h new file mode 100644 index 0000000..b6d9d28 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_led.h @@ -0,0 +1,22 @@ +#ifndef _LC15BTS_LED_H +#define _LC15BTS_LED_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "lc15bts_mgr.h" + +/* public function prototypes */ +void led_set(struct lc15bts_mgr_instance *mgr, int pattern_id); + +void select_led_pattern(struct lc15bts_mgr_instance *mgr); + +#endif diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr.c b/src/osmo-bts-litecell15/misc/lc15bts_mgr.c new file mode 100644 index 0000000..dbdcc9f --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_mgr.c @@ -0,0 +1,366 @@ +/* Main program for NuRAN Wireless Litecell 1.5 BTS management daemon */ + +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * sysmobts_mgr.c + * (C) 2012 by Harald Welte + * (C) 2014 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "misc/lc15bts_misc.h" +#include "misc/lc15bts_mgr.h" +#include "misc/lc15bts_par.h" +#include "misc/lc15bts_bid.h" +#include "misc/lc15bts_power.h" +#include "misc/lc15bts_swd.h" + +#include "lc15bts_led.h" + +static int no_rom_write = 0; +static int daemonize = 0; +void *tall_mgr_ctx; + +/* every 6 hours means 365*4 = 1460 rom writes per year (max) */ +#define SENSOR_TIMER_SECS (6 * 3600) + +/* every 1 hours means 365*24 = 8760 rom writes per year (max) */ +#define HOURS_TIMER_SECS (1 * 3600) + + +/* the initial state */ +static struct lc15bts_mgr_instance manager = { + .config_file = "lc15bts-mgr.cfg", + .temp = { + .supply_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -40, + }, + .soc_temp_limit = { + .thresh_warn_max = 95, + .thresh_crit_max = 100, + .thresh_warn_min = -40, + }, + .fpga_temp_limit = { + .thresh_warn_max = 95, + .thresh_crit_max = 100, + .thresh_warn_min = -40, + }, + .rmsdet_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -40, + }, + .ocxo_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -40, + }, + .tx0_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -20, + }, + .tx1_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -20, + }, + .pa0_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -40, + }, + .pa1_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -40, + } + }, + .volt = { + .supply_volt_limit = { + .thresh_warn_max = 30000, + .thresh_crit_max = 30500, + .thresh_warn_min = 19000, + .thresh_crit_min = 17500, + } + }, + .pwr = { + .supply_pwr_limit = { + .thresh_warn_max = 110, + .thresh_crit_max = 120, + }, + .pa0_pwr_limit = { + .thresh_warn_max = 50, + .thresh_crit_max = 60, + }, + .pa1_pwr_limit = { + .thresh_warn_max = 50, + .thresh_crit_max = 60, + } + }, + .vswr = { + .tx0_vswr_limit = { + .thresh_warn_max = 3000, + .thresh_crit_max = 5000, + }, + .tx1_vswr_limit = { + .thresh_warn_max = 3000, + .thresh_crit_max = 5000, + } + }, + .gps = { + .gps_fix_limit = { + .thresh_warn_max = 7, + } + }, + .state = { + .action_norm = SENSOR_ACT_NORM_PA0_ON | SENSOR_ACT_NORM_PA1_ON, + .action_warn = 0, + .action_crit = 0, + .action_comb = 0, + .state = STATE_NORMAL, + } +}; + +static struct osmo_timer_list sensor_timer; +static void check_sensor_timer_cb(void *unused) +{ + lc15bts_check_temp(no_rom_write); + lc15bts_check_power(no_rom_write); + lc15bts_check_vswr(no_rom_write); + osmo_timer_schedule(&sensor_timer, SENSOR_TIMER_SECS, 0); + /* TODO checks if lc15bts_check_temp/lc15bts_check_power/lc15bts_check_vswr went ok */ + lc15bts_swd_event(&manager, SWD_CHECK_SENSOR); +} + +static struct osmo_timer_list hours_timer; +static void hours_timer_cb(void *unused) +{ + lc15bts_update_hours(no_rom_write); + + osmo_timer_schedule(&hours_timer, HOURS_TIMER_SECS, 0); + /* TODO: validates if lc15bts_update_hours went correctly */ + lc15bts_swd_event(&manager, SWD_UPDATE_HOURS); +} + +static void print_help(void) +{ + printf("lc15bts-mgr [-nsD] [-d cat]\n"); + printf(" -n Do not write to ROM\n"); + printf(" -s Disable color\n"); + printf(" -d CAT enable debugging\n"); + printf(" -D daemonize\n"); + printf(" -c Specify the filename of the config file\n"); +} + +static int parse_options(int argc, char **argv) +{ + int opt; + + while ((opt = getopt(argc, argv, "nhsd:c:")) != -1) { + switch (opt) { + case 'n': + no_rom_write = 1; + break; + case 'h': + print_help(); + return -1; + 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': + manager.config_file = optarg; + break; + default: + return -1; + } + } + + return 0; +} + +static void signal_handler(int signal) +{ + fprintf(stderr, "signal %u received\n", signal); + + switch (signal) { + case SIGINT: + case SIGTERM: + lc15bts_check_temp(no_rom_write); + lc15bts_check_power(no_rom_write); + lc15bts_check_vswr(no_rom_write); + lc15bts_update_hours(no_rom_write); + exit(0); + break; + case SIGABRT: + case SIGUSR1: + case SIGUSR2: + talloc_report_full(tall_mgr_ctx, stderr); + break; + default: + break; + } +} + +static struct log_info_cat mgr_log_info_cat[] = { + [DTEMP] = { + .name = "DTEMP", + .description = "Temperature monitoring", + .color = "\033[1;35m", + .enabled = 1, .loglevel = LOGL_INFO, + }, + [DFW] = { + .name = "DFW", + .description = "Firmware management", + .color = "\033[1;36m", + .enabled = 1, .loglevel = LOGL_INFO, + }, + [DFIND] = { + .name = "DFIND", + .description = "ipaccess-find handling", + .color = "\033[1;37m", + .enabled = 1, .loglevel = LOGL_INFO, + }, + [DCALIB] = { + .name = "DCALIB", + .description = "Calibration handling", + .color = "\033[1;37m", + .enabled = 1, .loglevel = LOGL_INFO, + }, + [DSWD] = { + .name = "DSWD", + .description = "Software Watchdog", + .color = "\033[1;37m", + .enabled = 1, .loglevel = LOGL_INFO, + }, +}; + +static const struct log_info mgr_log_info = { + .cat = mgr_log_info_cat, + .num_cat = ARRAY_SIZE(mgr_log_info_cat), +}; + +int main(int argc, char **argv) +{ + int rc; + + tall_mgr_ctx = talloc_named_const(NULL, 1, "bts manager"); + msgb_talloc_ctx_init(tall_mgr_ctx, 0); + + osmo_init_logging2(tall_mgr_ctx, &mgr_log_info); + + osmo_init_ignore_signals(); + signal(SIGINT, &signal_handler); + signal(SIGTERM, &signal_handler); + signal(SIGUSR1, &signal_handler); + signal(SIGUSR2, &signal_handler); + + rc = parse_options(argc, argv); + if (rc < 0) + exit(2); + + lc15bts_mgr_vty_init(); + logging_vty_add_cmds(&mgr_log_info); + rc = lc15bts_mgr_parse_config(&manager); + if (rc < 0) { + LOGP(DFIND, LOGL_FATAL, "Cannot parse config file\n"); + exit(1); + } + + rc = telnet_init(tall_mgr_ctx, NULL, OSMO_VTY_PORT_BTSMGR); + if (rc < 0) { + fprintf(stderr, "Error initializing telnet\n"); + exit(1); + } + + INIT_LLIST_HEAD(&manager.lc15bts_leds.list); + INIT_LLIST_HEAD(&manager.alarms.list); + + /* Initialize the service watchdog notification for SWD_LAST event(s) */ + if (lc15bts_swd_init(&manager, (int)(SWD_LAST)) != 0) + exit(3); + + /* start temperature check timer */ + sensor_timer.cb = check_sensor_timer_cb; + check_sensor_timer_cb(NULL); + + /* start operational hours timer */ + hours_timer.cb = hours_timer_cb; + hours_timer_cb(NULL); + + /* Enable the PAs */ + rc = lc15bts_power_set(LC15BTS_POWER_PA0, 1); + if (rc < 0) { + exit(3); + } + + rc = lc15bts_power_set(LC15BTS_POWER_PA1, 1); + if (rc < 0) { + exit(3); + } + + /* handle broadcast messages for ipaccess-find */ + if (lc15bts_mgr_nl_init() != 0) + exit(3); + + /* Initialize the sensor control */ + lc15bts_mgr_sensor_init(&manager); + + if (lc15bts_mgr_calib_init(&manager) != 0) + exit(3); + + if (daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + exit(1); + } + } + + while (1) { + log_reset_context(); + osmo_select_main(0); + lc15bts_swd_event(&manager, SWD_MAINLOOP); + } +} diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr.h b/src/osmo-bts-litecell15/misc/lc15bts_mgr.h new file mode 100644 index 0000000..4bfbdbc --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_mgr.h @@ -0,0 +1,422 @@ +#ifndef _LC15BTS_MGR_H +#define _LC15BTS_MGR_H + +#include +#include + +#include +#include + +#include + +#define LC15BTS_SENSOR_TIMER_DURATION 60 +#define LC15BTS_PREVENT_TIMER_DURATION 15 * 60 +#define LC15BTS_PREVENT_TIMER_SHORT_DURATION 5 * 60 +#define LC15BTS_PREVENT_TIMER_NONE 0 +#define LC15BTS_PREVENT_RETRY INT_MAX - 1 + +enum BLINK_PATTERN { + BLINK_PATTERN_POWER_ON = 0, //hardware set + BLINK_PATTERN_INIT, + BLINK_PATTERN_NORMAL, + BLINK_PATTERN_EXT_LINK_MALFUNC, + BLINK_PATTERN_INT_PROC_MALFUNC, + BLINK_PATTERN_SUPPLY_VOLT_LOW, + BLINK_PATTERN_SUPPLY_VOLT_MIN, + BLINK_PATTERN_VSWR_HIGH, + BLINK_PATTERN_VSWR_MAX, + BLINK_PATTERN_TEMP_HIGH, + BLINK_PATTERN_TEMP_MAX, + BLINK_PATTERN_SUPPLY_PWR_HIGH, + BLINK_PATTERN_SUPPLY_PWR_MAX, + BLINK_PATTERN_SUPPLY_PWR_MAX2, + BLINK_PATTERN_PA_PWR_HIGH, + BLINK_PATTERN_PA_PWR_MAX, + BLINK_PATTERN_GPS_FIX_LOST, + BLINK_PATTERN_MAX_ITEM +}; + +#define BLINK_PATTERN_COMMAND {\ + "set red; sleep 5.0",\ + "set orange; sleep 5.0",\ + "set green; sleep 2.5; set off; sleep 2.5",\ + "set red; sleep 0.5; set off; sleep 0.5",\ + "set red; sleep 2.5; set off; sleep 2.5",\ + "set green; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5",\ + "set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5 ",\ + "set green; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\ + "set red; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\ + "set orange; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5 ",\ + "set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\ + "set green; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\ + "set red; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\ + "set orange; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\ + "set green; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\ + "set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\ + "set green; sleep 2.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\ +} + +enum { + DTEMP, + DFW, + DFIND, + DCALIB, + DSWD, +}; + +// TODO NTQD: Define new actions like reducing output power, limit ARM core speed, shutdown second TRX/PA, ... +enum { +#if 0 + SENSOR_ACT_PWR_CONTRL = 0x1, +#endif + SENSOR_ACT_PA0_OFF = 0x2, + SENSOR_ACT_PA1_OFF = 0x4, + SENSOR_ACT_BTS_SRV_OFF = 0x10, +}; + +/* actions only for normal state */ +enum { +#if 0 + SENSOR_ACT_NORM_PW_CONTRL = 0x1, +#endif + SENSOR_ACT_NORM_PA0_ON = 0x2, + SENSOR_ACT_NORM_PA1_ON = 0x4, + SENSOR_ACT_NORM_BTS_SRV_ON= 0x10, +}; + +enum lc15bts_sensor_state { + STATE_NORMAL, /* Everything is fine */ + STATE_WARNING_HYST, /* Go back to normal next? */ + STATE_WARNING, /* We are above the warning threshold */ + STATE_CRITICAL, /* We have an issue. Wait for below warning */ +}; + +enum lc15bts_leds_name { + LC15BTS_LED_RED = 0, + LC15BTS_LED_GREEN, + LC15BTS_LED_ORANGE, + LC15BTS_LED_OFF, + _LC15BTS_LED_MAX +}; + +struct lc15bts_led{ + char *name; + char *fullname; + char *path; +}; + +/** + * Temperature Limits. We separate from a threshold + * that will generate a warning and one that is so + * severe that an action will be taken. + */ +struct lc15bts_temp_limit { + int thresh_warn_max; + int thresh_crit_max; + int thresh_warn_min; +}; + +struct lc15bts_volt_limit { + int thresh_warn_max; + int thresh_crit_max; + int thresh_warn_min; + int thresh_crit_min; +}; + +struct lc15bts_pwr_limit { + int thresh_warn_max; + int thresh_crit_max; +}; + +struct lc15bts_vswr_limit { + int thresh_warn_max; + int thresh_crit_max; +}; + +struct lc15bts_gps_fix_limit { + int thresh_warn_max; +}; + +struct lc15bts_sleep_time { + int sleep_sec; + int sleep_usec; +}; + +struct lc15bts_led_timer { + uint8_t idx; + struct osmo_timer_list timer; + struct lc15bts_sleep_time param; +}; + +struct lc15bts_led_timer_list { + struct llist_head list; + struct lc15bts_led_timer led_timer; +}; + +struct lc15bts_preventive_list { + struct llist_head list; + struct lc15bts_sleep_time param; + int action_flag; +}; + +enum mgr_vty_node { + MGR_NODE = _LAST_OSMOVTY_NODE + 1, + + ACT_NORM_NODE, + ACT_WARN_NODE, + ACT_CRIT_NODE, + LIMIT_SUPPLY_TEMP_NODE, + LIMIT_SOC_NODE, + LIMIT_FPGA_NODE, + LIMIT_RMSDET_NODE, + LIMIT_OCXO_NODE, + LIMIT_TX0_TEMP_NODE, + LIMIT_TX1_TEMP_NODE, + LIMIT_PA0_TEMP_NODE, + LIMIT_PA1_TEMP_NODE, + LIMIT_SUPPLY_VOLT_NODE, + LIMIT_TX0_VSWR_NODE, + LIMIT_TX1_VSWR_NODE, + LIMIT_SUPPLY_PWR_NODE, + LIMIT_PA0_PWR_NODE, + LIMIT_PA1_PWR_NODE, + LIMIT_GPS_FIX_NODE, +}; + +enum mgr_vty_limit_type { + MGR_LIMIT_TYPE_TEMP = 0, + MGR_LIMIT_TYPE_VOLT, + MGR_LIMIT_TYPE_VSWR, + MGR_LIMIT_TYPE_PWR, + _MGR_LIMIT_TYPE_MAX, +}; + +struct lc15bts_mgr_instance { + const char *config_file; + + struct { + struct lc15bts_temp_limit supply_temp_limit; + struct lc15bts_temp_limit soc_temp_limit; + struct lc15bts_temp_limit fpga_temp_limit; + struct lc15bts_temp_limit rmsdet_temp_limit; + struct lc15bts_temp_limit ocxo_temp_limit; + struct lc15bts_temp_limit tx0_temp_limit; + struct lc15bts_temp_limit tx1_temp_limit; + struct lc15bts_temp_limit pa0_temp_limit; + struct lc15bts_temp_limit pa1_temp_limit; + } temp; + + struct { + struct lc15bts_volt_limit supply_volt_limit; + } volt; + + struct { + struct lc15bts_pwr_limit supply_pwr_limit; + struct lc15bts_pwr_limit pa0_pwr_limit; + struct lc15bts_pwr_limit pa1_pwr_limit; + } pwr; + + struct { + struct lc15bts_vswr_limit tx0_vswr_limit; + struct lc15bts_vswr_limit tx1_vswr_limit; + int tx0_last_vswr; + int tx1_last_vswr; + } vswr; + + struct { + struct lc15bts_gps_fix_limit gps_fix_limit; + time_t last_update; + } gps; + + struct { + int action_norm; + int action_warn; + int action_crit; + int action_comb; + + enum lc15bts_sensor_state state; + } state; + + struct { + int state; + int calib_from_loop; + struct osmo_timer_list calib_timeout; + } calib; + + struct { + int state; + int swd_from_loop; + unsigned long long int swd_events; + unsigned long long int swd_events_cache; + unsigned long long int swd_eventmasks; + int num_events; + struct osmo_timer_list swd_timeout; + } swd; + + struct { + uint8_t led_idx; + uint8_t last_pattern_id; + uint8_t active_timer; + struct llist_head list; + } lc15bts_leds; + + struct { + int is_up; + uint32_t last_seqno; + struct osmo_timer_list recon_timer; + struct ipa_client_conn *bts_conn; + uint32_t crit_flags; + uint32_t warn_flags; + } lc15bts_ctrl; + + struct lc15bts_alarms { + int temp_high; + int temp_max; + int supply_low; + int supply_min; + int vswr_high; + int vswr_max; + int supply_pwr_high; + int supply_pwr_max; + int supply_pwr_max2; + int pa_pwr_high; + int pa_pwr_max; + int gps_fix_lost; + struct llist_head list; + struct osmo_timer_list preventive_timer; + int preventive_duration; + int preventive_retry; + } alarms; + +}; + +enum lc15bts_mgr_fail_evt_rep_crit_sig { + /* Critical alarms */ + S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM = (1 << 0), + S_MGR_TEMP_SOC_CRIT_MAX_ALARM = (1 << 1), + S_MGR_TEMP_FPGA_CRIT_MAX_ALARM = (1 << 2), + S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM = (1 << 3), + S_MGR_TEMP_OCXO_CRIT_MAX_ALARM = (1 << 4), + S_MGR_TEMP_TRX0_CRIT_MAX_ALARM = (1 << 5), + S_MGR_TEMP_TRX1_CRIT_MAX_ALARM = (1 << 6), + S_MGR_TEMP_PA0_CRIT_MAX_ALARM = (1 << 7), + S_MGR_TEMP_PA1_CRIT_MAX_ALARM = (1 << 8), + S_MGR_SUPPLY_CRIT_MAX_ALARM = (1 << 9), + S_MGR_SUPPLY_CRIT_MIN_ALARM = (1 << 10), + S_MGR_VSWR0_CRIT_MAX_ALARM = (1 << 11), + S_MGR_VSWR1_CRIT_MAX_ALARM = (1 << 12), + S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM = (1 << 13), + S_MGR_PWR_PA0_CRIT_MAX_ALARM = (1 << 14), + S_MGR_PWR_PA1_CRIT_MAX_ALARM = (1 << 15), + _S_MGR_CRIT_ALARM_MAX, +}; + +enum lc15bts_mgr_fail_evt_rep_warn_sig { + /* Warning alarms */ + S_MGR_TEMP_SUPPLY_WARN_MIN_ALARM = (1 << 0), + S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM = (1 << 2), + S_MGR_TEMP_SOC_WARN_MIN_ALARM = (1 << 3), + S_MGR_TEMP_SOC_WARN_MAX_ALARM = (1 << 4), + S_MGR_TEMP_FPGA_WARN_MIN_ALARM = (1 << 5), + S_MGR_TEMP_FPGA_WARN_MAX_ALARM = (1 << 6), + S_MGR_TEMP_RMS_DET_WARN_MIN_ALARM = (1 << 7), + S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM = (1 << 8), + S_MGR_TEMP_OCXO_WARN_MIN_ALARM = (1 << 9), + S_MGR_TEMP_OCXO_WARN_MAX_ALARM = (1 << 10), + S_MGR_TEMP_TRX0_WARN_MIN_ALARM = (1 << 11), + S_MGR_TEMP_TRX0_WARN_MAX_ALARM = (1 << 12), + S_MGR_TEMP_TRX1_WARN_MIN_ALARM = (1 << 13), + S_MGR_TEMP_TRX1_WARN_MAX_ALARM = (1 << 14), + S_MGR_TEMP_PA0_WARN_MIN_ALARM = (1 << 15), + S_MGR_TEMP_PA0_WARN_MAX_ALARM = (1 << 16), + S_MGR_TEMP_PA1_WARN_MIN_ALARM = (1 << 17), + S_MGR_TEMP_PA1_WARN_MAX_ALARM = (1 << 18), + S_MGR_SUPPLY_WARN_MIN_ALARM = (1 << 19), + S_MGR_SUPPLY_WARN_MAX_ALARM = (1 << 20), + S_MGR_VSWR0_WARN_MAX_ALARM = (1 << 21), + S_MGR_VSWR1_WARN_MAX_ALARM = (1 << 22), + S_MGR_PWR_SUPPLY_WARN_MAX_ALARM = (1 << 23), + S_MGR_PWR_PA0_WARN_MAX_ALARM = (1 << 24), + S_MGR_PWR_PA1_WARN_MAX_ALARM = (1 << 25), + S_MGR_GPS_FIX_WARN_ALARM = (1 << 26), + _S_MGR_WARN_ALARM_MAX, +}; + +enum lc15bts_mgr_failure_event_causes { + /* Critical causes */ + NM_EVT_CAUSE_CRIT_TEMP_SUPPLY_MAX_FAIL = 0x4100, + NM_EVT_CAUSE_CRIT_TEMP_FPGA_MAX_FAIL = 0x4101, + NM_EVT_CAUSE_CRIT_TEMP_SOC_MAX_FAIL = 0x4102, + NM_EVT_CAUSE_CRIT_TEMP_RMS_DET_MAX_FAIL = 0x4103, + NM_EVT_CAUSE_CRIT_TEMP_OCXO_MAX_FAIL = 0x4104, + NM_EVT_CAUSE_CRIT_TEMP_TRX0_MAX_FAIL = 0x4105, + NM_EVT_CAUSE_CRIT_TEMP_TRX1_MAX_FAIL = 0x4106, + NM_EVT_CAUSE_CRIT_TEMP_PA0_MAX_FAIL = 0x4107, + NM_EVT_CAUSE_CRIT_TEMP_PA1_MAX_FAIL = 0x4108, + NM_EVT_CAUSE_CRIT_SUPPLY_MAX_FAIL = 0x4109, + NM_EVT_CAUSE_CRIT_SUPPLY_MIN_FAIL = 0x410A, + NM_EVT_CAUSE_CRIT_VSWR0_MAX_FAIL = 0x410B, + NM_EVT_CAUSE_CRIT_VSWR1_MAX_FAIL = 0x410C, + NM_EVT_CAUSE_CRIT_PWR_SUPPLY_MAX_FAIL = 0x410D, + NM_EVT_CAUSE_CRIT_PWR_PA0_MAX_FAIL = 0x410E, + NM_EVT_CAUSE_CRIT_PWR_PA1_MAX_FAIL = 0x410F, + /* Warning causes */ + NM_EVT_CAUSE_WARN_TEMP_SUPPLY_LOW_FAIL = 0x4400, + NM_EVT_CAUSE_WARN_TEMP_SUPPLY_HIGH_FAIL = 0x4401, + NM_EVT_CAUSE_WARN_TEMP_FPGA_LOW_FAIL = 0x4402, + NM_EVT_CAUSE_WARN_TEMP_FPGA_HIGH_FAIL = 0x4403, + NM_EVT_CAUSE_WARN_TEMP_SOC_LOW_FAIL = 0x4404, + NM_EVT_CAUSE_WARN_TEMP_SOC_HIGH_FAIL = 0x4405, + NM_EVT_CAUSE_WARN_TEMP_RMS_DET_LOW_FAIL = 0x4406, + NM_EVT_CAUSE_WARN_TEMP_RMS_DET_HIGH_FAIL= 0x4407, + NM_EVT_CAUSE_WARN_TEMP_OCXO_LOW_FAIL = 0x4408, + NM_EVT_CAUSE_WARN_TEMP_OCXO_HIGH_FAIL = 0x4409, + NM_EVT_CAUSE_WARN_TEMP_TRX0_LOW_FAIL = 0x440A, + NM_EVT_CAUSE_WARN_TEMP_TRX0_HIGH_FAIL = 0x440B, + NM_EVT_CAUSE_WARN_TEMP_TRX1_LOW_FAIL = 0x440C, + NM_EVT_CAUSE_WARN_TEMP_TRX1_HIGH_FAIL = 0x440D, + NM_EVT_CAUSE_WARN_TEMP_PA0_LOW_FAIL = 0x440E, + NM_EVT_CAUSE_WARN_TEMP_PA0_HIGH_FAIL = 0x440F, + NM_EVT_CAUSE_WARN_TEMP_PA1_LOW_FAIL = 0x4410, + NM_EVT_CAUSE_WARN_TEMP_PA1_HIGH_FAIL = 0x4411, + NM_EVT_CAUSE_WARN_SUPPLY_LOW_FAIL = 0x4412, + NM_EVT_CAUSE_WARN_SUPPLY_HIGH_FAIL = 0x4413, + NM_EVT_CAUSE_WARN_VSWR0_HIGH_FAIL = 0x4414, + NM_EVT_CAUSE_WANR_VSWR1_HIGH_FAIL = 0x4415, + NM_EVT_CAUSE_WARN_PWR_SUPPLY_HIGH_FAIL = 0x4416, + NM_EVT_CAUSE_WARN_PWR_PA0_HIGH_FAIL = 0x4417, + NM_EVT_CAUSE_WARN_PWR_PA1_HIGH_FAIL = 0x4418, + NM_EVT_CAUSE_WARN_GPS_FIX_FAIL = 0x4419, +}; + +/* This defines the list of notification events for systemd service watchdog. + all these events must be notified in a certain service defined timeslot + or the service (this app) would be restarted (only if related systemd service + unit file has WatchdogSec!=0). + WARNING: swd events must begin with event 0. Last events must be + SWD_LAST (max 64 events in this list). +*/ +enum mgr_swd_events { + SWD_MAINLOOP = 0, + SWD_CHECK_SENSOR, + SWD_UPDATE_HOURS, + SWD_CHECK_TEMP_SENSOR, + SWD_CHECK_LED_CTRL, + SWD_CHECK_CALIB, + SWD_CHECK_BTS_CONNECTION, + SWD_LAST +}; + +int lc15bts_mgr_vty_init(void); +int lc15bts_mgr_parse_config(struct lc15bts_mgr_instance *mgr); +int lc15bts_mgr_nl_init(void); +int lc15bts_mgr_sensor_init(struct lc15bts_mgr_instance *mgr); +const char *lc15bts_mgr_sensor_get_state(enum lc15bts_sensor_state state); + +int lc15bts_mgr_calib_init(struct lc15bts_mgr_instance *mgr); +int lc15bts_mgr_control_init(struct lc15bts_mgr_instance *mgr); +int lc15bts_mgr_calib_run(struct lc15bts_mgr_instance *mgr); +void lc15bts_mgr_dispatch_alarm(struct lc15bts_mgr_instance *mgr, const int cause, const char *key, const char *text); +extern void *tall_mgr_ctx; + +#endif diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr_calib.c b/src/osmo-bts-litecell15/misc/lc15bts_mgr_calib.c new file mode 100644 index 0000000..badb545 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_mgr_calib.c @@ -0,0 +1,292 @@ +/* OCXO calibration control for Litecell 1.5 BTS management daemon */ + +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * sysmobts_mgr_calib.c + * (C) 2014,2015 by Holger Hans Peter Freyther + * (C) 2014 by Harald Welte for the IPA code from the oml router + * + * 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 . + * + */ + +#include "misc/lc15bts_mgr.h" +#include "misc/lc15bts_misc.h" +#include "misc/lc15bts_clock.h" +#include "misc/lc15bts_swd.h" +#include "misc/lc15bts_par.h" +#include "misc/lc15bts_led.h" +#include "osmo-bts/msg_utils.h" + +#include +#include + +#include + +#include +#include + +#include +#include +#include + +#include + +static void calib_adjust(struct lc15bts_mgr_instance *mgr); +static void calib_state_reset(struct lc15bts_mgr_instance *mgr, int reason); +static void calib_loop_run(void *_data); + +static int ocxodac_saved_value = -1; + +enum calib_state { + CALIB_INITIAL, + CALIB_IN_PROGRESS, +}; + +enum calib_result { + CALIB_FAIL_START, + CALIB_FAIL_GPSFIX, + CALIB_FAIL_CLKERR, + CALIB_FAIL_OCXODAC, + CALIB_SUCCESS, +}; + +static void calib_start(struct lc15bts_mgr_instance *mgr) +{ + int rc; + + rc = lc15bts_clock_err_open(); + if (rc != 0) { + LOGP(DCALIB, LOGL_ERROR, "Failed to open clock error module %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_CLKERR); + return; + } + + rc = lc15bts_clock_dac_open(); + if (rc != 0) { + LOGP(DCALIB, LOGL_ERROR, "Failed to open OCXO dac module %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_OCXODAC); + return; + } + + calib_adjust(mgr); +} + +static void calib_adjust(struct lc15bts_mgr_instance *mgr) +{ + int rc; + int fault; + int error_ppt; + int accuracy_ppq; + int interval_sec; + int dac_value; + int new_dac_value; + int dac_correction; + time_t now; + time_t last_gps_fix; + + rc = lc15bts_clock_err_get(&fault, &error_ppt, + &accuracy_ppq, &interval_sec); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to get clock error measurement %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_CLKERR); + return; + } + + /* get current time */ + now = time(NULL); + + /* first time after start of manager program */ + if (mgr->gps.last_update == 0) + mgr->gps.last_update = now; + + /* read last GPS 3D fix from storage */ + rc = lc15bts_par_get_gps_fix(&last_gps_fix); + if (rc < 0) { + LOGP(DCALIB, LOGL_NOTICE, "Last GPS 3D fix can not read (%d). Last GPS 3D fix sets to zero\n", rc); + last_gps_fix = 0; + } + + if (fault) { + LOGP(DCALIB, LOGL_NOTICE, "GPS has no fix\n"); + calib_state_reset(mgr, CALIB_FAIL_GPSFIX); + return; + } + + /* We got GPS 3D fix */ + LOGP(DCALIB, LOGL_DEBUG, "Got GPS 3D fix warn_flags=0x%08x, last=%lld, now=%lld\n", + mgr->lc15bts_ctrl.warn_flags, + (long long)last_gps_fix, + (long long)now); + + rc = lc15bts_clock_dac_get(&dac_value); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to get OCXO dac value %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_OCXODAC); + return; + } + + /* Set OCXO initial dac value */ + if (ocxodac_saved_value < 0) + ocxodac_saved_value = dac_value; + + LOGP(DCALIB, LOGL_NOTICE, + "Calibration ERR(%f PPB) ACC(%f PPB) INT(%d) DAC(%d)\n", + error_ppt / 1000., accuracy_ppq / 1000000., interval_sec, dac_value); + + /* Need integration time to correct */ + if (interval_sec) { + /* 1 unit of correction equal about 0.5 - 1 PPB correction */ + dac_correction = (int)(-error_ppt * 0.0015); + new_dac_value = dac_value + dac_correction; + + if (new_dac_value > 4095) + new_dac_value = 4095; + else if (new_dac_value < 0) + new_dac_value = 0; + + /* We have a fix, make sure the measured error is + meaningful (10 times the accuracy) */ + if ((new_dac_value != dac_value) && ((100l * abs(error_ppt)) > accuracy_ppq)) { + + LOGP(DCALIB, LOGL_NOTICE, + "Going to apply %d as new clock setting.\n", + new_dac_value); + + rc = lc15bts_clock_dac_set(new_dac_value); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to set OCXO dac value %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_OCXODAC); + return; + } + rc = lc15bts_clock_err_reset(); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to reset clock error module %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_CLKERR); + return; + } + } + /* New conditions to store DAC value: + * - Resolution accuracy less or equal than 0.01PPB (or 10000 PPQ) + * - Error less or equal than 2PPB (or 2000PPT) + * - Solution different than the last one */ + else if (accuracy_ppq <= 10000) { + if((dac_value != ocxodac_saved_value) && (abs(error_ppt) < 2000)) { + LOGP(DCALIB, LOGL_NOTICE, "Saving OCXO DAC value to memory... val = %d\n", dac_value); + rc = lc15bts_clock_dac_save(); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to save OCXO dac value %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_OCXODAC); + } else { + ocxodac_saved_value = dac_value; + } + } + + rc = lc15bts_clock_err_reset(); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to reset clock error module %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_CLKERR); + } + } + } else { + LOGP(DCALIB, LOGL_NOTICE, "Skipping this iteration, no integration time\n"); + } + + calib_state_reset(mgr, CALIB_SUCCESS); + return; +} + +static void calib_close(struct lc15bts_mgr_instance *mgr) +{ + lc15bts_clock_err_close(); + lc15bts_clock_dac_close(); +} + +static void calib_state_reset(struct lc15bts_mgr_instance *mgr, int outcome) +{ + if (mgr->calib.calib_from_loop) { + /* + * In case of success calibrate in two hours again + * and in case of a failure in some minutes. + * + * TODO NTQ: Select timeout based on last error and accuracy + */ + int timeout = 60; + //int timeout = 2 * 60 * 60; + //if (outcome != CALIB_SUCESS) } + // timeout = 5 * 60; + //} + + mgr->calib.calib_timeout.data = mgr; + mgr->calib.calib_timeout.cb = calib_loop_run; + osmo_timer_schedule(&mgr->calib.calib_timeout, timeout, 0); + /* TODO: do we want to notify if we got a calibration error, like no gps fix? */ + lc15bts_swd_event(mgr, SWD_CHECK_CALIB); + } + + mgr->calib.state = CALIB_INITIAL; + calib_close(mgr); +} + +static int calib_run(struct lc15bts_mgr_instance *mgr, int from_loop) +{ + if (mgr->calib.state != CALIB_INITIAL) { + LOGP(DCALIB, LOGL_ERROR, "Calib is already in progress.\n"); + return -1; + } + + mgr->calib.calib_from_loop = from_loop; + + /* From now on everything will be handled from the failure */ + mgr->calib.state = CALIB_IN_PROGRESS; + calib_start(mgr); + return 0; +} + +static void calib_loop_run(void *_data) +{ + int rc; + struct lc15bts_mgr_instance *mgr = _data; + + LOGP(DCALIB, LOGL_NOTICE, "Going to calibrate the system.\n"); + rc = calib_run(mgr, 1); + if (rc != 0) { + calib_state_reset(mgr, CALIB_FAIL_START); + } +} + +int lc15bts_mgr_calib_run(struct lc15bts_mgr_instance *mgr) +{ + return calib_run(mgr, 0); +} + +int lc15bts_mgr_calib_init(struct lc15bts_mgr_instance *mgr) +{ + mgr->calib.state = CALIB_INITIAL; + mgr->calib.calib_timeout.data = mgr; + mgr->calib.calib_timeout.cb = calib_loop_run; + osmo_timer_schedule(&mgr->calib.calib_timeout, 0, 0); + + return 0; +} + diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr_nl.c b/src/osmo-bts-litecell15/misc/lc15bts_mgr_nl.c new file mode 100644 index 0000000..549c179 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_mgr_nl.c @@ -0,0 +1,210 @@ +/* NetworkListen for NuRAN Litecell 1.5 BTS management daemon */ + +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * sysmobts_mgr_nl.c + * (C) 2014 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include "misc/lc15bts_mgr.h" +#include "misc/lc15bts_misc.h" +#include "misc/lc15bts_nl.h" +#include "misc/lc15bts_par.h" +#include "misc/lc15bts_bid.h" + +#include + +#include + +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +#define ETH0_ADDR_SYSFS "/var/lc15/net/eth0/address" + +static struct osmo_fd nl_fd; + +/* + * The TLV structure in IPA messages in UDP packages is a bit + * weird. First the header appears to have an extra NULL byte + * and second the L16 of the L16TV needs to include +1 for the + * tag. The default msgb/tlv and libosmo-abis routines do not + * provide this. + */ + +static void ipaccess_prepend_header_quirk(struct msgb *msg, int proto) +{ + struct ipaccess_head *hh; + + /* prepend the ip.access header */ + hh = (struct ipaccess_head *) msgb_push(msg, sizeof(*hh) + 1); + hh->len = htons(msg->len - sizeof(*hh) - 1); + hh->proto = proto; +} + +static void quirk_l16tv_put(struct msgb *msg, uint16_t len, uint8_t tag, + const uint8_t *val) +{ + uint8_t *buf = msgb_put(msg, len + 2 + 1); + + *buf++ = (len + 1) >> 8; + *buf++ = (len + 1) & 0xff; + *buf++ = tag; + memcpy(buf, val, len); +} + +/* + * We don't look at the content of the request yet and lie + * about most of the responses. + */ +static void respond_to(struct sockaddr_in *src, struct osmo_fd *fd, + uint8_t *data, size_t len) +{ + static int fetched_info = 0; + static char mac_str[20] = {0, }; + static char model_name[64] = {0, }; + static char ser_str[20] = {0, }; + + struct sockaddr_in loc_addr; + int rc; + char loc_ip[INET_ADDRSTRLEN]; + struct msgb *msg = msgb_alloc_headroom(512, 128, "ipa get response"); + if (!msg) { + LOGP(DFIND, LOGL_ERROR, "Failed to allocate msgb\n"); + return; + } + + if (!fetched_info) { + int fd_eth; + int serno; + int model; + int rev; + + /* fetch the MAC */ + fd_eth = open(ETH0_ADDR_SYSFS, O_RDONLY); + if (fd_eth >= 0) { + read(fd_eth, mac_str, sizeof(mac_str)-1); + mac_str[sizeof(mac_str)-1] = '\0'; + close(fd_eth); + } + + /* fetch the serial number */ + lc15bts_par_get_int(tall_mgr_ctx, LC15BTS_PAR_SERNR, &serno); + snprintf(ser_str, sizeof(ser_str), "%d", serno); + + /* fetch the model and trx number */ + snprintf(model_name, sizeof(model_name), "Litecell 1.5 BTS"); + + rev = lc15bts_rev_get(); + if (rev >= 0) { + snprintf(model_name, sizeof(model_name), "%s Rev %c", + model_name, rev); + } + + model = lc15bts_model_get(); + if (model >= 0) { + snprintf(model_name, sizeof(model_name), "%s (%05X)", + model_name, model); + } + fetched_info = 1; + } + + if (source_for_dest(&src->sin_addr, &loc_addr.sin_addr) != 0) { + LOGP(DFIND, LOGL_ERROR, "Failed to determine local source\n"); + return; + } + + msgb_put_u8(msg, IPAC_MSGT_ID_RESP); + + /* append MAC addr */ + quirk_l16tv_put(msg, strlen(mac_str) + 1, IPAC_IDTAG_MACADDR, (uint8_t *) mac_str); + + /* append ip address */ + inet_ntop(AF_INET, &loc_addr.sin_addr, loc_ip, sizeof(loc_ip)); + quirk_l16tv_put(msg, strlen(loc_ip) + 1, IPAC_IDTAG_IPADDR, (uint8_t *) loc_ip); + + /* append the serial number */ + quirk_l16tv_put(msg, strlen(ser_str) + 1, IPAC_IDTAG_SERNR, (uint8_t *) ser_str); + + /* abuse some flags */ + quirk_l16tv_put(msg, strlen(model_name) + 1, IPAC_IDTAG_UNIT, (uint8_t *) model_name); + + /* ip.access nanoBTS would reply to port==3006 */ + ipaccess_prepend_header_quirk(msg, IPAC_PROTO_IPACCESS); + rc = sendto(fd->fd, msg->data, msg->len, 0, (struct sockaddr *)src, sizeof(*src)); + if (rc != msg->len) + LOGP(DFIND, LOGL_ERROR, + "Failed to send with rc(%d) errno(%d)\n", rc, errno); +} + +static int ipaccess_bcast(struct osmo_fd *fd, unsigned int what) +{ + uint8_t data[2048]; + char src[INET_ADDRSTRLEN]; + struct sockaddr_in addr = {}; + socklen_t len = sizeof(addr); + int rc; + + rc = recvfrom(fd->fd, data, sizeof(data), 0, + (struct sockaddr *) &addr, &len); + if (rc <= 0) { + LOGP(DFIND, LOGL_ERROR, + "Failed to read from socket errno(%d)\n", errno); + return -1; + } + + LOGP(DFIND, LOGL_DEBUG, + "Received request from: %s size %d\n", + inet_ntop(AF_INET, &addr.sin_addr, src, sizeof(src)), rc); + + if (rc < 6) + return 0; + + if (data[2] != IPAC_PROTO_IPACCESS || data[4] != IPAC_MSGT_ID_GET) + return 0; + + respond_to(&addr, fd, data + 6, rc - 6); + return 0; +} + +int lc15bts_mgr_nl_init(void) +{ + int rc; + + nl_fd.cb = ipaccess_bcast; + rc = osmo_sock_init_ofd(&nl_fd, AF_INET, SOCK_DGRAM, IPPROTO_UDP, + "0.0.0.0", 3006, OSMO_SOCK_F_BIND); + if (rc < 0) { + perror("Socket creation"); + return -1; + } + + return 0; +} diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr_temp.c b/src/osmo-bts-litecell15/misc/lc15bts_mgr_temp.c new file mode 100644 index 0000000..9665e1d --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_mgr_temp.c @@ -0,0 +1,378 @@ +/* Temperature control for NuRAN Litecell 1.5 BTS management daemon */ + +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * sysmobts_mgr_temp.c + * (C) 2014 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include "misc/lc15bts_mgr.h" +#include "misc/lc15bts_misc.h" +#include "misc/lc15bts_temp.h" +#include "misc/lc15bts_power.h" +#include "misc/lc15bts_led.h" +#include "misc/lc15bts_swd.h" +#include "limits.h" + +#include + +#include +#include +#include + +struct lc15bts_mgr_instance *s_mgr; +static struct osmo_timer_list sensor_ctrl_timer; + +static const struct value_string state_names[] = { + { STATE_NORMAL, "NORMAL" }, + { STATE_WARNING_HYST, "WARNING (HYST)" }, + { STATE_WARNING, "WARNING" }, + { STATE_CRITICAL, "CRITICAL" }, + { 0, NULL } +}; + +const char *lc15bts_mgr_sensor_get_state(enum lc15bts_sensor_state state) +{ + return get_value_string(state_names, state); +} + +static int next_state(enum lc15bts_sensor_state current_state, int critical, int warning) +{ + int next_state = -1; + switch (current_state) { + case STATE_NORMAL: + if (critical) + next_state = STATE_CRITICAL; + else if (warning) + next_state = STATE_WARNING; + break; + case STATE_WARNING_HYST: + if (critical) + next_state = STATE_CRITICAL; + else if (warning) + next_state = STATE_WARNING; + else + next_state = STATE_NORMAL; + break; + case STATE_WARNING: + if (critical) + next_state = STATE_CRITICAL; + else if (!warning) + next_state = STATE_WARNING_HYST; + break; + case STATE_CRITICAL: + if (!critical && !warning) + next_state = STATE_WARNING; + break; + }; + + return next_state; +} + +static void handle_normal_actions(int actions) +{ + /* switch on the PA */ + if (actions & SENSOR_ACT_NORM_PA0_ON) { + if (lc15bts_power_set(LC15BTS_POWER_PA0, 1) != 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to switch on the PA #0\n"); + } else { + LOGP(DTEMP, LOGL_NOTICE, + "Switched on the PA #0 as normal action.\n"); + } + } + + if (actions & SENSOR_ACT_NORM_PA1_ON) { + if (lc15bts_power_set(LC15BTS_POWER_PA1, 1) != 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to switch on the PA #1\n"); + } else { + LOGP(DTEMP, LOGL_NOTICE, + "Switched on the PA #1 as normal action.\n"); + } + } + + if (actions & SENSOR_ACT_NORM_BTS_SRV_ON) { + LOGP(DTEMP, LOGL_NOTICE, + "Going to switch on the BTS service\n"); + /* + * TODO: use/create something like nspawn that serializes + * and used SIGCHLD/waitpid to pick up the dead processes + * without invoking shell. + */ + system("/bin/systemctl start osmo-bts.service"); + } +} + +static void handle_actions(int actions) +{ + /* switch off the PA */ + if (actions & SENSOR_ACT_PA1_OFF) { + if (lc15bts_power_set(LC15BTS_POWER_PA1, 0) != 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to switch off the PA #1. Stop BTS?\n"); + } else { + LOGP(DTEMP, LOGL_NOTICE, + "Switched off the PA #1 due temperature.\n"); + } + } + + if (actions & SENSOR_ACT_PA0_OFF) { + if (lc15bts_power_set(LC15BTS_POWER_PA0, 0) != 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to switch off the PA #0. Stop BTS?\n"); + } else { + LOGP(DTEMP, LOGL_NOTICE, + "Switched off the PA #0 due temperature.\n"); + } + } + + if (actions & SENSOR_ACT_BTS_SRV_OFF) { + LOGP(DTEMP, LOGL_NOTICE, + "Going to switch off the BTS service\n"); + /* + * TODO: use/create something like nspawn that serializes + * and used SIGCHLD/waitpid to pick up the dead processes + * without invoking shell. + */ + system("/bin/systemctl stop osmo-bts.service"); + } +} + +/** + * Go back to normal! Depending on the configuration execute the normal + * actions that could (start to) undo everything we did in the other + * states. What is still missing is the power increase/decrease depending + * on the state. E.g. starting from WARNING_HYST we might want to slowly + * ramp up the output power again. + */ +static void execute_normal_act(struct lc15bts_mgr_instance *manager) +{ + LOGP(DTEMP, LOGL_NOTICE, "System is back to normal state.\n"); + handle_normal_actions(manager->state.action_norm); +} + +static void execute_warning_act(struct lc15bts_mgr_instance *manager) +{ + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning state.\n"); + handle_actions(manager->state.action_warn); +} + +static void execute_critical_act(struct lc15bts_mgr_instance *manager) +{ + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical warning.\n"); + handle_actions(manager->state.action_crit); +} + +static void lc15bts_mgr_sensor_handle(struct lc15bts_mgr_instance *manager, + int critical, int warning) +{ + int new_state = next_state(manager->state.state, critical, warning); + + /* Nothing changed */ + if (new_state < 0) + return; + + LOGP(DTEMP, LOGL_NOTICE, "Moving from state %s to %s.\n", + get_value_string(state_names, manager->state.state), + get_value_string(state_names, new_state)); + manager->state.state = new_state; + switch (manager->state.state) { + case STATE_NORMAL: + execute_normal_act(manager); + break; + case STATE_WARNING_HYST: + /* do nothing? Maybe start to increase transmit power? */ + break; + case STATE_WARNING: + execute_warning_act(manager); + break; + case STATE_CRITICAL: + execute_critical_act(manager); + break; + }; +} + +static void sensor_ctrl_check(struct lc15bts_mgr_instance *mgr) +{ + int rc; + int temp = 0; + int warn_thresh_passed = 0; + int crit_thresh_passed = 0; + + LOGP(DTEMP, LOGL_DEBUG, "Going to check the temperature.\n"); + + /* Read the current supply temperature */ + rc = lc15bts_temp_get(LC15BTS_TEMP_SUPPLY, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to read the supply temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.supply_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.supply_temp_limit.thresh_crit_max) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "Supply temperature is: %d\n", temp); + } + + /* Read the current SoC temperature */ + rc = lc15bts_temp_get(LC15BTS_TEMP_SOC, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to read the SoC temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.soc_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.soc_temp_limit.thresh_crit_max) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "SoC temperature is: %d\n", temp); + } + + /* Read the current fpga temperature */ + rc = lc15bts_temp_get(LC15BTS_TEMP_FPGA, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to read the fpga temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.fpga_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.fpga_temp_limit.thresh_crit_max) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "FPGA temperature is: %d\n", temp); + } + + /* Read the current RMS detector temperature */ + rc = lc15bts_temp_get(LC15BTS_TEMP_RMSDET, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to read the RMS detector temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.rmsdet_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.rmsdet_temp_limit.thresh_crit_max) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "RMS detector temperature is: %d\n", temp); + } + + /* Read the current OCXO temperature */ + rc = lc15bts_temp_get(LC15BTS_TEMP_OCXO, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to read the OCXO temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.ocxo_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.ocxo_temp_limit.thresh_crit_max) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "OCXO temperature is: %d\n", temp); + } + + /* Read the current TX #0 temperature */ + rc = lc15bts_temp_get(LC15BTS_TEMP_TX0, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to read the TX #0 temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.tx0_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.tx0_temp_limit.thresh_crit_max) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "TX #0 temperature is: %d\n", temp); + } + + /* Read the current TX #1 temperature */ + rc = lc15bts_temp_get(LC15BTS_TEMP_TX1, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to read the TX #1 temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.tx1_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.tx1_temp_limit.thresh_crit_max) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "TX #1 temperature is: %d\n", temp); + } + + /* Read the current PA #0 temperature */ + rc = lc15bts_temp_get(LC15BTS_TEMP_PA0, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to read the PA #0 temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.pa0_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.pa0_temp_limit.thresh_crit_max) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "PA #0 temperature is: %d\n", temp); + } + + /* Read the current PA #1 temperature */ + rc = lc15bts_temp_get(LC15BTS_TEMP_PA1, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to read the PA #1 temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.pa1_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.pa1_temp_limit.thresh_crit_max) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "PA #1 temperature is: %d\n", temp); + } + + lc15bts_mgr_sensor_handle(mgr, crit_thresh_passed, warn_thresh_passed); +} + +static void sensor_ctrl_check_cb(void *_data) +{ + struct lc15bts_mgr_instance *mgr = _data; + sensor_ctrl_check(mgr); + /* Check every minute? XXX make it configurable! */ + osmo_timer_schedule(&sensor_ctrl_timer, LC15BTS_SENSOR_TIMER_DURATION, 0); + LOGP(DTEMP, LOGL_DEBUG,"Check sensors timer expired\n"); + /* TODO: do we want to notify if some sensors could not be read? */ + lc15bts_swd_event(mgr, SWD_CHECK_TEMP_SENSOR); +} + +int lc15bts_mgr_sensor_init(struct lc15bts_mgr_instance *mgr) +{ + s_mgr = mgr; + sensor_ctrl_timer.cb = sensor_ctrl_check_cb; + sensor_ctrl_timer.data = s_mgr; + sensor_ctrl_check_cb(s_mgr); + return 0; +} diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr_vty.c b/src/osmo-bts-litecell15/misc/lc15bts_mgr_vty.c new file mode 100644 index 0000000..e1ddfc7 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_mgr_vty.c @@ -0,0 +1,1074 @@ +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * sysmobts_mgr_vty.c + * (C) 2014 by sysmocom - s.f.m.c. GmbH + * + * All Rights Reserved + * + * Author: Alvaro Neira Ayuso + * + * 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 . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "lc15bts_misc.h" +#include "lc15bts_mgr.h" +#include "lc15bts_temp.h" +#include "lc15bts_power.h" +#include "lc15bts_led.h" +#include "btsconfig.h" + +static struct lc15bts_mgr_instance *s_mgr; + +static const char copyright[] = + "(C) 2012 by Harald Welte \r\n" + "(C) 2014 by Holger Hans Peter Freyther\r\n" + "(C) 2015 by Yves Godin \r\n" + "License AGPLv3+: GNU AGPL version 2 or later \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"; + +static int go_to_parent(struct vty *vty) +{ + switch (vty->node) { + case MGR_NODE: + vty->node = CONFIG_NODE; + break; + case ACT_NORM_NODE: + case ACT_WARN_NODE: + case ACT_CRIT_NODE: + case LIMIT_SUPPLY_TEMP_NODE: + case LIMIT_SOC_NODE: + case LIMIT_FPGA_NODE: + case LIMIT_RMSDET_NODE: + case LIMIT_OCXO_NODE: + case LIMIT_TX0_TEMP_NODE: + case LIMIT_TX1_TEMP_NODE: + case LIMIT_PA0_TEMP_NODE: + case LIMIT_PA1_TEMP_NODE: + case LIMIT_SUPPLY_VOLT_NODE: + case LIMIT_TX0_VSWR_NODE: + case LIMIT_TX1_VSWR_NODE: + case LIMIT_SUPPLY_PWR_NODE: + case LIMIT_PA0_PWR_NODE: + case LIMIT_PA1_PWR_NODE: + vty->node = MGR_NODE; + break; + default: + vty->node = CONFIG_NODE; + } + return vty->node; +} + +static int is_config_node(struct vty *vty, int node) +{ + switch (node) { + case MGR_NODE: + case ACT_NORM_NODE: + case ACT_WARN_NODE: + case ACT_CRIT_NODE: + case LIMIT_SUPPLY_TEMP_NODE: + case LIMIT_SOC_NODE: + case LIMIT_FPGA_NODE: + case LIMIT_RMSDET_NODE: + case LIMIT_OCXO_NODE: + case LIMIT_TX0_TEMP_NODE: + case LIMIT_TX1_TEMP_NODE: + case LIMIT_PA0_TEMP_NODE: + case LIMIT_PA1_TEMP_NODE: + case LIMIT_SUPPLY_VOLT_NODE: + case LIMIT_TX0_VSWR_NODE: + case LIMIT_TX1_VSWR_NODE: + case LIMIT_SUPPLY_PWR_NODE: + case LIMIT_PA0_PWR_NODE: + case LIMIT_PA1_PWR_NODE: + return 1; + default: + return 0; + } +} + +static struct vty_app_info vty_info = { + .name = "lc15bts-mgr", + .version = PACKAGE_VERSION, + .go_parent_cb = go_to_parent, + .is_config_node = is_config_node, + .copyright = copyright, +}; + + +#define MGR_STR "Configure lc15bts-mgr\n" + +static struct cmd_node mgr_node = { + MGR_NODE, + "%s(lc15bts-mgr)# ", + 1, +}; + +static struct cmd_node act_norm_node = { + ACT_NORM_NODE, + "%s(actions-normal)# ", + 1, +}; + +static struct cmd_node act_warn_node = { + ACT_WARN_NODE, + "%s(actions-warn)# ", + 1, +}; + +static struct cmd_node act_crit_node = { + ACT_CRIT_NODE, + "%s(actions-critical)# ", + 1, +}; + +static struct cmd_node limit_supply_temp_node = { + LIMIT_SUPPLY_TEMP_NODE, + "%s(limit-supply-temp)# ", + 1, +}; + +static struct cmd_node limit_soc_node = { + LIMIT_SOC_NODE, + "%s(limit-soc)# ", + 1, +}; + +static struct cmd_node limit_fpga_node = { + LIMIT_FPGA_NODE, + "%s(limit-fpga)# ", + 1, +}; + +static struct cmd_node limit_rmsdet_node = { + LIMIT_RMSDET_NODE, + "%s(limit-rmsdet)# ", + 1, +}; + +static struct cmd_node limit_ocxo_node = { + LIMIT_OCXO_NODE, + "%s(limit-ocxo)# ", + 1, +}; + +static struct cmd_node limit_tx0_temp_node = { + LIMIT_TX0_TEMP_NODE, + "%s(limit-tx0-temp)# ", + 1, +}; +static struct cmd_node limit_tx1_temp_node = { + LIMIT_TX1_TEMP_NODE, + "%s(limit-tx1-temp)# ", + 1, +}; +static struct cmd_node limit_pa0_temp_node = { + LIMIT_PA0_TEMP_NODE, + "%s(limit-pa0-temp)# ", + 1, +}; +static struct cmd_node limit_pa1_temp_node = { + LIMIT_PA1_TEMP_NODE, + "%s(limit-pa1-temp)# ", + 1, +}; +static struct cmd_node limit_supply_volt_node = { + LIMIT_SUPPLY_VOLT_NODE, + "%s(limit-supply-volt)# ", + 1, +}; +static struct cmd_node limit_tx0_vswr_node = { + LIMIT_TX0_VSWR_NODE, + "%s(limit-tx0-vswr)# ", + 1, +}; +static struct cmd_node limit_tx1_vswr_node = { + LIMIT_TX1_VSWR_NODE, + "%s(limit-tx1-vswr)# ", + 1, +}; +static struct cmd_node limit_supply_pwr_node = { + LIMIT_SUPPLY_PWR_NODE, + "%s(limit-supply-pwr)# ", + 1, +}; +static struct cmd_node limit_pa0_pwr_node = { + LIMIT_PA0_PWR_NODE, + "%s(limit-pa0-pwr)# ", + 1, +}; +static struct cmd_node limit_pa1_pwr_node = { + LIMIT_PA1_PWR_NODE, + "%s(limit-pa1-pwr)# ", + 1, +}; + +static struct cmd_node limit_gps_fix_node = { + LIMIT_GPS_FIX_NODE, + "%s(limit-gps-fix)# ", + 1, +}; + +DEFUN(cfg_mgr, cfg_mgr_cmd, + "lc15bts-mgr", + MGR_STR) +{ + vty->node = MGR_NODE; + return CMD_SUCCESS; +} + +static void write_volt_limit(struct vty *vty, const char *name, + struct lc15bts_volt_limit *limit) +{ + vty_out(vty, " %s%s", name, VTY_NEWLINE); + vty_out(vty, " threshold warning min %d%s", + limit->thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " threshold critical min %d%s", + limit->thresh_crit_min, VTY_NEWLINE); +} + +static void write_vswr_limit(struct vty *vty, const char *name, + struct lc15bts_vswr_limit *limit) +{ + vty_out(vty, " %s%s", name, VTY_NEWLINE); + vty_out(vty, " threshold warning max %d%s", + limit->thresh_warn_max, VTY_NEWLINE); +} + +static void write_pwr_limit(struct vty *vty, const char *name, + struct lc15bts_pwr_limit *limit) +{ + vty_out(vty, " %s%s", name, VTY_NEWLINE); + vty_out(vty, " threshold warning max %d%s", + limit->thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " threshold critical max %d%s", + limit->thresh_crit_max, VTY_NEWLINE); +} + +static void write_norm_action(struct vty *vty, const char *name, int actions) +{ + vty_out(vty, " %s%s", name, VTY_NEWLINE); + vty_out(vty, " %spa0-on%s", + (actions & SENSOR_ACT_NORM_PA0_ON) ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %spa1-on%s", + (actions & SENSOR_ACT_NORM_PA1_ON) ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %sbts-service-on%s", + (actions & SENSOR_ACT_NORM_BTS_SRV_ON) ? "" : "no ", VTY_NEWLINE); +} + +static void write_action(struct vty *vty, const char *name, int actions) +{ + vty_out(vty, " %s%s", name, VTY_NEWLINE); + vty_out(vty, " %spa0-off%s", + (actions & SENSOR_ACT_PA0_OFF) ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %spa1-off%s", + (actions & SENSOR_ACT_PA1_OFF) ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %sbts-service-off%s", + (actions & SENSOR_ACT_BTS_SRV_OFF) ? "" : "no ", VTY_NEWLINE); +} + +static int config_write_mgr(struct vty *vty) +{ + vty_out(vty, "lc15bts-mgr%s", VTY_NEWLINE); + + write_volt_limit(vty, "limits supply_volt", &s_mgr->volt.supply_volt_limit); + write_pwr_limit(vty, "limits supply_pwr", &s_mgr->pwr.supply_pwr_limit); + write_vswr_limit(vty, "limits tx0_vswr", &s_mgr->vswr.tx0_vswr_limit); + write_vswr_limit(vty, "limits tx1_vswr", &s_mgr->vswr.tx1_vswr_limit); + + write_norm_action(vty, "actions normal", s_mgr->state.action_norm); + write_action(vty, "actions warn", s_mgr->state.action_warn); + write_action(vty, "actions critical", s_mgr->state.action_crit); + + return CMD_SUCCESS; +} + +static int config_write_dummy(struct vty *vty) +{ + return CMD_SUCCESS; +} + +#define CFG_LIMIT_TEMP(name, expl, switch_to, variable) \ +DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \ + "limits " #name, \ + "Configure Limits\n" expl) \ +{ \ + vty->node = switch_to; \ + vty->index = &s_mgr->temp.variable; \ + return CMD_SUCCESS; \ +} + +CFG_LIMIT_TEMP(supply_temp, "SUPPLY TEMP\n", LIMIT_SUPPLY_TEMP_NODE, supply_temp_limit) +CFG_LIMIT_TEMP(soc_temp, "SOC TEMP\n", LIMIT_SOC_NODE, soc_temp_limit) +CFG_LIMIT_TEMP(fpga_temp, "FPGA TEMP\n", LIMIT_FPGA_NODE, fpga_temp_limit) +CFG_LIMIT_TEMP(rmsdet_temp, "RMSDET TEMP\n", LIMIT_RMSDET_NODE, rmsdet_temp_limit) +CFG_LIMIT_TEMP(ocxo_temp, "OCXO TEMP\n", LIMIT_OCXO_NODE, ocxo_temp_limit) +CFG_LIMIT_TEMP(tx0_temp, "TX0 TEMP\n", LIMIT_TX0_TEMP_NODE, tx0_temp_limit) +CFG_LIMIT_TEMP(tx1_temp, "TX1 TEMP\n", LIMIT_TX1_TEMP_NODE, tx1_temp_limit) +CFG_LIMIT_TEMP(pa0_temp, "PA0 TEMP\n", LIMIT_PA0_TEMP_NODE, pa0_temp_limit) +CFG_LIMIT_TEMP(pa1_temp, "PA1 TEMP\n", LIMIT_PA1_TEMP_NODE, pa1_temp_limit) +#undef CFG_LIMIT_TEMP + +#define CFG_LIMIT_VOLT(name, expl, switch_to, variable) \ +DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \ + "limits " #name, \ + "Configure Limits\n" expl) \ +{ \ + vty->node = switch_to; \ + vty->index = &s_mgr->volt.variable; \ + return CMD_SUCCESS; \ +} + +CFG_LIMIT_VOLT(supply_volt, "SUPPLY VOLT\n", LIMIT_SUPPLY_VOLT_NODE, supply_volt_limit) +#undef CFG_LIMIT_VOLT + +#define CFG_LIMIT_VSWR(name, expl, switch_to, variable) \ +DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \ + "limits " #name, \ + "Configure Limits\n" expl) \ +{ \ + vty->node = switch_to; \ + vty->index = &s_mgr->vswr.variable; \ + return CMD_SUCCESS; \ +} + +CFG_LIMIT_VSWR(tx0_vswr, "TX0 VSWR\n", LIMIT_TX0_VSWR_NODE, tx0_vswr_limit) +CFG_LIMIT_VSWR(tx1_vswr, "TX1 VSWR\n", LIMIT_TX1_VSWR_NODE, tx1_vswr_limit) +#undef CFG_LIMIT_VSWR + +#define CFG_LIMIT_PWR(name, expl, switch_to, variable) \ +DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \ + "limits " #name, \ + "Configure Limits\n" expl) \ +{ \ + vty->node = switch_to; \ + vty->index = &s_mgr->pwr.variable; \ + return CMD_SUCCESS; \ +} + +CFG_LIMIT_PWR(supply_pwr, "SUPPLY PWR\n", LIMIT_SUPPLY_PWR_NODE, supply_pwr_limit) +CFG_LIMIT_PWR(pa0_pwr, "PA0 PWR\n", LIMIT_PA0_PWR_NODE, pa0_pwr_limit) +CFG_LIMIT_PWR(pa1_pwr, "PA1 PWR\n", LIMIT_PA1_PWR_NODE, pa1_pwr_limit) +#undef CFG_LIMIT_PWR + +#define CFG_LIMIT_GPS_FIX(name, expl, switch_to, variable) \ +DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \ + "limits " #name, \ + "Configure Limits\n" expl) \ +{ \ + vty->node = switch_to; \ + vty->index = &s_mgr->gps.variable; \ + return CMD_SUCCESS; \ +} + +CFG_LIMIT_GPS_FIX(gps_fix, "GPS FIX\n", LIMIT_GPS_FIX_NODE, gps_fix_limit) +#undef CFG_LIMIT_GPS_FIX + +DEFUN(cfg_limit_volt_warn_min, cfg_thresh_volt_warn_min_cmd, + "threshold warning min <0-48000>", + "Threshold to reach\n" "Warning level\n" "Range\n") +{ + struct lc15bts_volt_limit *limit = vty->index; + limit->thresh_warn_min = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_limit_volt_crit_min, cfg_thresh_volt_crit_min_cmd, + "threshold critical min <0-48000>", + "Threshold to reach\n" "Critical level\n" "Range\n") +{ + struct lc15bts_volt_limit *limit = vty->index; + limit->thresh_crit_min = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_limit_vswr_warn_max, cfg_thresh_vswr_warn_max_cmd, + "threshold warning max <1000-200000>", + "Threshold to reach\n" "Warning level\n" "Range\n") +{ + struct lc15bts_vswr_limit *limit = vty->index; + limit->thresh_warn_max = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_limit_vswr_crit_max, cfg_thresh_vswr_crit_max_cmd, + "threshold critical max <1000-200000>", + "Threshold to reach\n" "Critical level\n" "Range\n") +{ + struct lc15bts_vswr_limit *limit = vty->index; + limit->thresh_crit_max = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_limit_pwr_warn_max, cfg_thresh_pwr_warn_max_cmd, + "threshold warning max <0-200>", + "Threshold to reach\n" "Warning level\n" "Range\n") +{ + struct lc15bts_pwr_limit *limit = vty->index; + limit->thresh_warn_max = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_limit_pwr_crit_max, cfg_thresh_pwr_crit_max_cmd, + "threshold critical max <0-200>", + "Threshold to reach\n" "Critical level\n" "Range\n") +{ + struct lc15bts_pwr_limit *limit = vty->index; + limit->thresh_crit_max = atoi(argv[0]); + return CMD_SUCCESS; +} + +#define CFG_ACTION(name, expl, switch_to, variable) \ +DEFUN(cfg_action_##name, cfg_action_##name##_cmd, \ + "actions " #name, \ + "Configure Actions\n" expl) \ +{ \ + vty->node = switch_to; \ + vty->index = &s_mgr->state.variable; \ + return CMD_SUCCESS; \ +} +CFG_ACTION(normal, "Normal Actions\n", ACT_NORM_NODE, action_norm) +CFG_ACTION(warn, "Warning Actions\n", ACT_WARN_NODE, action_warn) +CFG_ACTION(critical, "Critical Actions\n", ACT_CRIT_NODE, action_crit) +#undef CFG_ACTION + +DEFUN(cfg_action_pa0_on, cfg_action_pa0_on_cmd, + "pa0-on", + "Switch the Power Amplifier #0 on\n") +{ + int *action = vty->index; + *action |= SENSOR_ACT_NORM_PA0_ON; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_action_pa0_on, cfg_no_action_pa0_on_cmd, + "no pa0-on", + NO_STR "Switch the Power Amplifieri #0 on\n") +{ + int *action = vty->index; + *action &= ~SENSOR_ACT_NORM_PA0_ON; + return CMD_SUCCESS; +} + +DEFUN(cfg_action_pa1_on, cfg_action_pa1_on_cmd, + "pa1-on", + "Switch the Power Amplifier #1 on\n") +{ + int *action = vty->index; + *action |= SENSOR_ACT_NORM_PA1_ON; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_action_pa1_on, cfg_no_action_pa1_on_cmd, + "no pa1-on", + NO_STR "Switch the Power Amplifieri #1 on\n") +{ + int *action = vty->index; + *action &= ~SENSOR_ACT_NORM_PA1_ON; + return CMD_SUCCESS; +} + +DEFUN(cfg_action_bts_srv_on, cfg_action_bts_srv_on_cmd, + "bts-service-on", + "Start the systemd lc15bts.service\n") +{ + int *action = vty->index; + *action |= SENSOR_ACT_NORM_BTS_SRV_ON; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_action_bts_srv_on, cfg_no_action_bts_srv_on_cmd, + "no bts-service-on", + NO_STR "Start the systemd lc15bts.service\n") +{ + int *action = vty->index; + *action &= ~SENSOR_ACT_NORM_BTS_SRV_ON; + return CMD_SUCCESS; +} + +DEFUN(cfg_action_pa0_off, cfg_action_pa0_off_cmd, + "pa0-off", + "Switch the Power Amplifier #0 off\n") +{ + int *action = vty->index; + *action |= SENSOR_ACT_PA0_OFF; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_action_pa0_off, cfg_no_action_pa0_off_cmd, + "no pa0-off", + NO_STR "Do not switch off the Power Amplifier #0\n") +{ + int *action = vty->index; + *action &= ~SENSOR_ACT_PA0_OFF; + return CMD_SUCCESS; +} + +DEFUN(cfg_action_pa1_off, cfg_action_pa1_off_cmd, + "pa1-off", + "Switch the Power Amplifier #1 off\n") +{ + int *action = vty->index; + *action |= SENSOR_ACT_PA1_OFF; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_action_pa1_off, cfg_no_action_pa1_off_cmd, + "no pa1-off", + NO_STR "Do not switch off the Power Amplifier #1\n") +{ + int *action = vty->index; + *action &= ~SENSOR_ACT_PA1_OFF; + return CMD_SUCCESS; +} + +DEFUN(cfg_action_bts_srv_off, cfg_action_bts_srv_off_cmd, + "bts-service-off", + "Stop the systemd lc15bts.service\n") +{ + int *action = vty->index; + *action |= SENSOR_ACT_BTS_SRV_OFF; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_action_bts_srv_off, cfg_no_action_bts_srv_off_cmd, + "no bts-service-off", + NO_STR "Stop the systemd lc15bts.service\n") +{ + int *action = vty->index; + *action &= ~SENSOR_ACT_BTS_SRV_OFF; + return CMD_SUCCESS; +} + +DEFUN(show_mgr, show_mgr_cmd, "show manager", + SHOW_STR "Display information about the manager") +{ + int temp, volt, current, power, vswr; + vty_out(vty, "Warning alarm flags: 0x%08x%s", + s_mgr->lc15bts_ctrl.warn_flags, VTY_NEWLINE); + vty_out(vty, "Critical alarm flags: 0x%08x%s", + s_mgr->lc15bts_ctrl.crit_flags, VTY_NEWLINE); + vty_out(vty, "Preventive action retried: %d%s", + s_mgr->alarms.preventive_retry, VTY_NEWLINE); + vty_out(vty, "Temperature control state: %s%s", + lc15bts_mgr_sensor_get_state(s_mgr->state.state), VTY_NEWLINE); + vty_out(vty, "Current Temperatures%s", VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_SUPPLY, &temp); + vty_out(vty, " Main Supply : %4.2f Celcius%s", + temp/ 1000.0f, + VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_SOC, &temp); + vty_out(vty, " SoC : %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_FPGA, &temp); + vty_out(vty, " FPGA : %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_RMSDET, &temp); + vty_out(vty, " RMSDet : %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_OCXO, &temp); + vty_out(vty, " OCXO : %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_TX0, &temp); + vty_out(vty, " TX 0 : %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_TX1, &temp); + vty_out(vty, " TX 1 : %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_PA0, &temp); + vty_out(vty, " Power Amp #0: %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_PA1, &temp); + vty_out(vty, " Power Amp #1: %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + + vty_out(vty, "Power Status%s", VTY_NEWLINE); + lc15bts_power_sensor_get(LC15BTS_POWER_SUPPLY, + LC15BTS_POWER_VOLTAGE, &volt); + lc15bts_power_sensor_get(LC15BTS_POWER_SUPPLY, + LC15BTS_POWER_CURRENT, ¤t); + lc15bts_power_sensor_get(LC15BTS_POWER_SUPPLY, + LC15BTS_POWER_POWER, &power); + vty_out(vty, " Main Supply : ON [%6.2f Vdc, %4.2f A, %6.2f W]%s", + volt /1000.0f, + current /1000.0f, + power /1000000.0f, + VTY_NEWLINE); + lc15bts_power_sensor_get(LC15BTS_POWER_PA0, + LC15BTS_POWER_VOLTAGE, &volt); + lc15bts_power_sensor_get(LC15BTS_POWER_PA0, + LC15BTS_POWER_CURRENT, ¤t); + lc15bts_power_sensor_get(LC15BTS_POWER_PA0, + LC15BTS_POWER_POWER, &power); + vty_out(vty, " Power Amp #0: %s [%6.2f Vdc, %4.2f A, %6.2f W]%s", + lc15bts_power_get(LC15BTS_POWER_PA0) ? "ON " : "OFF", + volt /1000.0f, + current /1000.0f, + power /1000000.0f, + VTY_NEWLINE); + lc15bts_power_sensor_get(LC15BTS_POWER_PA1, + LC15BTS_POWER_VOLTAGE, &volt); + lc15bts_power_sensor_get(LC15BTS_POWER_PA1, + LC15BTS_POWER_CURRENT, ¤t); + lc15bts_power_sensor_get(LC15BTS_POWER_PA1, + LC15BTS_POWER_POWER, &power); + vty_out(vty, " Power Amp #1: %s [%6.2f Vdc, %4.2f A, %6.2f W]%s", + lc15bts_power_get(LC15BTS_POWER_PA1) ? "ON " : "OFF", + volt /1000.0f, + current /1000.0f, + power /1000000.0f, + VTY_NEWLINE); + vty_out(vty, "VSWR Status%s", VTY_NEWLINE); + lc15bts_vswr_get(LC15BTS_VSWR_TX0, &vswr); + vty_out(vty, " VSWR TX 0: %f %s", + vswr / 1000.0f, + VTY_NEWLINE); + lc15bts_vswr_get(LC15BTS_VSWR_TX1, &vswr); + vty_out(vty, " VSWR TX 1: %f %s", + vswr / 1000.0f, + VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(show_thresh, show_thresh_cmd, "show thresholds", + SHOW_STR "Display information about the thresholds") +{ + vty_out(vty, "Temperature limits (Celsius)%s", VTY_NEWLINE); + vty_out(vty, " Main supply%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.supply_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.supply_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.supply_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " SoC%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.soc_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.soc_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.soc_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " FPGA%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.fpga_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.fpga_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.fpga_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " RMSDet%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " OCXO%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " TX0%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.tx0_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.tx0_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.tx0_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " TX1%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.tx1_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.tx1_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.tx1_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " PA0%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.pa0_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.pa0_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.pa0_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " PA1%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.pa1_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.pa1_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.pa1_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, "Power limits%s", VTY_NEWLINE); + vty_out(vty, " Main supply (mV)%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->volt.supply_volt_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->volt.supply_volt_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->volt.supply_volt_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " Critical min : %d%s",s_mgr->volt.supply_volt_limit.thresh_crit_min, VTY_NEWLINE); + vty_out(vty, " Main supply power (W)%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->pwr.supply_pwr_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->pwr.supply_pwr_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " PA0 power (W)%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->pwr.pa0_pwr_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->pwr.pa0_pwr_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " PA1 power (W)%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->pwr.pa1_pwr_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->pwr.pa1_pwr_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, "VSWR limits%s", VTY_NEWLINE); + vty_out(vty, " TX0%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->vswr.tx0_vswr_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->vswr.tx0_vswr_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " TX1%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->vswr.tx1_vswr_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->vswr.tx1_vswr_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, "Days since last GPS 3D fix%s", VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->gps.gps_fix_limit.thresh_warn_max, VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(calibrate_clock, calibrate_clock_cmd, + "calibrate clock", + "Calibration commands\n" + "Calibrate clock against GPS PPS\n") +{ + if (lc15bts_mgr_calib_run(s_mgr) < 0) { + vty_out(vty, "%%Failed to start calibration.%s", VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(set_led_pattern, set_led_pattern_cmd, + "set led pattern <0-255>", + "Set LED pattern\n" + "Set LED pattern for debugging purpose only. This pattern will be overridden after 60 seconds by LED pattern of actual system state\n") +{ + int pattern_id = atoi(argv[0]); + + if ((pattern_id < 0) || (pattern_id > BLINK_PATTERN_MAX_ITEM)) { + vty_out(vty, "%%Invalid LED pattern ID. It must be in range of %d..%d %s", 0, BLINK_PATTERN_MAX_ITEM - 1, VTY_NEWLINE); + return CMD_WARNING; + } + + led_set(s_mgr, pattern_id); + return CMD_SUCCESS; +} + +DEFUN(force_mgr_state, force_mgr_state_cmd, + "force manager state <0-255>", + "Force BTS manager state\n" + "Force BTS manager state for debugging purpose only\n") +{ + int state = atoi(argv[0]); + + if ((state < 0) || (state > STATE_CRITICAL)) { + vty_out(vty, "%%Invalid BTS manager state. It must be in range of %d..%d %s", 0, STATE_CRITICAL, VTY_NEWLINE); + return CMD_WARNING; + } + + s_mgr->state.state = state; + return CMD_SUCCESS; +} + +#define LIMIT_TEMP(name, limit, expl, variable, criticity, min_max) \ +DEFUN(limit_temp_##name##_##variable, limit_temp_##name##_##variable##_cmd, \ + "limit temp " #name " " #criticity " " #min_max " <-200-200>", \ + "Limit to reach\n" expl) \ +{ \ + s_mgr->temp.limit.variable = atoi(argv[0]); \ + return CMD_SUCCESS; \ +} + +LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(tx0, tx0_temp_limit, "TX0 TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(tx0, tx0_temp_limit, "TX0 TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(tx0, tx0_temp_limit, "TX0 TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(tx1, tx1_temp_limit, "TX1 TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(tx1, tx1_temp_limit, "TX1 TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(tx1, tx1_temp_limit, "TX1 TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(pa0, pa0_temp_limit, "PA0 TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(pa0, pa0_temp_limit, "PA0 TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(pa0, pa0_temp_limit, "PA0 TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(pa1, pa1_temp_limit, "PA1 TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(pa1, pa1_temp_limit, "PA1 TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(pa1, pa1_temp_limit, "PA1 TEMP\n", thresh_warn_min, warning, min) +#undef LIMIT_TEMP + +#define LIMIT_VOLT(name, limit, expl, variable, criticity, min_max) \ +DEFUN(limit_volt_##name##_##variable, limit_volt_##name##_##variable##_cmd, \ + "limit " #name " " #criticity " " #min_max " <0-48000>", \ + "Limit to reach\n" expl) \ +{ \ + s_mgr->volt.limit.variable = atoi(argv[0]); \ + return CMD_SUCCESS; \ +} + +LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_warn_max, warning, max) +LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_crit_max, critical, max) +LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_warn_min, warning, min) +LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_crit_min, critical, min) +#undef LIMIT_VOLT + +#define LIMIT_PWR(name, limit, expl, variable, criticity, min_max) \ + DEFUN(limit_pwr_##name##_##variable, limit_pwr_##name##_##variable##_cmd, \ + "limit power " #name " " #criticity " " #min_max " <0-200>", \ + "Limit to reach\n" expl) \ +{ \ + s_mgr->pwr.limit.variable = atoi(argv[0]); \ + return CMD_SUCCESS; \ +} + +LIMIT_PWR(supply, supply_pwr_limit, "SUPPLY PWR\n", thresh_warn_max, warning, max) +LIMIT_PWR(supply, supply_pwr_limit, "SUPPLY PWR\n", thresh_crit_max, critical, max) +LIMIT_PWR(pa0, pa0_pwr_limit, "PA0 PWR\n", thresh_warn_max, warning, max) +LIMIT_PWR(pa0, pa0_pwr_limit, "PA0 PWR\n", thresh_crit_max, critical, max) +LIMIT_PWR(pa1, pa1_pwr_limit, "PA1 PWR\n", thresh_warn_max, warning, max) +LIMIT_PWR(pa1, pa1_pwr_limit, "PA1 PWR\n", thresh_crit_max, critical, max) +#undef LIMIT_PWR + +#define LIMIT_VSWR(name, limit, expl, variable, criticity, min_max) \ +DEFUN(limit_vswr_##name##_##variable, limit_vswr_##name##_##variable##_cmd, \ + "limit vswr " #name " " #criticity " " #min_max " <1000-200000>", \ + "Limit to reach\n" expl) \ +{ \ + s_mgr->vswr.limit.variable = atoi(argv[0]); \ + return CMD_SUCCESS; \ +} + +LIMIT_VSWR(tx0, tx0_vswr_limit, "TX0 VSWR\n", thresh_warn_max, warning, max) +LIMIT_VSWR(tx0, tx0_vswr_limit, "TX0 VSWR\n", thresh_crit_max, critical, max) +LIMIT_VSWR(tx1, tx1_vswr_limit, "TX1 VSWR\n", thresh_warn_max, warning, max) +LIMIT_VSWR(tx1, tx1_vswr_limit, "TX1 VSWR\n", thresh_crit_max, critical, max) +#undef LIMIT_VSWR + +#define LIMIT_GPSFIX(limit, expl, variable, criticity, min_max) \ +DEFUN(limit_gpsfix_##variable, limit_gpsfix_##variable##_cmd, \ + "limit gpsfix " #criticity " " #min_max " <0-365>", \ + "Limit to reach\n" expl) \ +{ \ + s_mgr->gps.limit.variable = atoi(argv[0]); \ + return CMD_SUCCESS; \ +} + +LIMIT_GPSFIX(gps_fix_limit, "GPS FIX\n", thresh_warn_max, warning, max) +#undef LIMIT_GPSFIX + +static void register_limit(int limit, uint32_t unit) +{ + switch (unit) { + case MGR_LIMIT_TYPE_VOLT: + install_element(limit, &cfg_thresh_volt_warn_min_cmd); + install_element(limit, &cfg_thresh_volt_crit_min_cmd); + break; + case MGR_LIMIT_TYPE_VSWR: + install_element(limit, &cfg_thresh_vswr_warn_max_cmd); + install_element(limit, &cfg_thresh_vswr_crit_max_cmd); + break; + case MGR_LIMIT_TYPE_PWR: + install_element(limit, &cfg_thresh_pwr_warn_max_cmd); + install_element(limit, &cfg_thresh_pwr_crit_max_cmd); + break; + default: + break; + } +} + +static void register_normal_action(int act) +{ + install_element(act, &cfg_action_pa0_on_cmd); + install_element(act, &cfg_no_action_pa0_on_cmd); + install_element(act, &cfg_action_pa1_on_cmd); + install_element(act, &cfg_no_action_pa1_on_cmd); + install_element(act, &cfg_action_bts_srv_on_cmd); + install_element(act, &cfg_no_action_bts_srv_on_cmd); +} + +static void register_action(int act) +{ + install_element(act, &cfg_action_pa0_off_cmd); + install_element(act, &cfg_no_action_pa0_off_cmd); + install_element(act, &cfg_action_pa1_off_cmd); + install_element(act, &cfg_no_action_pa1_off_cmd); + install_element(act, &cfg_action_bts_srv_off_cmd); + install_element(act, &cfg_no_action_bts_srv_off_cmd); +} + +static void register_hidden_commands() +{ + install_element(ENABLE_NODE, &limit_temp_supply_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_supply_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_supply_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_soc_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_soc_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_soc_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_fpga_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_fpga_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_fpga_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_tx0_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_tx0_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_tx0_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_tx1_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_tx1_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_tx1_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_pa0_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_pa0_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_pa0_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_pa1_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_pa1_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_pa1_thresh_warn_min_cmd); + + install_element(ENABLE_NODE, &limit_volt_supply_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_volt_supply_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_volt_supply_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_volt_supply_thresh_crit_min_cmd); + + install_element(ENABLE_NODE, &limit_pwr_supply_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_pwr_supply_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_pwr_pa0_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_pwr_pa0_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_pwr_pa1_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_pwr_pa1_thresh_crit_max_cmd); + + install_element(ENABLE_NODE, &limit_vswr_tx0_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_vswr_tx0_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_vswr_tx1_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_vswr_tx1_thresh_crit_max_cmd); + + install_element(ENABLE_NODE, &limit_gpsfix_thresh_warn_max_cmd); +} + +int lc15bts_mgr_vty_init(void) +{ + vty_init(&vty_info); + + install_element_ve(&show_mgr_cmd); + install_element_ve(&show_thresh_cmd); + + install_element(ENABLE_NODE, &calibrate_clock_cmd); + + install_node(&mgr_node, config_write_mgr); + install_element(CONFIG_NODE, &cfg_mgr_cmd); + + /* install the limit nodes */ + install_node(&limit_supply_temp_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_supply_temp_cmd); + + install_node(&limit_soc_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_soc_temp_cmd); + + install_node(&limit_fpga_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_fpga_temp_cmd); + + install_node(&limit_rmsdet_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_rmsdet_temp_cmd); + + install_node(&limit_ocxo_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_ocxo_temp_cmd); + + install_node(&limit_tx0_temp_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_tx0_temp_cmd); + + install_node(&limit_tx1_temp_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_tx1_temp_cmd); + + install_node(&limit_pa0_temp_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_pa0_temp_cmd); + + install_node(&limit_pa1_temp_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_pa1_temp_cmd); + + install_node(&limit_supply_volt_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_supply_volt_cmd); + register_limit(LIMIT_SUPPLY_VOLT_NODE, MGR_LIMIT_TYPE_VOLT); + + install_node(&limit_tx0_vswr_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_tx0_vswr_cmd); + register_limit(LIMIT_TX0_VSWR_NODE, MGR_LIMIT_TYPE_VSWR); + + install_node(&limit_tx1_vswr_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_tx1_vswr_cmd); + register_limit(LIMIT_TX1_VSWR_NODE, MGR_LIMIT_TYPE_VSWR); + + install_node(&limit_supply_pwr_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_supply_pwr_cmd); + register_limit(LIMIT_SUPPLY_PWR_NODE, MGR_LIMIT_TYPE_PWR); + + install_node(&limit_pa0_pwr_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_pa0_pwr_cmd); + register_limit(LIMIT_PA0_PWR_NODE, MGR_LIMIT_TYPE_PWR); + + install_node(&limit_pa1_pwr_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_pa1_pwr_cmd); + register_limit(LIMIT_PA1_PWR_NODE, MGR_LIMIT_TYPE_PWR); + + install_node(&limit_gps_fix_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_gps_fix_cmd); + + /* install the normal node */ + install_node(&act_norm_node, config_write_dummy); + install_element(MGR_NODE, &cfg_action_normal_cmd); + register_normal_action(ACT_NORM_NODE); + + /* install the warning and critical node */ + install_node(&act_warn_node, config_write_dummy); + install_element(MGR_NODE, &cfg_action_warn_cmd); + register_action(ACT_WARN_NODE); + + install_node(&act_crit_node, config_write_dummy); + install_element(MGR_NODE, &cfg_action_critical_cmd); + register_action(ACT_CRIT_NODE); + + /* install LED pattern command for debugging purpose */ + install_element_ve(&set_led_pattern_cmd); + install_element_ve(&force_mgr_state_cmd); + + register_hidden_commands(); + + return 0; +} + +int lc15bts_mgr_parse_config(struct lc15bts_mgr_instance *manager) +{ + int rc; + + s_mgr = manager; + rc = vty_read_config_file(s_mgr->config_file, NULL); + if (rc < 0) { + fprintf(stderr, "Failed to parse the config file: '%s'\n", + s_mgr->config_file); + return rc; + } + + return 0; +} diff --git a/src/osmo-bts-litecell15/misc/lc15bts_misc.c b/src/osmo-bts-litecell15/misc/lc15bts_misc.c new file mode 100644 index 0000000..2cedc5d --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_misc.c @@ -0,0 +1,383 @@ +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * sysmobts_misc.c + * (C) 2012 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "lc15bts_mgr.h" +#include "btsconfig.h" +#include "lc15bts_misc.h" +#include "lc15bts_par.h" +#include "lc15bts_mgr.h" +#include "lc15bts_temp.h" +#include "lc15bts_power.h" + +/********************************************************************* + * Temperature handling + *********************************************************************/ + +static const struct { + const char *name; + int has_max; + enum lc15bts_temp_sensor sensor; + enum lc15bts_par ee_par; +} temp_data[] = { + { + .name = "supply_temp", + .has_max = 1, + .sensor = LC15BTS_TEMP_SUPPLY, + .ee_par = LC15BTS_PAR_TEMP_SUPPLY_MAX, + }, { + .name = "soc_temp", + .has_max = 0, + .sensor = LC15BTS_TEMP_SOC, + .ee_par = LC15BTS_PAR_TEMP_SOC_MAX, + }, { + .name = "fpga_temp", + .has_max = 0, + .sensor = LC15BTS_TEMP_FPGA, + .ee_par = LC15BTS_PAR_TEMP_FPGA_MAX, + + }, { + .name = "rmsdet_temp", + .has_max = 1, + .sensor = LC15BTS_TEMP_RMSDET, + .ee_par = LC15BTS_PAR_TEMP_RMSDET_MAX, + }, { + .name = "ocxo_temp", + .has_max = 1, + .sensor = LC15BTS_TEMP_OCXO, + .ee_par = LC15BTS_PAR_TEMP_OCXO_MAX, + }, { + .name = "tx0_temp", + .has_max = 0, + .sensor = LC15BTS_TEMP_TX0, + .ee_par = LC15BTS_PAR_TEMP_TX0_MAX, + }, { + .name = "tx1_temp", + .has_max = 0, + .sensor = LC15BTS_TEMP_TX1, + .ee_par = LC15BTS_PAR_TEMP_TX1_MAX, + }, { + .name = "pa0_temp", + .has_max = 1, + .sensor = LC15BTS_TEMP_PA0, + .ee_par = LC15BTS_PAR_TEMP_PA0_MAX, + }, { + .name = "pa1_temp", + .has_max = 1, + .sensor = LC15BTS_TEMP_PA1, + .ee_par = LC15BTS_PAR_TEMP_PA1_MAX, + } +}; + +static const struct { + const char *name; + int has_max; + enum lc15bts_power_source sensor_source; + enum lc15bts_power_type sensor_type; + enum lc15bts_par ee_par; +} power_data[] = { + { + .name = "supply_volt", + .has_max = 1, + .sensor_source = LC15BTS_POWER_SUPPLY, + .sensor_type = LC15BTS_POWER_VOLTAGE, + .ee_par = LC15BTS_PAR_VOLT_SUPPLY_MAX, + }, { + .name = "supply_pwr", + .has_max = 1, + .sensor_source = LC15BTS_POWER_SUPPLY, + .sensor_type = LC15BTS_POWER_POWER, + .ee_par = LC15BTS_PAR_PWR_SUPPLY_MAX, + }, { + .name = "pa0_pwr", + .has_max = 1, + .sensor_source = LC15BTS_POWER_PA0, + .sensor_type = LC15BTS_POWER_POWER, + .ee_par = LC15BTS_PAR_PWR_PA0_MAX, + }, { + .name = "pa1_pwr", + .has_max = 1, + .sensor_source = LC15BTS_POWER_PA1, + .sensor_type = LC15BTS_POWER_POWER, + .ee_par = LC15BTS_PAR_PWR_PA1_MAX, + } +}; + +static const struct { + const char *name; + int has_max; + enum lc15bts_vswr_sensor sensor; + enum lc15bts_par ee_par; +} vswr_data[] = { + { + .name = "tx0_vswr", + .has_max = 0, + .sensor = LC15BTS_VSWR_TX0, + .ee_par = LC15BTS_PAR_VSWR_TX0_MAX, + }, { + .name = "tx1_vswr", + .has_max = 0, + .sensor = LC15BTS_VSWR_TX1, + .ee_par = LC15BTS_PAR_VSWR_TX1_MAX, + } +}; + +static const struct value_string power_unit_strs[] = { + { LC15BTS_POWER_POWER, "W" }, + { LC15BTS_POWER_VOLTAGE, "V" }, + { 0, NULL } +}; + +void lc15bts_check_temp(int no_rom_write) +{ + int temp_old[ARRAY_SIZE(temp_data)]; + int temp_cur[ARRAY_SIZE(temp_data)]; + int i, rc; + + for (i = 0; i < ARRAY_SIZE(temp_data); i++) { + int ret; + rc = lc15bts_par_get_int(tall_mgr_ctx, temp_data[i].ee_par, &ret); + temp_old[i] = ret * 1000; + + lc15bts_temp_get(temp_data[i].sensor, &temp_cur[i]); + if (temp_cur[i] < 0 && temp_cur[i] > -1000) { + LOGP(DTEMP, LOGL_ERROR, "Error reading temperature (%d): unexpected value %d\n", + temp_data[i].sensor, temp_cur[i]); + continue; + } + + LOGP(DTEMP, LOGL_DEBUG, "Current %s temperature: %d.%d C\n", + temp_data[i].name, temp_cur[i]/1000, temp_cur[i]%1000); + + if (temp_cur[i] > temp_old[i]) { + LOGP(DTEMP, LOGL_NOTICE, "New maximum %s " + "temperature: %d.%d C\n", temp_data[i].name, + temp_cur[i]/1000, temp_old[i]%1000); + + if (!no_rom_write) { + rc = lc15bts_par_set_int(tall_mgr_ctx, temp_data[i].ee_par, temp_cur[i]/1000); + if (rc < 0) + LOGP(DTEMP, LOGL_ERROR, "error writing new %s " + "max temp %d (%s)\n", temp_data[i].name, + rc, strerror(errno)); + } + } + } +} + +void lc15bts_check_power(int no_rom_write) +{ + int power_old[ARRAY_SIZE(power_data)]; + int power_cur[ARRAY_SIZE(power_data)]; + int i, rc; + int div_ratio; + + for (i = 0; i < ARRAY_SIZE(power_data); i++) { + int ret; + rc = lc15bts_par_get_int(tall_mgr_ctx, power_data[i].ee_par, &ret); + switch(power_data[i].sensor_type) { + case LC15BTS_POWER_VOLTAGE: + div_ratio = 1000; + break; + case LC15BTS_POWER_POWER: + div_ratio = 1000000; + break; + default: + div_ratio = 1000; + } + power_old[i] = ret * div_ratio; + + lc15bts_power_sensor_get(power_data[i].sensor_source, power_data[i].sensor_type, &power_cur[i]); + if (power_cur[i] < 0 && power_cur[i] > -1000) { + LOGP(DTEMP, LOGL_ERROR, "Error reading power (%d) (%d)\n", power_data[i].sensor_source, + power_data[i].sensor_type); + continue; + } + LOGP(DTEMP, LOGL_DEBUG, "Current %s power: %d.%d %s\n", + power_data[i].name, power_cur[i]/div_ratio, power_cur[i]%div_ratio, + get_value_string(power_unit_strs, power_data[i].sensor_type)); + + if (power_cur[i] > power_old[i]) { + LOGP(DTEMP, LOGL_NOTICE, "New maximum %s " + "power: %d.%d %s\n", power_data[i].name, + power_cur[i]/div_ratio, power_cur[i]%div_ratio, + get_value_string(power_unit_strs, power_data[i].sensor_type)); + + if (!no_rom_write) { + rc = lc15bts_par_set_int(tall_mgr_ctx, power_data[i].ee_par, power_cur[i]/div_ratio); + if (rc < 0) + LOGP(DTEMP, LOGL_ERROR, "error writing new %s " + "max power %d (%s)\n", power_data[i].name, + rc, strerror(errno)); + } + } + } +} + +void lc15bts_check_vswr(int no_rom_write) +{ + int vswr_old[ARRAY_SIZE(vswr_data)]; + int vswr_cur[ARRAY_SIZE(vswr_data)]; + int i, rc; + + for (i = 0; i < ARRAY_SIZE(vswr_data); i++) { + int ret; + rc = lc15bts_par_get_int(tall_mgr_ctx, vswr_data[i].ee_par, &ret); + vswr_old[i] = ret * 1000; + + lc15bts_vswr_get(vswr_data[i].sensor, &vswr_cur[i]); + if (vswr_cur[i] < 0 && vswr_cur[i] > -1000) { + LOGP(DTEMP, LOGL_ERROR, "Error reading vswr (%d)\n", vswr_data[i].sensor); + continue; + } + + LOGP(DTEMP, LOGL_DEBUG, "Current %s vswr: %d.%d\n", + vswr_data[i].name, vswr_cur[i]/1000, vswr_cur[i]%1000); + + if (vswr_cur[i] > vswr_old[i]) { + LOGP(DTEMP, LOGL_NOTICE, "New maximum %s " + "vswr: %d.%d C\n", vswr_data[i].name, + vswr_cur[i]/1000, vswr_old[i]%1000); + + if (!no_rom_write) { + rc = lc15bts_par_set_int(tall_mgr_ctx, vswr_data[i].ee_par, vswr_cur[i]/1000); + if (rc < 0) + LOGP(DTEMP, LOGL_ERROR, "error writing new %s " + "max vswr %d (%s)\n", vswr_data[i].name, + rc, strerror(errno)); + } + } + } +} + +/********************************************************************* + * Hours handling + *********************************************************************/ +static time_t last_update; + +int lc15bts_update_hours(int no_rom_write) +{ + time_t now = time(NULL); + int rc, op_hrs; + + /* first time after start of manager program */ + if (last_update == 0) { + last_update = now; + + rc = lc15bts_par_get_int(tall_mgr_ctx, LC15BTS_PAR_HOURS, &op_hrs); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, "Unable to read " + "operational hours: %d (%s)\n", rc, + strerror(errno)); + return rc; + } + + LOGP(DTEMP, LOGL_INFO, "Total hours of Operation: %u\n", + op_hrs); + + return 0; + } + + if (now >= last_update + 3600) { + rc = lc15bts_par_get_int(tall_mgr_ctx, LC15BTS_PAR_HOURS, &op_hrs); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, "Unable to read " + "operational hours: %d (%s)\n", rc, + strerror(errno)); + return rc; + } + + /* number of hours to increase */ + op_hrs += (now-last_update)/3600; + + LOGP(DTEMP, LOGL_INFO, "Total hours of Operation: %u\n", + op_hrs); + + if (!no_rom_write) { + rc = lc15bts_par_set_int(tall_mgr_ctx, LC15BTS_PAR_HOURS, op_hrs); + if (rc < 0) + return rc; + } + + last_update = now; + } + + return 0; +} + +/********************************************************************* + * Firmware reloading + *********************************************************************/ + +static const char *fw_sysfs[_NUM_FW] = { + [LC15BTS_FW_DSP0] = "/sys/kernel/debug/remoteproc/remoteproc0/recovery", + [LC15BTS_FW_DSP1] = "/sys/kernel/debug/remoteproc/remoteproc0/recovery", +}; + +int lc15bts_firmware_reload(enum lc15bts_firmware_type type) +{ + int fd; + int rc; + + switch (type) { + case LC15BTS_FW_DSP0: + case LC15BTS_FW_DSP1: + fd = open(fw_sysfs[type], O_WRONLY); + if (fd < 0) { + LOGP(DFW, LOGL_ERROR, "unable ot open firmware device %s: %s\n", + fw_sysfs[type], strerror(errno)); + close(fd); + return fd; + } + rc = write(fd, "restart", 8); + if (rc < 8) { + LOGP(DFW, LOGL_ERROR, "short write during " + "fw write to %s\n", fw_sysfs[type]); + close(fd); + return -EIO; + } + close(fd); + default: + return -EINVAL; + } + return 0; +} diff --git a/src/osmo-bts-litecell15/misc/lc15bts_misc.h b/src/osmo-bts-litecell15/misc/lc15bts_misc.h new file mode 100644 index 0000000..79e9e68 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_misc.h @@ -0,0 +1,18 @@ +#ifndef _LC15BTS_MISC_H +#define _LC15BTS_MISC_H + +#include + +void lc15bts_check_temp(int no_rom_write); +void lc15bts_check_power(int no_rom_write); +void lc15bts_check_vswr(int no_rom_write); + +int lc15bts_update_hours(int no_rom_write); + +enum lc15bts_firmware_type { + LC15BTS_FW_DSP0, + LC15BTS_FW_DSP1, + _NUM_FW +}; + +#endif diff --git a/src/osmo-bts-litecell15/misc/lc15bts_nl.c b/src/osmo-bts-litecell15/misc/lc15bts_nl.c new file mode 100644 index 0000000..39f64aa --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_nl.c @@ -0,0 +1,123 @@ +/* Helper for netlink */ + +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * sysmobts_nl.c + * (C) 2014 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +#define NLMSG_TAIL(nmsg) \ + ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) + +/** + * In case one binds to 0.0.0.0/INADDR_ANY and wants to know which source + * address will be used when sending a message this function can be used. + * It will ask the routing code of the kernel for the PREFSRC + */ +int source_for_dest(const struct in_addr *dest, struct in_addr *loc_source) +{ + int fd, rc; + struct rtmsg *r; + struct rtattr *rta; + struct { + struct nlmsghdr n; + struct rtmsg r; + char buf[1024]; + } req; + + memset(&req, 0, sizeof(req)); + + fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_ROUTE); + if (fd < 0) { + perror("nl socket"); + return -1; + } + + /* Send a rtmsg and ask for a response */ + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + req.n.nlmsg_type = RTM_GETROUTE; + req.n.nlmsg_seq = 1; + + /* Prepare the routing request */ + req.r.rtm_family = AF_INET; + + /* set the dest */ + rta = NLMSG_TAIL(&req.n); + rta->rta_type = RTA_DST; + rta->rta_len = RTA_LENGTH(sizeof(*dest)); + memcpy(RTA_DATA(rta), dest, sizeof(*dest)); + + /* update sizes for dest */ + req.r.rtm_dst_len = sizeof(*dest) * 8; + req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + RTA_ALIGN(rta->rta_len); + + rc = send(fd, &req, req.n.nlmsg_len, 0); + if (rc != req.n.nlmsg_len) { + perror("short write"); + close(fd); + return -2; + } + + + /* now receive a response and parse it */ + rc = recv(fd, &req, sizeof(req), 0); + if (rc <= 0) { + perror("short read"); + close(fd); + return -3; + } + + if (!NLMSG_OK(&req.n, rc) || req.n.nlmsg_type != RTM_NEWROUTE) { + close(fd); + return -4; + } + + r = NLMSG_DATA(&req.n); + rc -= NLMSG_LENGTH(sizeof(*r)); + rta = RTM_RTA(r); + while (RTA_OK(rta, rc)) { + if (rta->rta_type != RTA_PREFSRC) { + rta = RTA_NEXT(rta, rc); + continue; + } + + /* we are done */ + memcpy(loc_source, RTA_DATA(rta), RTA_PAYLOAD(rta)); + close(fd); + return 0; + } + + close(fd); + return -5; +} diff --git a/src/osmo-bts-litecell15/misc/lc15bts_nl.h b/src/osmo-bts-litecell15/misc/lc15bts_nl.h new file mode 100644 index 0000000..340cf11 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_nl.h @@ -0,0 +1,27 @@ +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * sysmobts_nl.h + * (C) 2014 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 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 . + * + */ +#pragma once + +struct in_addr; + +int source_for_dest(const struct in_addr *dest, struct in_addr *loc_source); diff --git a/src/osmo-bts-litecell15/misc/lc15bts_par.c b/src/osmo-bts-litecell15/misc/lc15bts_par.c new file mode 100644 index 0000000..75314a4 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_par.c @@ -0,0 +1,232 @@ +/* lc15bts - access to hardware related parameters */ + +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * sysmobts_par.c + * (C) 2012 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "lc15bts_par.h" + +const struct value_string lc15bts_par_names[_NUM_LC15BTS_PAR+1] = { + { LC15BTS_PAR_TEMP_SUPPLY_MAX, "temp-supply-max" }, + { LC15BTS_PAR_TEMP_SOC_MAX, "temp-soc-max" }, + { LC15BTS_PAR_TEMP_FPGA_MAX, "temp-fpga-max" }, + { LC15BTS_PAR_TEMP_RMSDET_MAX, "temp-rmsdet-max" }, + { LC15BTS_PAR_TEMP_OCXO_MAX, "temp-ocxo-max" }, + { LC15BTS_PAR_TEMP_TX0_MAX, "temp-tx0-max" }, + { LC15BTS_PAR_TEMP_TX1_MAX, "temp-tx1-max" }, + { LC15BTS_PAR_TEMP_PA0_MAX, "temp-pa0-max" }, + { LC15BTS_PAR_TEMP_PA1_MAX, "temp-pa1-max" }, + { LC15BTS_PAR_VOLT_SUPPLY_MAX, "volt-supply-max" }, + { LC15BTS_PAR_PWR_SUPPLY_MAX, "pwr-supply-max" }, + { LC15BTS_PAR_PWR_PA0_MAX, "pwr-pa0-max" }, + { LC15BTS_PAR_PWR_PA1_MAX, "pwr-pa1-max" }, + { LC15BTS_PAR_VSWR_TX0_MAX, "vswr-tx0-max" }, + { LC15BTS_PAR_VSWR_TX1_MAX, "vswr-tx1-max" }, + { LC15BTS_PAR_GPS_FIX, "gps-fix" }, + { LC15BTS_PAR_SERNR, "serial-nr" }, + { LC15BTS_PAR_HOURS, "hours-running" }, + { LC15BTS_PAR_BOOTS, "boot-count" }, + { LC15BTS_PAR_KEY, "key" }, + { 0, NULL } +}; + +int lc15bts_par_is_int(enum lc15bts_par par) +{ + switch (par) { + case LC15BTS_PAR_TEMP_SUPPLY_MAX: + case LC15BTS_PAR_TEMP_SOC_MAX: + case LC15BTS_PAR_TEMP_FPGA_MAX: + case LC15BTS_PAR_TEMP_RMSDET_MAX: + case LC15BTS_PAR_TEMP_OCXO_MAX: + case LC15BTS_PAR_TEMP_TX0_MAX: + case LC15BTS_PAR_TEMP_TX1_MAX: + case LC15BTS_PAR_TEMP_PA0_MAX: + case LC15BTS_PAR_TEMP_PA1_MAX: + case LC15BTS_PAR_VOLT_SUPPLY_MAX: + case LC15BTS_PAR_VSWR_TX0_MAX: + case LC15BTS_PAR_VSWR_TX1_MAX: + case LC15BTS_PAR_SERNR: + case LC15BTS_PAR_HOURS: + case LC15BTS_PAR_BOOTS: + case LC15BTS_PAR_PWR_SUPPLY_MAX: + case LC15BTS_PAR_PWR_PA0_MAX: + case LC15BTS_PAR_PWR_PA1_MAX: + return 1; + default: + return 0; + } +} + +FILE *lc15bts_par_get_path(void *ctx, enum lc15bts_par par, const char* mode) +{ + char *fpath; + FILE *fp; + + if (par >= _NUM_LC15BTS_PAR) + return NULL; + + fpath = talloc_asprintf(ctx, "%s/%s", USER_ROM_PATH, get_value_string(lc15bts_par_names, par)); + if (!fpath) + return NULL; + + fp = fopen(fpath, mode); + if (!fp) + fprintf(stderr, "Failed to open %s due to '%s' error\n", fpath, strerror(errno)); + + talloc_free(fpath); + + return fp; +} + +int lc15bts_par_get_int(void *ctx, enum lc15bts_par par, int *ret) +{ + FILE *fp = lc15bts_par_get_path(ctx, par, "r"); + int rc; + + if (fp == NULL) { + return -errno; + } + + rc = fscanf(fp, "%d", ret); + if (rc != 1) { + fclose(fp); + return -EIO; + } + fclose(fp); + return 0; +} + +int lc15bts_par_set_int(void *ctx, enum lc15bts_par par, int val) +{ + FILE *fp = lc15bts_par_get_path(ctx, par, "w"); + int rc; + + if (fp == NULL) { + return -errno; + } + + rc = fprintf(fp, "%d", val); + if (rc < 0) { + fclose(fp); + return -EIO; + } + + fsync(fp); + fclose(fp); + return 0; +} + +int lc15bts_par_get_buf(void *ctx, enum lc15bts_par par, uint8_t *buf, unsigned int size) +{ + FILE *fp = lc15bts_par_get_path(ctx, par, "rb"); + int rc; + + if (fp == NULL) { + return -errno; + } + + rc = fread(buf, 1, size, fp); + + fclose(fp); + + return rc; +} + +int lc15bts_par_set_buf(void *ctx, enum lc15bts_par par, const uint8_t *buf, unsigned int size) +{ + FILE *fp = lc15bts_par_get_path(ctx, par, "wb"); + int rc; + + if (fp == NULL) { + return -errno; + } + + rc = fwrite(buf, 1, size, fp); + + fsync(fp); + fclose(fp); + + return rc; +} + +int lc15bts_par_get_gps_fix(time_t *ret) +{ + char fpath[PATH_MAX]; + FILE *fp; + int rc; + + snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(lc15bts_par_names, LC15BTS_PAR_GPS_FIX)); + fpath[sizeof(fpath)-1] = '\0'; + + fp = fopen(fpath, "r"); + if (fp == NULL) { + return -errno; + } + + rc = fscanf(fp, "%lld", (long long *)ret); + if (rc != 1) { + fclose(fp); + return -EIO; + } + fclose(fp); + + return 0; +} + +int lc15bts_par_set_gps_fix(time_t val) +{ + char fpath[PATH_MAX]; + FILE *fp; + int rc; + + snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(lc15bts_par_names, LC15BTS_PAR_GPS_FIX)); + fpath[sizeof(fpath)-1] = '\0'; + + fp = fopen(fpath, "w"); + if (fp == NULL) { + return -errno; + } + + rc = fprintf(fp, "%lld", (long long)val); + if (rc < 0) { + fclose(fp); + return -EIO; + } + fsync(fp); + fclose(fp); + + return 0; +} diff --git a/src/osmo-bts-litecell15/misc/lc15bts_par.h b/src/osmo-bts-litecell15/misc/lc15bts_par.h new file mode 100644 index 0000000..217ae5f --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_par.h @@ -0,0 +1,44 @@ +#ifndef _LC15BTS_PAR_H +#define _LC15BTS_PAR_H + +#include + +#define FACTORY_ROM_PATH "/mnt/rom/factory" +#define USER_ROM_PATH "/mnt/storage/var/run/lc15bts-mgr" + +enum lc15bts_par { + LC15BTS_PAR_TEMP_SUPPLY_MAX, + LC15BTS_PAR_TEMP_SOC_MAX, + LC15BTS_PAR_TEMP_FPGA_MAX, + LC15BTS_PAR_TEMP_RMSDET_MAX, + LC15BTS_PAR_TEMP_OCXO_MAX, + LC15BTS_PAR_TEMP_TX0_MAX, + LC15BTS_PAR_TEMP_TX1_MAX, + LC15BTS_PAR_TEMP_PA0_MAX, + LC15BTS_PAR_TEMP_PA1_MAX, + LC15BTS_PAR_VOLT_SUPPLY_MAX, + LC15BTS_PAR_PWR_SUPPLY_MAX, + LC15BTS_PAR_PWR_PA0_MAX, + LC15BTS_PAR_PWR_PA1_MAX, + LC15BTS_PAR_VSWR_TX0_MAX, + LC15BTS_PAR_VSWR_TX1_MAX, + LC15BTS_PAR_GPS_FIX, + LC15BTS_PAR_SERNR, + LC15BTS_PAR_HOURS, + LC15BTS_PAR_BOOTS, + LC15BTS_PAR_KEY, + _NUM_LC15BTS_PAR +}; + +extern const struct value_string lc15bts_par_names[_NUM_LC15BTS_PAR+1]; + +int lc15bts_par_get_int(void *ctx, enum lc15bts_par par, int *ret); +int lc15bts_par_set_int(void *ctx, enum lc15bts_par par, int val); +int lc15bts_par_get_buf(void *ctx, enum lc15bts_par par, uint8_t *buf, unsigned int size); +int lc15bts_par_set_buf(void *ctx, enum lc15bts_par par, const uint8_t *buf, unsigned int size); + +int lc15bts_par_is_int(enum lc15bts_par par); +int lc15bts_par_get_gps_fix(time_t *ret); +int lc15bts_par_set_gps_fix(time_t val); + +#endif diff --git a/src/osmo-bts-litecell15/misc/lc15bts_power.c b/src/osmo-bts-litecell15/misc/lc15bts_power.c new file mode 100644 index 0000000..1a37d8e --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_power.c @@ -0,0 +1,210 @@ +/* Copyright (C) 2015 by Yves Godin + * + * 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 . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lc15bts_power.h" + +#define LC15BTS_PA_VOLTAGE 24000000 + +#define PA_SUPPLY_MIN_SYSFS "/var/lc15/pa-supply/min_microvolts" +#define PA_SUPPLY_MAX_SYSFS "/var/lc15/pa-supply/max_microvolts" + +static const char *power_enable_devs[_NUM_POWER_SOURCES] = { + [LC15BTS_POWER_PA0] = "/var/lc15/pa-state/pa0/state", + [LC15BTS_POWER_PA1] = "/var/lc15/pa-state/pa1/state", +}; + +static const char *power_sensor_devs[_NUM_POWER_SOURCES] = { + [LC15BTS_POWER_SUPPLY] = "/var/lc15/pwr-sense/main-supply/", + [LC15BTS_POWER_PA0] = "/var/lc15/pwr-sense/pa0/", + [LC15BTS_POWER_PA1] = "/var/lc15/pwr-sense/pa1/", +}; + +static const char *power_sensor_type_str[_NUM_POWER_TYPES] = { + [LC15BTS_POWER_POWER] = "power", + [LC15BTS_POWER_VOLTAGE] = "voltage", + [LC15BTS_POWER_CURRENT] = "current", +}; + +int lc15bts_power_sensor_get( + enum lc15bts_power_source source, + enum lc15bts_power_type type, + int *power) +{ + char buf[PATH_MAX]; + char pwrstr[10]; + int fd, rc; + + if (source >= _NUM_POWER_SOURCES) + return -EINVAL; + + if (type >= _NUM_POWER_TYPES) + return -EINVAL; + + snprintf(buf, sizeof(buf)-1, "%s%s", power_sensor_devs[source], power_sensor_type_str[type]); + buf[sizeof(buf)-1] = '\0'; + + fd = open(buf, O_RDONLY); + if (fd < 0) + return fd; + + rc = read(fd, pwrstr, sizeof(pwrstr)); + pwrstr[sizeof(pwrstr)-1] = '\0'; + if (rc < 0) { + close(fd); + return rc; + } + if (rc == 0) { + close(fd); + return -EIO; + } + close(fd); + *power = atoi(pwrstr); + return 0; +} + + +int lc15bts_power_set( + enum lc15bts_power_source source, + int en) +{ + int fd; + int rc; + + if ((source != LC15BTS_POWER_PA0) + && (source != LC15BTS_POWER_PA1) ) { + return -EINVAL; + } + + fd = open(PA_SUPPLY_MAX_SYSFS, O_WRONLY); + if (fd < 0) { + return fd; + } + rc = write(fd, "32000000", 9); + close( fd ); + + if (rc != 9) { + return -1; + } + + fd = open(PA_SUPPLY_MIN_SYSFS, O_WRONLY); + if (fd < 0) { + return fd; + } + + /* TODO NTQ: Make the voltage configurable */ + rc = write(fd, "24000000", 9); + close( fd ); + + if (rc != 9) { + return -1; + } + + fd = open(power_enable_devs[source], O_WRONLY); + if (fd < 0) { + return fd; + } + rc = write(fd, en?"1":"0", 2); + close( fd ); + + if (rc != 2) { + return -1; + } + + if (en) usleep(50*1000); + + return 0; +} + +int lc15bts_power_get( + enum lc15bts_power_source source) +{ + int fd; + int rc; + int retVal = 0; + char enstr[10]; + + fd = open(power_enable_devs[source], O_RDONLY); + if (fd < 0) { + return fd; + } + + rc = read(fd, enstr, sizeof(enstr)); + enstr[rc-1] = '\0'; + + close(fd); + + if (rc < 0) { + return rc; + } + if (rc == 0) { + return -EIO; + } + + rc = strcmp(enstr, "enabled"); + if(rc == 0) { + retVal = 1; + } + + return retVal; +} + +static const char *vswr_devs[_NUM_VSWR_SENSORS] = { + [LC15BTS_VSWR_TX0] = "/var/lc15/vswr/tx0/vswr", + [LC15BTS_VSWR_TX1] = "/var/lc15/vswr/tx1/vswr", +}; + +int lc15bts_vswr_get(enum lc15bts_vswr_sensor sensor, int *vswr) +{ + char buf[PATH_MAX]; + char vswrstr[8]; + int fd, rc; + + if (sensor < 0 || sensor >= _NUM_VSWR_SENSORS) + return -EINVAL; + + snprintf(buf, sizeof(buf)-1, "%s", vswr_devs[sensor]); + buf[sizeof(buf)-1] = '\0'; + + fd = open(buf, O_RDONLY); + if (fd < 0) + return fd; + + rc = read(fd, vswrstr, sizeof(vswrstr)); + vswrstr[sizeof(vswrstr)-1] = '\0'; + if (rc < 0) { + close(fd); + return rc; + } + if (rc == 0) { + close(fd); + return -EIO; + } + close(fd); + *vswr = atoi(vswrstr); + return 0; +} diff --git a/src/osmo-bts-litecell15/misc/lc15bts_power.h b/src/osmo-bts-litecell15/misc/lc15bts_power.h new file mode 100644 index 0000000..b48cfdc --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_power.h @@ -0,0 +1,38 @@ +#ifndef _LC15BTS_POWER_H +#define _LC15BTS_POWER_H + +enum lc15bts_power_source { + LC15BTS_POWER_SUPPLY, + LC15BTS_POWER_PA0, + LC15BTS_POWER_PA1, + _NUM_POWER_SOURCES +}; + +enum lc15bts_power_type { + LC15BTS_POWER_POWER, + LC15BTS_POWER_VOLTAGE, + LC15BTS_POWER_CURRENT, + _NUM_POWER_TYPES +}; + +int lc15bts_power_sensor_get( + enum lc15bts_power_source source, + enum lc15bts_power_type type, + int *volt); + +int lc15bts_power_set( + enum lc15bts_power_source source, + int en); + +int lc15bts_power_get( + enum lc15bts_power_source source); + +enum lc15bts_vswr_sensor { + LC15BTS_VSWR_TX0, + LC15BTS_VSWR_TX1, + _NUM_VSWR_SENSORS +}; + +int lc15bts_vswr_get(enum lc15bts_vswr_sensor sensor, int *vswr); + +#endif diff --git a/src/osmo-bts-litecell15/misc/lc15bts_swd.c b/src/osmo-bts-litecell15/misc/lc15bts_swd.c new file mode 100644 index 0000000..59c7b61 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_swd.c @@ -0,0 +1,178 @@ +/* Systemd service wd notification for Litecell 1.5 BTS management daemon */ + +/* Copyright (C) 2015 by Yves Godin + * + * 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 . + * + */ + +#include "misc/lc15bts_mgr.h" +#include "misc/lc15bts_swd.h" +#include + +/* Needed for service watchdog notification */ +#include + +/* This is the period used to verify if all events have been registered to be allowed + to notify the systemd service watchdog +*/ +#define SWD_PERIOD 30 + +static void swd_start(struct lc15bts_mgr_instance *mgr); +static void swd_process(struct lc15bts_mgr_instance *mgr); +static void swd_close(struct lc15bts_mgr_instance *mgr); +static void swd_state_reset(struct lc15bts_mgr_instance *mgr, int reason); +static int swd_run(struct lc15bts_mgr_instance *mgr, int from_loop); +static void swd_loop_run(void *_data); + +enum swd_state { + SWD_INITIAL, + SWD_IN_PROGRESS, +}; + +enum swd_result { + SWD_FAIL_START, + SWD_FAIL_NOTIFY, + SWD_SUCCESS, +}; + +static void swd_start(struct lc15bts_mgr_instance *mgr) +{ + swd_process(mgr); +} + +static void swd_process(struct lc15bts_mgr_instance *mgr) +{ + int rc = 0, notify = 0; + + /* Did we get all needed conditions ? */ + if (mgr->swd.swd_eventmasks == mgr->swd.swd_events) { + /* Ping systemd service wd if enabled */ + rc = sd_notify(0, "WATCHDOG=1"); + LOGP(DSWD, LOGL_NOTICE, "Watchdog notification attempt\n"); + notify = 1; + } + else { + LOGP(DSWD, LOGL_NOTICE, "Missing watchdog events: e:0x%016llx,m:0x%016llx\n",mgr->swd.swd_events,mgr->swd.swd_eventmasks); + } + + if (rc < 0) { + LOGP(DSWD, LOGL_ERROR, + "Failed to notify system service watchdog: %d\n", rc); + swd_state_reset(mgr, SWD_FAIL_NOTIFY); + return; + } + else { + /* Did we notified the watchdog? */ + if (notify) { + mgr->swd.swd_events = 0; + /* Makes sure we really cleared it in case any event was notified at this same moment (it would be lost) */ + if (mgr->swd.swd_events != 0) + mgr->swd.swd_events = 0; + } + } + + swd_state_reset(mgr, SWD_SUCCESS); + return; +} + +static void swd_close(struct lc15bts_mgr_instance *mgr) +{ +} + +static void swd_state_reset(struct lc15bts_mgr_instance *mgr, int outcome) +{ + if (mgr->swd.swd_from_loop) { + mgr->swd.swd_timeout.data = mgr; + mgr->swd.swd_timeout.cb = swd_loop_run; + osmo_timer_schedule(&mgr->swd.swd_timeout, SWD_PERIOD, 0); + } + + mgr->swd.state = SWD_INITIAL; + swd_close(mgr); +} + +static int swd_run(struct lc15bts_mgr_instance *mgr, int from_loop) +{ + if (mgr->swd.state != SWD_INITIAL) { + LOGP(DSWD, LOGL_ERROR, "Swd is already in progress.\n"); + return -1; + } + + mgr->swd.swd_from_loop = from_loop; + + /* From now on everything will be handled from the failure */ + mgr->swd.state = SWD_IN_PROGRESS; + swd_start(mgr); + return 0; +} + +static void swd_loop_run(void *_data) +{ + int rc; + struct lc15bts_mgr_instance *mgr = _data; + + LOGP(DSWD, LOGL_NOTICE, "Going to check for watchdog notification.\n"); + rc = swd_run(mgr, 1); + if (rc != 0) { + swd_state_reset(mgr, SWD_FAIL_START); + } +} + +/* 'swd_num_events' configures the number of events to be monitored before notifying the + systemd service watchdog. It must be in the range of [1,64]. Events are notified + through the function 'lc15bts_swd_event' +*/ +int lc15bts_swd_init(struct lc15bts_mgr_instance *mgr, int swd_num_events) +{ + /* Checks for a valid number of events to validate */ + if (swd_num_events < 1 || swd_num_events > 64) + return(-1); + + mgr->swd.state = SWD_INITIAL; + mgr->swd.swd_timeout.data = mgr; + mgr->swd.swd_timeout.cb = swd_loop_run; + osmo_timer_schedule(&mgr->swd.swd_timeout, 0, 0); + + if (swd_num_events == 64){ + mgr->swd.swd_eventmasks = 0xffffffffffffffffULL; + } + else { + mgr->swd.swd_eventmasks = ((1ULL << swd_num_events) - 1); + } + mgr->swd.swd_events = 0; + mgr->swd.num_events = swd_num_events; + + return 0; +} + +/* Notifies that the specified event 'swd_event' happened correctly; + the value must be in the range of [0,'swd_num_events'[ (see lc15bts_swd_init). + For example, if 'swd_num_events' was 64, 'swd_event' events are numbered 0 to 63. + WARNING: if this function can be used from multiple threads at the same time, + it must be protected with a kind of mutex to avoid loosing event notification. +*/ +int lc15bts_swd_event(struct lc15bts_mgr_instance *mgr, enum mgr_swd_events swd_event) +{ + /* Checks for a valid specified event (smaller than max possible) */ + if ((int)(swd_event) < 0 || (int)(swd_event) >= mgr->swd.num_events) + return(-1); + + mgr->swd.swd_events = mgr->swd.swd_events | ((unsigned long long int)(1) << (int)(swd_event)); + + /* !!! Uncomment following line to debug events notification */ + LOGP(DSWD, LOGL_DEBUG,"Swd event notified: %d\n", (int)(swd_event)); + + return 0; +} diff --git a/src/osmo-bts-litecell15/misc/lc15bts_swd.h b/src/osmo-bts-litecell15/misc/lc15bts_swd.h new file mode 100644 index 0000000..b78a2c2 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_swd.h @@ -0,0 +1,7 @@ +#ifndef _LC15BTS_SWD_H +#define _LC15BTS_SWD_H + +int lc15bts_swd_init(struct lc15bts_mgr_instance *mgr, int swd_num_events); +int lc15bts_swd_event(struct lc15bts_mgr_instance *mgr, enum mgr_swd_events swd_event); + +#endif diff --git a/src/osmo-bts-litecell15/misc/lc15bts_temp.c b/src/osmo-bts-litecell15/misc/lc15bts_temp.c new file mode 100644 index 0000000..45602dc --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_temp.c @@ -0,0 +1,74 @@ +/* Copyright (C) 2015 by Yves Godin + * + * 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 . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "lc15bts_temp.h" + +static const char *temp_devs[_NUM_TEMP_SENSORS] = { + [LC15BTS_TEMP_SUPPLY] = "/var/lc15/temp/main-supply/temp", + [LC15BTS_TEMP_SOC] = "/var/lc15/temp/cpu/temp", + [LC15BTS_TEMP_FPGA] = "/var/lc15/temp/fpga/temp", + [LC15BTS_TEMP_RMSDET] = "/var/lc15/temp/rmsdet/temp", + [LC15BTS_TEMP_OCXO] = "/var/lc15/temp/ocxo/temp", + [LC15BTS_TEMP_TX0] = "/var/lc15/temp/tx0/temp", + [LC15BTS_TEMP_TX1] = "/var/lc15/temp/tx1/temp", + [LC15BTS_TEMP_PA0] = "/var/lc15/temp/pa0/temp", + [LC15BTS_TEMP_PA1] = "/var/lc15/temp/pa1/temp", +}; + +int lc15bts_temp_get(enum lc15bts_temp_sensor sensor, int *temp) +{ + char buf[PATH_MAX]; + char tempstr[8]; + int fd, rc; + + if (sensor < 0 || sensor >= _NUM_TEMP_SENSORS) + return -EINVAL; + + snprintf(buf, sizeof(buf)-1, "%s", temp_devs[sensor]); + buf[sizeof(buf)-1] = '\0'; + + fd = open(buf, O_RDONLY); + if (fd < 0) + return fd; + + rc = read(fd, tempstr, sizeof(tempstr)); + tempstr[sizeof(tempstr)-1] = '\0'; + if (rc < 0) { + close(fd); + return rc; + } + if (rc == 0) { + close(fd); + return -EIO; + } + close(fd); + *temp = atoi(tempstr); + return 0; +} + diff --git a/src/osmo-bts-litecell15/misc/lc15bts_temp.h b/src/osmo-bts-litecell15/misc/lc15bts_temp.h new file mode 100644 index 0000000..35d81f1 --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_temp.h @@ -0,0 +1,28 @@ +#ifndef _LC15BTS_TEMP_H +#define _LC15BTS_TEMP_H + +enum lc15bts_temp_sensor { + LC15BTS_TEMP_SUPPLY, + LC15BTS_TEMP_SOC, + LC15BTS_TEMP_FPGA, + LC15BTS_TEMP_RMSDET, + LC15BTS_TEMP_OCXO, + LC15BTS_TEMP_TX0, + LC15BTS_TEMP_TX1, + LC15BTS_TEMP_PA0, + LC15BTS_TEMP_PA1, + _NUM_TEMP_SENSORS +}; + +enum lc15bts_temp_type { + LC15BTS_TEMP_INPUT, + LC15BTS_TEMP_LOWEST, + LC15BTS_TEMP_HIGHEST, + LC15BTS_TEMP_FAULT, + _NUM_TEMP_TYPES +}; + +int lc15bts_temp_get(enum lc15bts_temp_sensor sensor, int *temp); + + +#endif diff --git a/src/osmo-bts-litecell15/misc/lc15bts_util.c b/src/osmo-bts-litecell15/misc/lc15bts_util.c new file mode 100644 index 0000000..430ce0f --- /dev/null +++ b/src/osmo-bts-litecell15/misc/lc15bts_util.c @@ -0,0 +1,164 @@ +/* lc15bts-util - access to hardware related parameters */ + +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * sysmobts_misc.c + * (C) 2012-2013 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lc15bts_par.h" + +void *tall_util_ctx; + +enum act { + ACT_GET, + ACT_SET, +}; + +static enum act action; +static char *write_arg; +static int void_warranty; + +static void print_help() +{ + const struct value_string *par = lc15bts_par_names; + + printf("lc15bts-util [--void-warranty -r | -w value] param_name\n"); + printf("Possible param names:\n"); + + for (; par->str != NULL; par += 1) { + if (!lc15bts_par_is_int(par->value)) + continue; + printf(" %s\n", par->str); + } +} + +static int parse_options(int argc, char **argv) +{ + while (1) { + int option_idx = 0, c; + static const struct option long_options[] = { + { "help", 0, 0, 'h' }, + { "read", 0, 0, 'r' }, + { "void-warranty", 0, 0, 1000}, + { "write", 1, 0, 'w' }, + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "rw:h", + long_options, &option_idx); + if (c == -1) + break; + switch (c) { + case 'r': + action = ACT_GET; + break; + case 'w': + action = ACT_SET; + write_arg = optarg; + break; + case 'h': + print_help(); + return -1; + break; + case 1000: + printf("Will void warranty on write.\n"); + void_warranty = 1; + break; + default: + return -1; + } + } + + return 0; +} + +int main(int argc, char **argv) +{ + const char *parname; + enum lc15bts_par par; + int rc, val; + + tall_util_ctx = talloc_named_const(NULL, 1, "lc15 utils"); + msgb_talloc_ctx_init(tall_util_ctx, 0); + + rc = parse_options(argc, argv); + if (rc < 0) + exit(2); + + if (optind >= argc) { + fprintf(stderr, "You must specify the parameter name\n"); + exit(2); + } + parname = argv[optind]; + + rc = get_string_value(lc15bts_par_names, parname); + if (rc < 0) { + fprintf(stderr, "`%s' is not a valid parameter\n", parname); + exit(2); + } else + par = rc; + + switch (action) { + case ACT_GET: + rc = lc15bts_par_get_int(tall_util_ctx, par, &val); + if (rc < 0) { + fprintf(stderr, "Error %d\n", rc); + goto err; + } + printf("%d\n", val); + break; + case ACT_SET: + rc = lc15bts_par_get_int(tall_util_ctx, par, &val); + if (rc < 0) { + fprintf(stderr, "Error %d\n", rc); + goto err; + } + if (val != 0xFFFF && val != 0xFF && val != 0xFFFFFFFF && !void_warranty) { + fprintf(stderr, "Parameter is already set!\r\n"); + goto err; + } + rc = lc15bts_par_set_int(tall_util_ctx, par, atoi(write_arg)); + if (rc < 0) { + fprintf(stderr, "Error %d\n", rc); + goto err; + } + printf("Success setting %s=%d\n", parname, + atoi(write_arg)); + break; + default: + fprintf(stderr, "Unsupported action\n"); + goto err; + } + + exit(0); + +err: + exit(1); +} + diff --git a/src/osmo-bts-litecell15/oml.c b/src/osmo-bts-litecell15/oml.c new file mode 100644 index 0000000..2c3002b --- /dev/null +++ b/src/osmo-bts-litecell15/oml.c @@ -0,0 +1,1937 @@ +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * (C) 2011 by Harald Welte + * (C) 2013-2014 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "l1_if.h" +#include "lc15bts.h" +#include "utils.h" + +static int mph_info_chan_confirm(struct gsm_lchan *lchan, + enum osmo_mph_info_type type, uint8_t cause) +{ + struct osmo_phsap_prim l1sap; + + memset(&l1sap, 0, sizeof(l1sap)); + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_CONFIRM, + NULL); + l1sap.u.info.type = type; + l1sap.u.info.u.act_cnf.chan_nr = gsm_lchan2chan_nr(lchan); + l1sap.u.info.u.act_cnf.cause = cause; + + return l1sap_up(lchan->ts->trx, &l1sap); +} + +enum sapi_cmd_type { + SAPI_CMD_ACTIVATE, + SAPI_CMD_CONFIG_CIPHERING, + SAPI_CMD_CONFIG_LOGCH_PARAM, + SAPI_CMD_SACCH_REL_MARKER, + SAPI_CMD_REL_MARKER, + SAPI_CMD_DEACTIVATE, +}; + +struct sapi_cmd { + struct llist_head entry; + GsmL1_Sapi_t sapi; + GsmL1_Dir_t dir; + enum sapi_cmd_type type; + int (*callback)(struct gsm_lchan *lchan, int status); +}; + +static const enum GsmL1_LogChComb_t pchan_to_logChComb[_GSM_PCHAN_MAX] = { + [GSM_PCHAN_NONE] = GsmL1_LogChComb_0, + [GSM_PCHAN_CCCH] = GsmL1_LogChComb_IV, + [GSM_PCHAN_CCCH_SDCCH4] = GsmL1_LogChComb_V, + [GSM_PCHAN_CCCH_SDCCH4_CBCH] = GsmL1_LogChComb_V, + [GSM_PCHAN_TCH_F] = GsmL1_LogChComb_I, + [GSM_PCHAN_TCH_H] = GsmL1_LogChComb_II, + [GSM_PCHAN_SDCCH8_SACCH8C] = GsmL1_LogChComb_VII, + [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = GsmL1_LogChComb_VII, + [GSM_PCHAN_PDCH] = GsmL1_LogChComb_XIII, + [GSM_PCHAN_UNKNOWN] = GsmL1_LogChComb_0, + /* + * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_TCH_F_TCH_H_PDCH should not be + * part of this, only "real" pchan values will be looked up here. + * See the callers of ts_connect_as(). + */ +}; + +static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb); + +static void *prim_init(GsmL1_Prim_t *prim, GsmL1_PrimId_t id, struct lc15l1_hdl *gl1, + uint32_t hLayer3_uint32) +{ + HANDLE hLayer3; + prim->id = id; + + osmo_static_assert(sizeof(HANDLE) >= 4, l1p_handle_is_at_least_32bit); + hLayer3 = (void*)hLayer3_uint32; + + switch (id) { + case GsmL1_PrimId_MphInitReq: + //prim->u.mphInitReq.hLayer1 = (HANDLE)gl1->hLayer1; + prim->u.mphInitReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphCloseReq: + prim->u.mphCloseReq.hLayer1 = (HANDLE)gl1->hLayer1; + prim->u.mphCloseReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConnectReq: + prim->u.mphConnectReq.hLayer1 = (HANDLE)gl1->hLayer1; + prim->u.mphConnectReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDisconnectReq: + prim->u.mphDisconnectReq.hLayer1 = (HANDLE)gl1->hLayer1; + prim->u.mphDisconnectReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphActivateReq: + prim->u.mphActivateReq.hLayer1 = (HANDLE)gl1->hLayer1; + prim->u.mphActivateReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDeactivateReq: + prim->u.mphDeactivateReq.hLayer1 = (HANDLE)gl1->hLayer1; + prim->u.mphDeactivateReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConfigReq: + prim->u.mphConfigReq.hLayer1 = (HANDLE)gl1->hLayer1; + prim->u.mphConfigReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphMeasureReq: + prim->u.mphMeasureReq.hLayer1 = (HANDLE)gl1->hLayer1; + prim->u.mphMeasureReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphInitCnf: + prim->u.mphInitCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphCloseCnf: + prim->u.mphCloseCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConnectCnf: + prim->u.mphConnectCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDisconnectCnf: + prim->u.mphDisconnectCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphActivateCnf: + prim->u.mphActivateCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDeactivateCnf: + prim->u.mphDeactivateCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConfigCnf: + prim->u.mphConfigCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphMeasureCnf: + prim->u.mphMeasureCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphTimeInd: + break; + case GsmL1_PrimId_MphSyncInd: + break; + case GsmL1_PrimId_PhEmptyFrameReq: + prim->u.phEmptyFrameReq.hLayer1 = (HANDLE)gl1->hLayer1; + break; + case GsmL1_PrimId_PhDataReq: + prim->u.phDataReq.hLayer1 = (HANDLE)gl1->hLayer1; + break; + case GsmL1_PrimId_PhConnectInd: + break; + case GsmL1_PrimId_PhReadyToSendInd: + break; + case GsmL1_PrimId_PhDataInd: + break; + case GsmL1_PrimId_PhRaInd: + break; + default: + LOGP(DL1C, LOGL_ERROR, "unknown L1 primitive %u\n", id); + break; + } + return &prim->u; +} + +static uint32_t l1p_handle_for_trx(struct gsm_bts_trx *trx) +{ + struct gsm_bts *bts = trx->bts; + + osmo_static_assert(sizeof(trx->nr) == 1, trx_nr_is_8bit); + osmo_static_assert(sizeof(bts->nr) == 1, bts_nr_is_8bit); + + return bts->nr << 24 + | trx->nr << 16; +} + +static uint32_t l1p_handle_for_ts(struct gsm_bts_trx_ts *ts) +{ + osmo_static_assert(sizeof(ts->nr) == 1, ts_nr_is_8bit); + + return l1p_handle_for_trx(ts->trx) + | ts->nr << 8; +} + + +static uint32_t l1p_handle_for_lchan(struct gsm_lchan *lchan) +{ + osmo_static_assert(sizeof(lchan->nr) == 1, lchan_nr_is_8bit); + + return l1p_handle_for_ts(lchan->ts) + | lchan->nr; +} + +GsmL1_Status_t prim_status(GsmL1_Prim_t *prim) +{ + switch (prim->id) { + case GsmL1_PrimId_MphInitCnf: + return prim->u.mphInitCnf.status; + case GsmL1_PrimId_MphCloseCnf: + return prim->u.mphCloseCnf.status; + case GsmL1_PrimId_MphConnectCnf: + return prim->u.mphConnectCnf.status; + case GsmL1_PrimId_MphDisconnectCnf: + return prim->u.mphDisconnectCnf.status; + case GsmL1_PrimId_MphActivateCnf: + return prim->u.mphActivateCnf.status; + case GsmL1_PrimId_MphDeactivateCnf: + return prim->u.mphDeactivateCnf.status; + case GsmL1_PrimId_MphConfigCnf: + return prim->u.mphConfigCnf.status; + case GsmL1_PrimId_MphMeasureCnf: + return prim->u.mphMeasureCnf.status; + default: + break; + } + return GsmL1_Status_Success; +} + +#if 0 +static int compl_cb_send_oml_msg(struct msgb *l1_msg, void *data) +{ + struct msgb *resp_msg = data; + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + + if (prim_status(l1p) != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_ERROR, "Rx %s, status: %s\n", + get_value_string(lc15bts_l1prim_names, l1p->id), + get_value_string(lc15bts_l1status_names, cc->status)); + return 0; + } + + msgb_free(l1_msg); + + return abis_nm_sendmsg(msg); +} +#endif + +int lchan_activate(struct gsm_lchan *lchan); + +static int opstart_compl(struct gsm_abis_mo *mo, struct msgb *l1_msg) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_Status_t status = prim_status(l1p); + + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_ERROR, "Rx %s, status: %s\n", + get_value_string(lc15bts_l1prim_names, l1p->id), + get_value_string(lc15bts_l1status_names, status)); + msgb_free(l1_msg); + return oml_mo_opstart_nack(mo, NM_NACK_CANT_PERFORM); + } + + msgb_free(l1_msg); + + /* Set to Operational State: Enabled */ + oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); + + /* ugly hack to auto-activate all SAPIs for the BCCH/CCCH on TS0 */ + if (mo->obj_class == NM_OC_CHANNEL && mo->obj_inst.trx_nr == 0 && + mo->obj_inst.ts_nr == 0) { + struct gsm_lchan *cbch = gsm_bts_get_cbch(mo->bts); + DEBUGP(DL1C, "====> trying to activate lchans of BCCH\n"); + mo->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind = + LCHAN_REL_ACT_OML; + lchan_activate(&mo->bts->c0->ts[0].lchan[CCCH_LCHAN]); + if (cbch) { + cbch->rel_act_kind = LCHAN_REL_ACT_OML; + lchan_activate(cbch); + } + } + + /* Send OPSTART ack */ + return oml_mo_opstart_ack(mo); +} + +static int opstart_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + struct gsm_abis_mo *mo; + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphConnectCnf_t *cnf = &l1p->u.mphConnectCnf; + + mo = &trx->ts[cnf->u8Tn].mo; + return opstart_compl(mo, l1_msg); +} + +static int trx_mute_on_init_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + Litecell15_Prim_t *sysp = msgb_sysprim(resp); + GsmL1_Status_t status; + + status = sysp->u.muteRfCnf.status; + + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_FATAL, "Rx RF-MUTE.conf status=%s\n", + get_value_string(lc15bts_l1status_names, status)); + bts_shutdown(trx->bts, "RF-MUTE failure"); + } + + msgb_free(resp); + + return 0; +} + +static int trx_init_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphInitCnf_t *ic = &l1p->u.mphInitCnf; + + LOGP(DL1C, LOGL_INFO, "Rx MPH-INIT.conf (status=%s)\n", + get_value_string(lc15bts_l1status_names, ic->status)); + + /* store layer1 handle */ + if (ic->status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_FATAL, "Rx MPH-INIT.conf status=%s\n", + get_value_string(lc15bts_l1status_names, ic->status)); + bts_shutdown(trx->bts, "MPH-INIT failure"); + } + + fl1h->hLayer1 = (uint32_t)ic->hLayer1; + + /* If the TRX was already locked the MphInit would have undone it */ + if (trx->mo.nm_state.administrative == NM_STATE_LOCKED) + trx_rf_lock(trx, 1, trx_mute_on_init_cb); + + /* Begin to ramp up the power */ + power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0); + + return opstart_compl(&trx->mo, l1_msg); +} + +int gsm_abis_mo_check_attr(const struct gsm_abis_mo *mo, const uint8_t *attr_ids, + unsigned int num_attr_ids) +{ + unsigned int i; + + if (!mo->nm_attr) + return 0; + + for (i = 0; i < num_attr_ids; i++) { + if (!TLVP_PRESENT(mo->nm_attr, attr_ids[i])) + return 0; + } + return 1; +} + +static const uint8_t trx_rqd_attr[] = { NM_ATT_RF_MAXPOWR_R }; + +/* initialize the layer1 */ +static int trx_init(struct gsm_bts_trx *trx) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + struct msgb *msg; + GsmL1_MphInitReq_t *mi_req; + GsmL1_DeviceParam_t *dev_par; + int lc15_band; + + if (!gsm_abis_mo_check_attr(&trx->mo, trx_rqd_attr, + ARRAY_SIZE(trx_rqd_attr))) { + /* HACK: spec says we need to decline, but openbsc + * doesn't deal with this very well */ + return oml_mo_opstart_ack(&trx->mo); + //return oml_mo_opstart_nack(&trx->mo, NM_NACK_CANT_PERFORM); + } + + lc15_band = lc15bts_select_lc15_band(trx, trx->arfcn); + if (lc15_band < 0) { + LOGP(DL1C, LOGL_ERROR, "Unsupported GSM band %s\n", + gsm_band_name(trx->bts->band)); + } + + msg = l1p_msgb_alloc(); + mi_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphInitReq, fl1h, + l1p_handle_for_trx(trx)); + dev_par = &mi_req->deviceParam; + dev_par->devType = GsmL1_DevType_TxdRxu; + dev_par->freqBand = lc15_band; + dev_par->u16Arfcn = trx->arfcn; + dev_par->u16BcchArfcn = trx->bts->c0->arfcn; + dev_par->u8NbTsc = trx->bts->bsic & 7; + dev_par->fRxPowerLevel = trx_ms_pwr_ctrl_is_osmo(trx) + ? 0.0 : trx->bts->ul_power_target; + + dev_par->fTxPowerLevel = 0.0; + LOGP(DL1C, LOGL_NOTICE, "Init TRX (Band %d, ARFCN %u, TSC %u, RxPower % 2f dBm, " + "TxPower % 2.2f dBm\n", dev_par->freqBand, dev_par->u16Arfcn, dev_par->u8NbTsc, + dev_par->fRxPowerLevel, dev_par->fTxPowerLevel); + + /* send MPH-INIT-REQ, wait for MPH-INIT-CNF */ + return l1if_gsm_req_compl(fl1h, msg, trx_init_compl_cb, NULL); +} + +uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + + return fl1h->hLayer1; +} + +static int trx_close_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + msgb_free(l1_msg); + return 0; +} + +int bts_model_trx_close(struct gsm_bts_trx *trx) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + struct msgb *msg; + + msg = l1p_msgb_alloc(); + prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphCloseReq, fl1h, + l1p_handle_for_trx(trx)); + LOGP(DL1C, LOGL_NOTICE, "Close TRX %u\n", trx->nr); + + return l1if_gsm_req_compl(fl1h, msg, trx_close_compl_cb, NULL); +} + +static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + uint8_t mute[8]; + int i; + + for (i = 0; i < ARRAY_SIZE(mute); ++i) + mute[i] = locked ? 1 : 0; + + return l1if_mute_rf(fl1h, mute, cb); +} + +int oml_mo_rf_lock_chg(struct gsm_abis_mo *mo, uint8_t mute_state[8], + int success) +{ + if (success) { + int i; + int is_locked = 1; + + for (i = 0; i < 8; ++i) + if (!mute_state[i]) + is_locked = 0; + + mo->nm_state.administrative = + is_locked ? NM_STATE_LOCKED : NM_STATE_UNLOCKED; + mo->procedure_pending = 0; + return oml_mo_statechg_ack(mo); + } else { + mo->procedure_pending = 0; + return oml_mo_statechg_nack(mo, NM_NACK_REQ_NOT_GRANT); + } +} + +static int ts_connect_as(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config pchan, + l1if_compl_cb *cb, void *data) +{ + struct msgb *msg = l1p_msgb_alloc(); + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(ts->trx); + GsmL1_MphConnectReq_t *cr; + + if (pchan == GSM_PCHAN_TCH_F_PDCH + || pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + LOGP(DL1C, LOGL_ERROR, + "%s Requested TS connect as %s," + " expected a specific pchan instead\n", + gsm_ts_and_pchan_name(ts), gsm_pchan_name(pchan)); + return -EINVAL; + } + + cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConnectReq, fl1h, + l1p_handle_for_ts(ts)); + cr->u8Tn = ts->nr; + cr->logChComb = pchan_to_logChComb[pchan]; + + return l1if_gsm_req_compl(fl1h, msg, cb, NULL); +} + +static int ts_opstart(struct gsm_bts_trx_ts *ts) +{ + enum gsm_phys_chan_config pchan = ts->pchan; + switch (pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + ts->dyn.pchan_is = ts->dyn.pchan_want = GSM_PCHAN_NONE; + /* First connect as NONE, until first RSL CHAN ACT. */ + pchan = GSM_PCHAN_NONE; + break; + case GSM_PCHAN_TCH_F_PDCH: + /* First connect as TCH/F, expecting PDCH ACT. */ + pchan = GSM_PCHAN_TCH_F; + break; + default: + /* simply use ts->pchan */ + break; + } + return ts_connect_as(ts, pchan, opstart_compl_cb, NULL); +} + +GsmL1_Sapi_t lchan_to_GsmL1_Sapi_t(const struct gsm_lchan *lchan) +{ + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + return GsmL1_Sapi_TchF; + case GSM_LCHAN_TCH_H: + return GsmL1_Sapi_TchH; + default: + LOGP(DL1C, LOGL_NOTICE, "%s cannot determine L1 SAPI\n", + gsm_lchan_name(lchan)); + break; + } + return GsmL1_Sapi_Idle; +} + +GsmL1_SubCh_t lchan_to_GsmL1_SubCh_t(const struct gsm_lchan *lchan) +{ + enum gsm_phys_chan_config pchan = lchan->ts->pchan; + + if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) + pchan = lchan->ts->dyn.pchan_want; + + switch (pchan) { + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + if (lchan->type == GSM_LCHAN_CCCH) + return GsmL1_SubCh_NA; + /* fall-through */ + case GSM_PCHAN_TCH_H: + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + return lchan->nr; + case GSM_PCHAN_NONE: + case GSM_PCHAN_CCCH: + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_PDCH: + case GSM_PCHAN_UNKNOWN: + default: + /* case GSM_PCHAN_TCH_F_TCH_H_PDCH: is caught above */ + return GsmL1_SubCh_NA; + } + + return GsmL1_SubCh_NA; +} + +struct sapi_dir { + GsmL1_Sapi_t sapi; + GsmL1_Dir_t dir; +}; + +static const struct sapi_dir ccch_sapis[] = { + { GsmL1_Sapi_Fcch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Sch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Bcch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Agch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Pch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink }, +}; + +static const struct sapi_dir tchf_sapis[] = { + { GsmL1_Sapi_TchF, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_TchF, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_FacchF, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_FacchF, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink }, +}; + +static const struct sapi_dir tchh_sapis[] = { + { GsmL1_Sapi_TchH, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_TchH, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_FacchH, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_FacchH, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink }, +}; + +static const struct sapi_dir sdcch_sapis[] = { + { GsmL1_Sapi_Sdcch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Sdcch, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink }, +}; + +static const struct sapi_dir cbch_sapis[] = { + { GsmL1_Sapi_Cbch, GsmL1_Dir_TxDownlink }, + /* Does the CBCH really have a SACCH in Downlink? */ + { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink }, +}; + +static const struct sapi_dir pdtch_sapis[] = { + { GsmL1_Sapi_Pdtch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Pdtch, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_Ptcch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Prach, GsmL1_Dir_RxUplink }, +#if 0 + { GsmL1_Sapi_Ptcch, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_Pacch, GsmL1_Dir_TxDownlink }, +#endif +}; + +static const struct sapi_dir ho_sapis[] = { + { GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink }, +}; + +struct lchan_sapis { + const struct sapi_dir *sapis; + unsigned int num_sapis; +}; + +static const struct lchan_sapis sapis_for_lchan[_GSM_LCHAN_MAX] = { + [GSM_LCHAN_SDCCH] = { + .sapis = sdcch_sapis, + .num_sapis = ARRAY_SIZE(sdcch_sapis), + }, + [GSM_LCHAN_TCH_F] = { + .sapis = tchf_sapis, + .num_sapis = ARRAY_SIZE(tchf_sapis), + }, + [GSM_LCHAN_TCH_H] = { + .sapis = tchh_sapis, + .num_sapis = ARRAY_SIZE(tchh_sapis), + }, + [GSM_LCHAN_CCCH] = { + .sapis = ccch_sapis, + .num_sapis = ARRAY_SIZE(ccch_sapis), + }, + [GSM_LCHAN_PDTCH] = { + .sapis = pdtch_sapis, + .num_sapis = ARRAY_SIZE(pdtch_sapis), + }, + [GSM_LCHAN_CBCH] = { + .sapis = cbch_sapis, + .num_sapis = ARRAY_SIZE(cbch_sapis), + }, +}; + +static const struct lchan_sapis sapis_for_ho = { + .sapis = ho_sapis, + .num_sapis = ARRAY_SIZE(ho_sapis), +}; + +static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd); +static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd); +static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd); +static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cmd); + +static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir); +static int lchan_deactivate_sapis(struct gsm_lchan *lchan); + +/** + * Execute the first SAPI command of the queue. In case of the markers + * this method is re-entrant so we need to make sure to remove a command + * from the list before calling a function that will queue a command. + * + * \return 0 in case no Gsm Request was sent, 1 otherwise + */ +static int sapi_queue_exeute(struct gsm_lchan *lchan) +{ + int res; + struct sapi_cmd *cmd; + + cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry); + + switch (cmd->type) { + case SAPI_CMD_ACTIVATE: + mph_send_activate_req(lchan, cmd); + res = 1; + break; + case SAPI_CMD_CONFIG_CIPHERING: + mph_send_config_ciphering(lchan, cmd); + res = 1; + break; + case SAPI_CMD_CONFIG_LOGCH_PARAM: + mph_send_config_logchpar(lchan, cmd); + res = 1; + break; + case SAPI_CMD_SACCH_REL_MARKER: + llist_del(&cmd->entry); + talloc_free(cmd); + res = check_sapi_release(lchan, GsmL1_Sapi_Sacch, + GsmL1_Dir_TxDownlink); + res |= check_sapi_release(lchan, GsmL1_Sapi_Sacch, + GsmL1_Dir_RxUplink); + break; + case SAPI_CMD_REL_MARKER: + llist_del(&cmd->entry); + talloc_free(cmd); + res = lchan_deactivate_sapis(lchan); + break; + case SAPI_CMD_DEACTIVATE: + mph_send_deactivate_req(lchan, cmd); + res = 1; + break; + default: + LOGP(DL1C, LOGL_NOTICE, + "Unimplemented command type %d\n", cmd->type); + llist_del(&cmd->entry); + talloc_free(cmd); + res = 0; + abort(); + break; + } + + return res; +} + +static void sapi_queue_send(struct gsm_lchan *lchan) +{ + int res; + + do { + res = sapi_queue_exeute(lchan); + } while (res == 0 && !llist_empty(&lchan->sapi_cmds)); +} + +static void sapi_queue_dispatch(struct gsm_lchan *lchan, int status) +{ + int end; + struct sapi_cmd *cmd = llist_entry(lchan->sapi_cmds.next, + struct sapi_cmd, entry); + llist_del(&cmd->entry); + end = llist_empty(&lchan->sapi_cmds); + + if (cmd->callback) + cmd->callback(lchan, status); + talloc_free(cmd); + + if (end || llist_empty(&lchan->sapi_cmds)) { + LOGP(DL1C, LOGL_DEBUG, + "%s End of SAPI cmd queue encountered.%s\n", + gsm_lchan_name(lchan), + llist_empty(&lchan->sapi_cmds) + ? " Queue is now empty." + : " More pending."); + return; + } + + sapi_queue_send(lchan); +} + +/** + * Queue and possible execute a SAPI command. Return 1 in case the command was + * already executed and 0 in case if it was only put into the queue + */ +static int queue_sapi_command(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + int start = llist_empty(&lchan->sapi_cmds); + llist_add_tail(&cmd->entry, &lchan->sapi_cmds); + + if (!start) + return 0; + + sapi_queue_send(lchan); + return 1; +} + +static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + enum lchan_sapi_state status; + struct sapi_cmd *cmd; + struct gsm_lchan *lchan; + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphActivateCnf_t *ic = &l1p->u.mphActivateCnf; + + /* get the lchan from the information we supplied */ + lchan = l1if_hLayer_to_lchan(trx, (uint32_t)ic->hLayer3); + if (!lchan) { + LOGP(DL1C, LOGL_ERROR, + "Failed to find lchan for hLayer3=0x%x\n", (uint32_t)ic->hLayer3); + goto err; + } + + LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.conf (%s ", + gsm_lchan_name(lchan), + get_value_string(lc15bts_l1sapi_names, ic->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(lc15bts_dir_names, ic->dir)); + + if (ic->status == GsmL1_Status_Success) { + DEBUGP(DL1C, "Successful activation of L1 SAPI %s on TS %u\n", + get_value_string(lc15bts_l1sapi_names, ic->sapi), ic->u8Tn); + status = LCHAN_SAPI_S_ASSIGNED; + } else { + LOGP(DL1C, LOGL_ERROR, "Error activating L1 SAPI %s on TS %u: %s\n", + get_value_string(lc15bts_l1sapi_names, ic->sapi), ic->u8Tn, + get_value_string(lc15bts_l1status_names, ic->status)); + status = LCHAN_SAPI_S_ERROR; + } + + if (ic->dir & GsmL1_Dir_TxDownlink) + lchan->sapis_dl[ic->sapi] = status; + if (ic->dir & GsmL1_Dir_RxUplink) + lchan->sapis_ul[ic->sapi] = status; + + if (llist_empty(&lchan->sapi_cmds)) { + LOGP(DL1C, LOGL_ERROR, + "%s Got activation confirmation with empty queue\n", + gsm_lchan_name(lchan)); + goto err; + } + + cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry); + if (cmd->sapi != ic->sapi || cmd->dir != ic->dir || + cmd->type != SAPI_CMD_ACTIVATE) { + LOGP(DL1C, LOGL_ERROR, + "%s Confirmation mismatch (%d, %d) (%d, %d)\n", + gsm_lchan_name(lchan), cmd->sapi, cmd->dir, + ic->sapi, ic->dir); + goto err; + } + + sapi_queue_dispatch(lchan, ic->status); + +err: + msgb_free(l1_msg); + + return 0; +} + +uint32_t l1if_lchan_to_hLayer(struct gsm_lchan *lchan) +{ + return 0xBB + | (lchan->nr << 8) + | (lchan->ts->nr << 16) + | (lchan->ts->trx->nr << 24); +} + +/* obtain a ptr to the lapdm_channel for a given hLayer */ +struct gsm_lchan * +l1if_hLayer_to_lchan(struct gsm_bts_trx *trx, uint32_t hLayer2) +{ + uint8_t magic = hLayer2 & 0xff; + uint8_t ts_nr = (hLayer2 >> 16) & 0xff; + uint8_t lchan_nr = (hLayer2 >> 8)& 0xff; + struct gsm_bts_trx_ts *ts; + + if (magic != 0xBB) + return NULL; + + /* FIXME: if we actually run on the BTS, the 32bit field is large + * enough to simply put a pointer inside. */ + if (ts_nr >= ARRAY_SIZE(trx->ts)) + return NULL; + + ts = &trx->ts[ts_nr]; + + if (lchan_nr >= ARRAY_SIZE(ts->lchan)) + return NULL; + + return &ts->lchan[lchan_nr]; +} + +/* we regularly check if the DSP L1 is still sending us primitives. + * if not, we simply stop the BTS program (and be re-spawned) */ +static void alive_timer_cb(void *data) +{ + struct lc15l1_hdl *fl1h = data; + + if (fl1h->alive_prim_cnt == 0) { + LOGP(DL1C, LOGL_FATAL, "DSP L1 is no longer sending primitives!\n"); + exit(23); + } + fl1h->alive_prim_cnt = 0; + osmo_timer_schedule(&fl1h->alive_timer, 5, 0); +} + +static void clear_amr_params(GsmL1_LogChParam_t *lch_par) +{ + int j; + /* common for the SIGN, V1 and EFR: */ + lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_NA; + lch_par->tch.amrInitCodecMode = GsmL1_AmrCodecMode_Unset; + for (j = 0; j < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); j++) + lch_par->tch.amrActiveCodecSet[j] = GsmL1_AmrCodec_Unset; +} + +static void set_payload_format(GsmL1_LogChParam_t *lch_par) +{ + lch_par->tch.tchPlFmt = GsmL1_TchPlFmt_Rtp; +} + +static void lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan) +{ + struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr; + struct gsm48_multi_rate_conf *mr_conf = + (struct gsm48_multi_rate_conf *) amr_mrc->gsm48_ie; + int j; + + LOGP(DL1C, LOGL_INFO, "%s: %s tch_mode=0x%02x\n", + gsm_lchan_name(lchan), __FUNCTION__, lchan->tch_mode); + + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: + /* we have to set some TCH payload type even if we don't + * know yet what codec we will use later on */ + if (lchan->type == GSM_LCHAN_TCH_F) + lch_par->tch.tchPlType = GsmL1_TchPlType_Fr; + else + lch_par->tch.tchPlType = GsmL1_TchPlType_Hr; + clear_amr_params(lch_par); + break; + case GSM48_CMODE_SPEECH_V1: + if (lchan->type == GSM_LCHAN_TCH_F) + lch_par->tch.tchPlType = GsmL1_TchPlType_Fr; + else + lch_par->tch.tchPlType = GsmL1_TchPlType_Hr; + set_payload_format(lch_par); + clear_amr_params(lch_par); + break; + case GSM48_CMODE_SPEECH_EFR: + lch_par->tch.tchPlType = GsmL1_TchPlType_Efr; + set_payload_format(lch_par); + clear_amr_params(lch_par); + break; + case GSM48_CMODE_SPEECH_AMR: + lch_par->tch.tchPlType = GsmL1_TchPlType_Amr; + set_payload_format(lch_par); + lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_Odd; /* FIXME? */ + lch_par->tch.amrInitCodecMode = amr_get_initial_mode(lchan); + + /* initialize to clean state */ + for (j = 0; j < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); j++) + lch_par->tch.amrActiveCodecSet[j] = GsmL1_AmrCodec_Unset; + + j = 0; + if (mr_conf->m4_75) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_4_75; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + + if (mr_conf->m5_15) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_5_15; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + + if (mr_conf->m5_90) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_5_9; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + + if (mr_conf->m6_70) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_6_7; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + + if (mr_conf->m7_40) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_7_4; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + + if (mr_conf->m7_95) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_7_95; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + + if (mr_conf->m10_2) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_10_2; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + if (mr_conf->m12_2) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_12_2; + break; + case GSM48_CMODE_DATA_14k5: + case GSM48_CMODE_DATA_12k0: + case GSM48_CMODE_DATA_6k0: + case GSM48_CMODE_DATA_3k6: + LOGP(DL1C, LOGL_ERROR, "%s: CSD not supported!\n", + gsm_lchan_name(lchan)); + break; + } +} + +static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(lchan->ts->trx); + struct msgb *msg = l1p_msgb_alloc(); + int sapi = cmd->sapi; + int dir = cmd->dir; + GsmL1_MphActivateReq_t *act_req; + GsmL1_LogChParam_t *lch_par; + + act_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphActivateReq, + fl1h, l1p_handle_for_lchan(lchan)); + lch_par = &act_req->logChPrm; + act_req->u8Tn = lchan->ts->nr; + act_req->subCh = lchan_to_GsmL1_SubCh_t(lchan); + act_req->dir = dir; + act_req->sapi = sapi; + act_req->hLayer2 = (HANDLE *)l1if_lchan_to_hLayer(lchan); + act_req->hLayer3 = act_req->hLayer2; + + switch (act_req->sapi) { + case GsmL1_Sapi_Rach: + lch_par->rach.u8Bsic = lchan->ts->trx->bts->bsic; + break; + case GsmL1_Sapi_Agch: + lch_par->agch.u8NbrOfAgch = num_agch(lchan->ts->trx, lchan->name); + break; + case GsmL1_Sapi_TchH: + case GsmL1_Sapi_TchF: + lchan2lch_par(lch_par, lchan); + /* + * Be sure that every packet is received, even if it + * fails. In this case the length might be lower or 0. + */ + act_req->fBFILevel = -200.0f; + break; + case GsmL1_Sapi_Ptcch: + lch_par->ptcch.u8Bsic = lchan->ts->trx->bts->bsic; + break; + case GsmL1_Sapi_Prach: + lch_par->prach.u8Bsic = lchan->ts->trx->bts->bsic; + break; + case GsmL1_Sapi_Sacch: + /* + * For the SACCH we need to set the u8MsPowerLevel when + * doing manual MS power control. + */ + if (trx_ms_pwr_ctrl_is_osmo(lchan->ts->trx)) + lch_par->sacch.u8MsPowerLevel = lchan->ms_power_ctrl.current; + /* fall through */ + case GsmL1_Sapi_Pdtch: + case GsmL1_Sapi_Pacch: + /* + * Be sure that every packet is received, even if it + * fails. In this case the length might be lower or 0. + */ + act_req->fBFILevel = -200.0f; + break; + default: + break; + } + + LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.req (hL2=0x%08x, %s ", + gsm_lchan_name(lchan), (uint32_t)act_req->hLayer2, + get_value_string(lc15bts_l1sapi_names, act_req->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(lc15bts_dir_names, act_req->dir)); + + /* send the primitive for all GsmL1_Sapi_* that match the LCHAN */ + return l1if_gsm_req_compl(fl1h, msg, lchan_act_compl_cb, NULL); +} + +static void sapi_clear_queue(struct llist_head *queue) +{ + struct sapi_cmd *next, *tmp; + + llist_for_each_entry_safe(next, tmp, queue, entry) { + llist_del(&next->entry); + talloc_free(next); + } +} + +static int sapi_activate_cb(struct gsm_lchan *lchan, int status) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(lchan->ts->trx); + + /* FIXME: Error handling */ + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_ERROR, + "%s act failed mark broken due status: %d\n", + gsm_lchan_name(lchan), status); + lchan_set_state(lchan, LCHAN_S_BROKEN); + sapi_clear_queue(&lchan->sapi_cmds); + mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, RSL_ERR_PROCESSOR_OVERLOAD); + return -1; + } + + if (!llist_empty(&lchan->sapi_cmds)) + return 0; + + if (lchan->state != LCHAN_S_ACT_REQ) + return 0; + + lchan_set_state(lchan, LCHAN_S_ACTIVE); + mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, 0); + + /* set the initial ciphering parameters for both directions */ + l1if_set_ciphering(fl1h, lchan, 1); + l1if_set_ciphering(fl1h, lchan, 0); + if (lchan->encr.alg_id) + lchan->ciph_state = LCHAN_CIPH_RXTX_REQ; + else + lchan->ciph_state = LCHAN_CIPH_NONE; + + return 0; +} + +static void enqueue_sapi_act_cmd(struct gsm_lchan *lchan, int sapi, int dir) +{ + struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + + cmd->sapi = sapi; + cmd->dir = dir; + cmd->type = SAPI_CMD_ACTIVATE; + cmd->callback = sapi_activate_cb; + queue_sapi_command(lchan, cmd); +} + +int lchan_activate(struct gsm_lchan *lchan) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(lchan->ts->trx); + const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type]; + unsigned int i; + + lchan_set_state(lchan, LCHAN_S_ACT_REQ); + + if (!llist_empty(&lchan->sapi_cmds)) + LOGP(DL1C, LOGL_ERROR, + "%s Trying to activate lchan, but commands in queue\n", + gsm_lchan_name(lchan)); + + /* override the regular SAPIs if this is the first hand-over + * related activation of the LCHAN */ + if (lchan->ho.active == HANDOVER_ENABLED) + s4l = &sapis_for_ho; + + for (i = 0; i < s4l->num_sapis; i++) { + int sapi = s4l->sapis[i].sapi; + int dir = s4l->sapis[i].dir; + + if (sapi == GsmL1_Sapi_Sch) { + /* once we activate the SCH, we should get MPH-TIME.ind */ + fl1h->alive_timer.cb = alive_timer_cb; + fl1h->alive_timer.data = fl1h; + fl1h->alive_prim_cnt = 0; + osmo_timer_schedule(&fl1h->alive_timer, 5, 0); + } + enqueue_sapi_act_cmd(lchan, sapi, dir); + } + +#warning "FIXME: Should this be in sapi_activate_cb?" + lchan_init_lapdm(lchan); + + return 0; +} + +const struct value_string lc15bts_l1cfgt_names[] = { + { GsmL1_ConfigParamId_SetNbTsc, "Set NB TSC" }, + { GsmL1_ConfigParamId_SetTxPowerLevel, "Set Tx power level" }, + { GsmL1_ConfigParamId_SetLogChParams, "Set logical channel params" }, + { GsmL1_ConfigParamId_SetCipheringParams,"Configure ciphering params" }, + { 0, NULL } +}; + +static void dump_lch_par(int logl, GsmL1_LogChParam_t *lch_par, GsmL1_Sapi_t sapi) +{ + int i; + + switch (sapi) { + case GsmL1_Sapi_Rach: + LOGPC(DL1C, logl, "BSIC=0x%08x", lch_par->rach.u8Bsic); + break; + case GsmL1_Sapi_Agch: + LOGPC(DL1C, logl, "BS_AG_BLKS_RES=%u ", + lch_par->agch.u8NbrOfAgch); + break; + case GsmL1_Sapi_Sacch: + LOGPC(DL1C, logl, "MS Power Level 0x%02x", + lch_par->sacch.u8MsPowerLevel); + break; + case GsmL1_Sapi_TchF: + case GsmL1_Sapi_TchH: + LOGPC(DL1C, logl, "amrCmiPhase=0x%02x amrInitCodec=0x%02x (", + lch_par->tch.amrCmiPhase, + lch_par->tch.amrInitCodecMode); + for (i = 0; i < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); i++) { + LOGPC(DL1C, logl, "%x ", + lch_par->tch.amrActiveCodecSet[i]); + } + break; + /* FIXME: PRACH / PTCCH */ + default: + break; + } + LOGPC(DL1C, logl, ")\n"); +} + +static int chmod_txpower_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf; + + LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ", + gsm_trx_name(trx), + get_value_string(lc15bts_l1cfgt_names, cc->cfgParamId)); + + LOGPC(DL1C, LOGL_INFO, "setTxPower %f dBm\n", + cc->cfgParams.setTxPowerLevel.fTxPowerLevel); + + power_trx_change_compl(trx, + (int) (cc->cfgParams.setTxPowerLevel.fTxPowerLevel * 1000)); + + msgb_free(l1_msg); + + return 0; +} + +static int chmod_modif_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + struct gsm_lchan *lchan; + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf; + + /* get the lchan from the information we supplied */ + lchan = l1if_hLayer_to_lchan(trx, (uint32_t)cc->hLayer3); + if (!lchan) { + LOGP(DL1C, LOGL_ERROR, + "Failed to find lchan for hLayer3=0x%x\n", (uint32_t)cc->hLayer3); + goto err; + } + + LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ", + gsm_lchan_name(lchan), + get_value_string(lc15bts_l1cfgt_names, cc->cfgParamId)); + + switch (cc->cfgParamId) { + case GsmL1_ConfigParamId_SetLogChParams: + dump_lch_par(LOGL_INFO, + &cc->cfgParams.setLogChParams.logChParams, + cc->cfgParams.setLogChParams.sapi); + + sapi_queue_dispatch(lchan, cc->status); + break; + case GsmL1_ConfigParamId_SetCipheringParams: + switch (lchan->ciph_state) { + case LCHAN_CIPH_RX_REQ: + LOGPC(DL1C, LOGL_INFO, "RX_REQ -> RX_CONF\n"); + lchan->ciph_state = LCHAN_CIPH_RX_CONF; + break; + case LCHAN_CIPH_RX_CONF_TX_REQ: + LOGPC(DL1C, LOGL_INFO, "RX_CONF_TX_REQ -> RXTX_CONF\n"); + lchan->ciph_state = LCHAN_CIPH_RXTX_CONF; + break; + case LCHAN_CIPH_RXTX_REQ: + LOGPC(DL1C, LOGL_INFO, "RXTX_REQ -> RX_CONF_TX_REQ\n"); + lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ; + break; + case LCHAN_CIPH_NONE: + LOGPC(DL1C, LOGL_INFO, "\n"); + break; + default: + LOGPC(DL1C, LOGL_INFO, "unhandled state %u\n", lchan->ciph_state); + break; + } + if (llist_empty(&lchan->sapi_cmds)) { + LOGP(DL1C, LOGL_ERROR, + "%s Got ciphering conf with empty queue\n", + gsm_lchan_name(lchan)); + goto err; + } + + sapi_queue_dispatch(lchan, cc->status); + break; + case GsmL1_ConfigParamId_SetNbTsc: + default: + LOGPC(DL1C, LOGL_INFO, "\n"); + break; + } + +err: + msgb_free(l1_msg); + + return 0; +} + +static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + struct gsm_bts_trx *trx = lchan->ts->trx; + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + struct msgb *msg = l1p_msgb_alloc(); + GsmL1_MphConfigReq_t *conf_req; + GsmL1_LogChParam_t *lch_par; + + /* channel mode, encryption and/or multirate have changed */ + + /* update multi-rate config */ + conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h, + l1p_handle_for_lchan(lchan)); + conf_req->cfgParamId = GsmL1_ConfigParamId_SetLogChParams; + conf_req->cfgParams.setLogChParams.sapi = cmd->sapi; + conf_req->cfgParams.setLogChParams.u8Tn = lchan->ts->nr; + conf_req->cfgParams.setLogChParams.subCh = lchan_to_GsmL1_SubCh_t(lchan); + conf_req->cfgParams.setLogChParams.dir = cmd->dir; + conf_req->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan); + + lch_par = &conf_req->cfgParams.setLogChParams.logChParams; + lchan2lch_par(lch_par, lchan); + + /* Update the MS Power Level */ + if (cmd->sapi == GsmL1_Sapi_Sacch && trx_ms_pwr_ctrl_is_osmo(trx)) + lch_par->sacch.u8MsPowerLevel = lchan->ms_power_ctrl.current; + + /* FIXME: update encryption */ + + LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.req (%s) ", + gsm_lchan_name(lchan), + get_value_string(lc15bts_l1sapi_names, + conf_req->cfgParams.setLogChParams.sapi)); + LOGPC(DL1C, LOGL_INFO, "cfgParams Tn=%u, subCh=%u, dir=0x%x ", + conf_req->cfgParams.setLogChParams.u8Tn, + conf_req->cfgParams.setLogChParams.subCh, + conf_req->cfgParams.setLogChParams.dir); + dump_lch_par(LOGL_INFO, + &conf_req->cfgParams.setLogChParams.logChParams, + conf_req->cfgParams.setLogChParams.sapi); + + return l1if_gsm_req_compl(fl1h, msg, chmod_modif_compl_cb, NULL); +} + +static void enqueue_sapi_logchpar_cmd(struct gsm_lchan *lchan, int dir, GsmL1_Sapi_t sapi) +{ + struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + + cmd->dir = dir; + cmd->sapi = sapi; + cmd->type = SAPI_CMD_CONFIG_LOGCH_PARAM; + queue_sapi_command(lchan, cmd); +} + +static int tx_confreq_logchpar(struct gsm_lchan *lchan, uint8_t direction) +{ + enqueue_sapi_logchpar_cmd(lchan, direction, lchan_to_GsmL1_Sapi_t(lchan)); + return 0; +} + +int l1if_set_txpower(struct lc15l1_hdl *fl1h, float tx_power) +{ + struct msgb *msg = l1p_msgb_alloc(); + GsmL1_MphConfigReq_t *conf_req; + + conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h, 0); + conf_req->cfgParamId = GsmL1_ConfigParamId_SetTxPowerLevel; + conf_req->cfgParams.setTxPowerLevel.fTxPowerLevel = tx_power; + + return l1if_gsm_req_compl(fl1h, msg, chmod_txpower_compl_cb, NULL); +} + +const enum GsmL1_CipherId_t rsl2l1_ciph[] = { + [0] = GsmL1_CipherId_A50, + [1] = GsmL1_CipherId_A50, + [2] = GsmL1_CipherId_A51, + [3] = GsmL1_CipherId_A52, + [4] = GsmL1_CipherId_A53, +}; + +static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(lchan->ts->trx); + struct msgb *msg = l1p_msgb_alloc(); + struct GsmL1_MphConfigReq_t *cfgr; + + cfgr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h, + l1p_handle_for_lchan(lchan)); + + cfgr->cfgParamId = GsmL1_ConfigParamId_SetCipheringParams; + cfgr->cfgParams.setCipheringParams.u8Tn = lchan->ts->nr; + cfgr->cfgParams.setCipheringParams.subCh = lchan_to_GsmL1_SubCh_t(lchan); + cfgr->cfgParams.setCipheringParams.dir = cmd->dir; + cfgr->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan); + + if (lchan->encr.alg_id >= ARRAY_SIZE(rsl2l1_ciph)) + return -EINVAL; + cfgr->cfgParams.setCipheringParams.cipherId = rsl2l1_ciph[lchan->encr.alg_id]; + + LOGP(DL1C, LOGL_NOTICE, "%s SET_CIPHERING (ALG=%u %s)\n", + gsm_lchan_name(lchan), + cfgr->cfgParams.setCipheringParams.cipherId, + get_value_string(lc15bts_dir_names, + cfgr->cfgParams.setCipheringParams.dir)); + + memcpy(cfgr->cfgParams.setCipheringParams.u8Kc, + lchan->encr.key, lchan->encr.key_len); + + return l1if_gsm_req_compl(fl1h, msg, chmod_modif_compl_cb, NULL); +} + +static void enqueue_sapi_ciphering_cmd(struct gsm_lchan *lchan, int dir) +{ + struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + + cmd->dir = dir; + cmd->type = SAPI_CMD_CONFIG_CIPHERING; + queue_sapi_command(lchan, cmd); +} + +int l1if_set_ciphering(struct lc15l1_hdl *fl1h, + struct gsm_lchan *lchan, + int dir_downlink) +{ + int dir; + + /* ignore the request when the channel is not active */ + if (lchan->state != LCHAN_S_ACTIVE) + return -1; + + if (dir_downlink) + dir = GsmL1_Dir_TxDownlink; + else + dir = GsmL1_Dir_RxUplink; + + enqueue_sapi_ciphering_cmd(lchan, dir); + + return 0; +} + +int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) +{ + if (lchan->state != LCHAN_S_ACTIVE) + return -1; + + enqueue_sapi_logchpar_cmd(lchan, GsmL1_Dir_RxUplink, GsmL1_Sapi_Sacch); + return 0; +} + +int l1if_rsl_mode_modify(struct gsm_lchan *lchan) +{ + if (lchan->state != LCHAN_S_ACTIVE) + return -1; + + /* channel mode, encryption and/or multirate have changed */ + + /* update multi-rate config */ + tx_confreq_logchpar(lchan, GsmL1_Dir_RxUplink); + tx_confreq_logchpar(lchan, GsmL1_Dir_TxDownlink); + + /* FIXME: update encryption */ + + return 0; +} + +static int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + enum lchan_sapi_state status; + struct sapi_cmd *cmd; + struct gsm_lchan *lchan; + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphDeactivateCnf_t *ic = &l1p->u.mphDeactivateCnf; + + lchan = l1if_hLayer_to_lchan(trx, (uint32_t)ic->hLayer3); + if (!lchan) { + LOGP(DL1C, LOGL_ERROR, + "Failed to find lchan for hLayer3=0x%x\n", (uint32_t)ic->hLayer3); + goto err; + } + + LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.conf (%s ", + gsm_lchan_name(lchan), + get_value_string(lc15bts_l1sapi_names, ic->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(lc15bts_dir_names, ic->dir)); + + if (ic->status == GsmL1_Status_Success) { + DEBUGP(DL1C, "Successful deactivation of L1 SAPI %s on TS %u\n", + get_value_string(lc15bts_l1sapi_names, ic->sapi), ic->u8Tn); + status = LCHAN_SAPI_S_NONE; + } else { + LOGP(DL1C, LOGL_ERROR, "Error deactivating L1 SAPI %s on TS %u: %s\n", + get_value_string(lc15bts_l1sapi_names, ic->sapi), ic->u8Tn, + get_value_string(lc15bts_l1status_names, ic->status)); + status = LCHAN_SAPI_S_ERROR; + } + + if (ic->dir & GsmL1_Dir_TxDownlink) + lchan->sapis_dl[ic->sapi] = status; + if (ic->dir & GsmL1_Dir_RxUplink) + lchan->sapis_ul[ic->sapi] = status; + + + if (llist_empty(&lchan->sapi_cmds)) { + LOGP(DL1C, LOGL_ERROR, + "%s Got de-activation confirmation with empty queue\n", + gsm_lchan_name(lchan)); + goto err; + } + + cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry); + if (cmd->sapi != ic->sapi || cmd->dir != ic->dir || + cmd->type != SAPI_CMD_DEACTIVATE) { + LOGP(DL1C, LOGL_ERROR, + "%s Confirmation mismatch (%d, %d) (%d, %d)\n", + gsm_lchan_name(lchan), cmd->sapi, cmd->dir, + ic->sapi, ic->dir); + goto err; + } + + sapi_queue_dispatch(lchan, ic->status); + +err: + msgb_free(l1_msg); + return 0; +} + +static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(lchan->ts->trx); + struct msgb *msg = l1p_msgb_alloc(); + GsmL1_MphDeactivateReq_t *deact_req; + + deact_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDeactivateReq, + fl1h, l1p_handle_for_lchan(lchan)); + deact_req->u8Tn = lchan->ts->nr; + deact_req->subCh = lchan_to_GsmL1_SubCh_t(lchan); + deact_req->dir = cmd->dir; + deact_req->sapi = cmd->sapi; + deact_req->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan); + + LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.req (%s ", + gsm_lchan_name(lchan), + get_value_string(lc15bts_l1sapi_names, deact_req->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(lc15bts_dir_names, deact_req->dir)); + + /* send the primitive for all GsmL1_Sapi_* that match the LCHAN */ + return l1if_gsm_req_compl(fl1h, msg, lchan_deact_compl_cb, NULL); +} + +static int sapi_deactivate_cb(struct gsm_lchan *lchan, int status) +{ + /* FIXME: Error handling. There is no NACK... */ + if (status != GsmL1_Status_Success && lchan->state == LCHAN_S_REL_REQ) { + LOGP(DL1C, LOGL_ERROR, "%s is now broken. Stopping the release.\n", + gsm_lchan_name(lchan)); + lchan_set_state(lchan, LCHAN_S_BROKEN); + sapi_clear_queue(&lchan->sapi_cmds); + mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0); + return -1; + } + + if (!llist_empty(&lchan->sapi_cmds)) + return 0; + + /* Don't send an REL ACK on SACCH deactivate */ + if (lchan->state != LCHAN_S_REL_REQ) + return 0; + + lchan_set_state(lchan, LCHAN_S_NONE); + mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0); + + /* Reactivate CCCH due to SI3 update in RSL */ + if (lchan->rel_act_kind == LCHAN_REL_ACT_REACT) { + lchan->rel_act_kind = LCHAN_REL_ACT_RSL; + lchan_activate(lchan); + } + return 0; +} + +static int enqueue_sapi_deact_cmd(struct gsm_lchan *lchan, int sapi, int dir) +{ + struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + + cmd->sapi = sapi; + cmd->dir = dir; + cmd->type = SAPI_CMD_DEACTIVATE; + cmd->callback = sapi_deactivate_cb; + return queue_sapi_command(lchan, cmd); +} + +/* + * Release the SAPI if it was allocated. E.g. the SACCH might be already + * deactivated or during a hand-over the TCH was not allocated yet. + */ +static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir) +{ + /* check if we should schedule a release */ + if (dir & GsmL1_Dir_TxDownlink) { + if (lchan->sapis_dl[sapi] != LCHAN_SAPI_S_ASSIGNED) + return 0; + lchan->sapis_dl[sapi] = LCHAN_SAPI_S_REL; + } else if (dir & GsmL1_Dir_RxUplink) { + if (lchan->sapis_ul[sapi] != LCHAN_SAPI_S_ASSIGNED) + return 0; + lchan->sapis_ul[sapi] = LCHAN_SAPI_S_REL; + } + + /* now schedule the command and maybe dispatch it */ + return enqueue_sapi_deact_cmd(lchan, sapi, dir); +} + +static int release_sapis_for_ho(struct gsm_lchan *lchan) +{ + int res = 0; + int i; + + const struct lchan_sapis *s4l = &sapis_for_ho; + + for (i = s4l->num_sapis-1; i >= 0; i--) + res |= check_sapi_release(lchan, + s4l->sapis[i].sapi, s4l->sapis[i].dir); + return res; +} + +static int lchan_deactivate_sapis(struct gsm_lchan *lchan) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(lchan->ts->trx); + const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type]; + int i, res; + + res = 0; + + /* The order matters.. the Facch needs to be released first */ + for (i = s4l->num_sapis-1; i >= 0; i--) { + /* Stop the alive timer once we deactivate the SCH */ + if (s4l->sapis[i].sapi == GsmL1_Sapi_Sch) + osmo_timer_del(&fl1h->alive_timer); + + /* Release if it was allocated */ + res |= check_sapi_release(lchan, s4l->sapis[i].sapi, s4l->sapis[i].dir); + } + + /* always attempt to disable the RACH burst */ + res |= release_sapis_for_ho(lchan); + + /* nothing was queued */ + if (res == 0) { + LOGP(DL1C, LOGL_ERROR, "%s all SAPIs already released?\n", + gsm_lchan_name(lchan)); + lchan_set_state(lchan, LCHAN_S_BROKEN); + mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0); + } + + return res; +} + +static void enqueue_rel_marker(struct gsm_lchan *lchan) +{ + struct sapi_cmd *cmd; + + /* remember we need to release all active SAPIs */ + cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + cmd->type = SAPI_CMD_REL_MARKER; + queue_sapi_command(lchan, cmd); +} + +int bts_model_lchan_deactivate(struct gsm_lchan *lchan) +{ + lchan_set_state(lchan, LCHAN_S_REL_REQ); + enqueue_rel_marker(lchan); + return 0; +} + +static void enqueue_sacch_rel_marker(struct gsm_lchan *lchan) +{ + struct sapi_cmd *cmd; + + /* remember we need to check if the SACCH is allocated */ + cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + cmd->type = SAPI_CMD_SACCH_REL_MARKER; + queue_sapi_command(lchan, cmd); +} + +int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan) +{ + enqueue_sacch_rel_marker(lchan); + return 0; +} + +/* callback from OML */ +int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, + struct tlv_parsed *old_attr, struct tlv_parsed *new_attr, + void *obj) +{ + /* FIXME: more checks if the attributes are valid */ + + switch (msg_type) { + case NM_MT_SET_CHAN_ATTR: + /* our L1 only supports one global TSC for all channels + * one one TRX, so we need to make sure not to activate + * channels with a different TSC!! */ + if (TLVP_PRES_LEN(new_attr, NM_ATT_TSC, 1) && + *TLVP_VAL(new_attr, NM_ATT_TSC) != (bts->bsic & 7)) { + LOGP(DOML, LOGL_ERROR, "Channel TSC %u != BSIC-TSC %u\n", + *TLVP_VAL(new_attr, NM_ATT_TSC), bts->bsic & 7); + return -NM_NACK_PARAM_RANGE; + } + break; + } + return 0; +} + +/* callback from OML */ +int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, + struct tlv_parsed *new_attr, int kind, void *obj) +{ + if (kind == NM_OC_RADIO_CARRIER) { + struct gsm_bts_trx *trx = obj; + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + + /* Did we go through MphInit yet? If yes fire and forget */ + if (fl1h->hLayer1) + power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0); + } + + /* FIXME: we actaully need to send a ACK or NACK for the OML message */ + return oml_fom_ack_nack(msg, 0); +} + +/* callback from OML */ +int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, + void *obj) +{ + int rc; + + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + rc = trx_init(obj); + break; + case NM_OC_CHANNEL: + rc = ts_opstart(obj); + break; + case NM_OC_BTS: + case NM_OC_SITE_MANAGER: + case NM_OC_BASEB_TRANSC: + case NM_OC_GPRS_NSE: + case NM_OC_GPRS_CELL: + case NM_OC_GPRS_NSVC: + oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, -1); + rc = oml_mo_opstart_ack(mo); + if (mo->obj_class == NM_OC_BTS) { + oml_mo_state_chg(&bts->mo, -1, NM_AVSTATE_OK); + oml_mo_state_chg(&bts->gprs.nse.mo, -1, NM_AVSTATE_OK); + oml_mo_state_chg(&bts->gprs.cell.mo, -1, NM_AVSTATE_OK); + oml_mo_state_chg(&bts->gprs.nsvc[0].mo, -1, NM_AVSTATE_OK); + } + break; + default: + rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP); + } + return rc; +} + +int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo, + void *obj, uint8_t adm_state) +{ + int rc = -EINVAL; + int granted = 0; + + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + + if (mo->procedure_pending) { + LOGP(DL1C, LOGL_ERROR, "Discarding adm change command: " + "pending procedure on RC %d\n", + ((struct gsm_bts_trx *)obj)->nr); + return 0; + } + mo->procedure_pending = 1; + switch (adm_state) { + case NM_STATE_LOCKED: + rc = trx_rf_lock(obj, 1, NULL); + break; + case NM_STATE_UNLOCKED: + rc = trx_rf_lock(obj, 0, NULL); + break; + default: + granted = 1; + break; + } + + if (!granted && rc == 0) + /* in progress, will send ack/nack after completion */ + return 0; + + mo->procedure_pending = 0; + + break; + default: + /* blindly accept all state changes */ + granted = 1; + break; + } + + if (granted) { + mo->nm_state.administrative = adm_state; + return oml_mo_statechg_ack(mo); + } else + return oml_mo_statechg_nack(mo, NM_NACK_REQ_NOT_GRANT); + +} + +int l1if_rsl_chan_act(struct gsm_lchan *lchan) +{ + //uint8_t mode = *TLVP_VAL(tp, RSL_IE_CHAN_MODE); + //uint8_t type = *TLVP_VAL(tp, RSL_IE_ACT_TYPE); + lchan_activate(lchan); + return 0; +} + +/** + * Modify the given lchan in the handover scenario. This is a lot like + * second channel activation but with some additional activation. + */ +int l1if_rsl_chan_mod(struct gsm_lchan *lchan) +{ + const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type]; + unsigned int i; + + if (lchan->ho.active == HANDOVER_NONE) + return -1; + + LOGP(DHO, LOGL_ERROR, "%s modifying channel for handover\n", + gsm_lchan_name(lchan)); + + /* Give up listening to RACH bursts */ + release_sapis_for_ho(lchan); + + /* Activate the normal SAPIs */ + for (i = 0; i < s4l->num_sapis; i++) { + int sapi = s4l->sapis[i].sapi; + int dir = s4l->sapis[i].dir; + enqueue_sapi_act_cmd(lchan, sapi, dir); + } + + return 0; +} + +int l1if_rsl_chan_rel(struct gsm_lchan *lchan) +{ + /* A duplicate RF Release Request, ignore it */ + if (lchan->state == LCHAN_S_REL_REQ) { + LOGP(DL1C, LOGL_ERROR, "%s already in release request state.\n", + gsm_lchan_name(lchan)); + return 0; + } + + lchan_deactivate(lchan); + return 0; +} + +int l1if_rsl_deact_sacch(struct gsm_lchan *lchan) +{ + /* Only de-activate the SACCH if the lchan is active */ + if (lchan->state != LCHAN_S_ACTIVE) + return 0; + return bts_model_lchan_deactivate_sacch(lchan); +} + +int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) +{ + struct lc15l1_hdl *fl1 = trx_lc15l1_hdl(trx); + + return l1if_activate_rf(fl1, 0); +} + +int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm) +{ + return l1if_set_txpower(trx_lc15l1_hdl(trx), ((float) p_trxout_mdBm)/1000.0); +} + +static int ts_disconnect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphDisconnectCnf_t *cnf = &l1p->u.mphDisconnectCnf; + struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn]; + OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS); + + LOGP(DL1C, LOGL_DEBUG, "%s Rx mphDisconnectCnf\n", + gsm_lchan_name(ts->lchan)); + + cb_ts_disconnected(ts); + + return 0; +} + +int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) +{ + struct msgb *msg = l1p_msgb_alloc(); + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(ts->trx); + GsmL1_MphDisconnectReq_t *cr; + + DEBUGP(DRSL, "%s TS disconnect\n", gsm_lchan_name(ts->lchan)); + cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDisconnectReq, fl1h, + l1p_handle_for_ts(ts)); + cr->u8Tn = ts->nr; + + return l1if_gsm_req_compl(fl1h, msg, ts_disconnect_cb, NULL); +} + +static int ts_connect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphConnectCnf_t *cnf = &l1p->u.mphConnectCnf; + struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn]; + OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS); + + DEBUGP(DL1C, "%s %s Rx mphConnectCnf flags=%s%s%s\n", + gsm_lchan_name(ts->lchan), + gsm_pchan_name(ts->pchan), + ts->flags & TS_F_PDCH_ACTIVE ? "ACTIVE " : "", + ts->flags & TS_F_PDCH_ACT_PENDING ? "ACT_PENDING " : "", + ts->flags & TS_F_PDCH_DEACT_PENDING ? "DEACT_PENDING " : ""); + + cb_ts_connected(ts); + + return 0; +} + +int bts_model_ts_connect(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config as_pchan) +{ + return ts_connect_as(ts, as_pchan, ts_connect_cb, NULL); +} diff --git a/src/osmo-bts-litecell15/oml_router.c b/src/osmo-bts-litecell15/oml_router.c new file mode 100644 index 0000000..198d5e3 --- /dev/null +++ b/src/osmo-bts-litecell15/oml_router.c @@ -0,0 +1,132 @@ +/* Beginnings of an OML router */ + +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * (C) 2014 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include "oml_router.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +static int oml_router_read_cb(struct osmo_fd *fd, unsigned int what) +{ + struct msgb *msg; + int rc; + + msg = oml_msgb_alloc(); + if (!msg) { + LOGP(DL1C, LOGL_ERROR, "Failed to allocate oml msgb.\n"); + return -1; + } + + rc = recv(fd->fd, msg->tail, msg->data_len, 0); + if (rc <= 0) { + close(fd->fd); + osmo_fd_unregister(fd); + fd->fd = -1; + goto err; + } + + msg->l1h = msgb_put(msg, rc); + rc = msg_verify_ipa_structure(msg); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, + "OML Router: Invalid IPA message rc(%d)\n", rc); + goto err; + } + + rc = msg_verify_oml_structure(msg); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, + "OML Router: Invalid OML message rc(%d)\n", rc); + goto err; + } + + /* todo dispatch message */ + +err: + msgb_free(msg); + return -1; +} + +static int oml_router_accept_cb(struct osmo_fd *accept_fd, unsigned int what) +{ + int fd; + struct osmo_fd *read_fd = (struct osmo_fd *) accept_fd->data; + + /* Accept only one connection at a time. De-register it */ + if (read_fd->fd > -1) { + LOGP(DL1C, LOGL_NOTICE, + "New OML router connection. Closing old one.\n"); + close(read_fd->fd); + osmo_fd_unregister(read_fd); + read_fd->fd = -1; + } + + fd = accept(accept_fd->fd, NULL, NULL); + if (fd < 0) { + LOGP(DL1C, LOGL_ERROR, "Failed to accept. errno: %s.\n", + strerror(errno)); + return -1; + } + + read_fd->fd = fd; + if (osmo_fd_register(read_fd) != 0) { + LOGP(DL1C, LOGL_ERROR, "Registering the read fd failed.\n"); + close(fd); + read_fd->fd = -1; + return -1; + } + + return 0; +} + +int oml_router_init(struct gsm_bts *bts, const char *path, + struct osmo_fd *accept_fd, struct osmo_fd *read_fd) +{ + int rc; + + memset(accept_fd, 0, sizeof(*accept_fd)); + memset(read_fd, 0, sizeof(*read_fd)); + + accept_fd->cb = oml_router_accept_cb; + accept_fd->data = read_fd; + + read_fd->cb = oml_router_read_cb; + read_fd->data = bts; + read_fd->when = BSC_FD_READ; + read_fd->fd = -1; + + rc = osmo_sock_unix_init_ofd(accept_fd, SOCK_SEQPACKET, 0, + path, + OSMO_SOCK_F_BIND | OSMO_SOCK_F_NONBLOCK); + return rc; +} diff --git a/src/osmo-bts-litecell15/oml_router.h b/src/osmo-bts-litecell15/oml_router.h new file mode 100644 index 0000000..8c08baa --- /dev/null +++ b/src/osmo-bts-litecell15/oml_router.h @@ -0,0 +1,13 @@ +#pragma once + +struct gsm_bts; +struct osmo_fd; + +/** + * The default path lc15bts will listen for incoming + * registrations for OML routing and sending. + */ +#define OML_ROUTER_PATH "/var/run/lc15bts_oml_router" + + +int oml_router_init(struct gsm_bts *bts, const char *path, struct osmo_fd *accept, struct osmo_fd *read); diff --git a/src/osmo-bts-litecell15/tch.c b/src/osmo-bts-litecell15/tch.c new file mode 100644 index 0000000..0becfc4 --- /dev/null +++ b/src/osmo-bts-litecell15/tch.c @@ -0,0 +1,534 @@ +/* Traffic channel support for NuRAN Wireless Litecell 1.5 BTS L1 */ + +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * (C) 2011-2012 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "lc15bts.h" +#include "l1_if.h" + +static struct msgb *l1_to_rtppayload_fr(uint8_t *l1_payload, uint8_t payload_len, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + + /* new L1 can deliver bits like we need them */ + cur = msgb_put(msg, GSM_FR_BYTES); + memcpy(cur, l1_payload, GSM_FR_BYTES); + + lchan_set_marker(osmo_fr_check_sid(l1_payload, payload_len), lchan); + + return msg; +} + +/*! \brief convert GSM-FR from RTP payload to L1 format + * \param[out] l1_payload payload part of L1 buffer + * \param[in] rtp_payload pointer to RTP payload data + * \param[in] payload_len length of \a rtp_payload + * \returns number of \a l1_payload bytes filled + */ +static int rtppayload_to_l1_fr(uint8_t *l1_payload, const uint8_t *rtp_payload, + unsigned int payload_len) +{ + /* new L1 can deliver bits like we need them */ + memcpy(l1_payload, rtp_payload, GSM_FR_BYTES); + return GSM_FR_BYTES; +} + +static struct msgb *l1_to_rtppayload_efr(uint8_t *l1_payload, + uint8_t payload_len, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + + /* new L1 can deliver bits like we need them */ + cur = msgb_put(msg, GSM_EFR_BYTES); + memcpy(cur, l1_payload, GSM_EFR_BYTES); + enum osmo_amr_type ft; + enum osmo_amr_quality bfi; + uint8_t cmr; + int8_t sti, cmi; + osmo_amr_rtp_dec(l1_payload, payload_len, &cmr, &cmi, &ft, &bfi, &sti); + lchan_set_marker(ft == AMR_GSM_EFR_SID, lchan); + + return msg; +} + +static int rtppayload_to_l1_efr(uint8_t *l1_payload, const uint8_t *rtp_payload, + unsigned int payload_len) +{ + memcpy(l1_payload, rtp_payload, payload_len); + + return payload_len; +} + +static struct msgb *l1_to_rtppayload_hr(uint8_t *l1_payload, uint8_t payload_len, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + + if (payload_len != GSM_HR_BYTES) { + LOGP(DL1P, LOGL_ERROR, "L1 HR frame length %u != expected %u\n", + payload_len, GSM_HR_BYTES); + return NULL; + } + + cur = msgb_put(msg, GSM_HR_BYTES); + memcpy(cur, l1_payload, GSM_HR_BYTES); + + lchan_set_marker(osmo_hr_check_sid(l1_payload, payload_len), lchan); + + return msg; +} + +/*! \brief convert GSM-FR from RTP payload to L1 format + * \param[out] l1_payload payload part of L1 buffer + * \param[in] rtp_payload pointer to RTP payload data + * \param[in] payload_len length of \a rtp_payload + * \returns number of \a l1_payload bytes filled + */ +static int rtppayload_to_l1_hr(uint8_t *l1_payload, const uint8_t *rtp_payload, + unsigned int payload_len) +{ + + if (payload_len != GSM_HR_BYTES) { + LOGP(DL1P, LOGL_ERROR, "RTP HR frame length %u != expected %u\n", + payload_len, GSM_HR_BYTES); + return 0; + } + + memcpy(l1_payload, rtp_payload, GSM_HR_BYTES); + + return GSM_HR_BYTES; +} + +static struct msgb *l1_to_rtppayload_amr(uint8_t *l1_payload, uint8_t payload_len, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t amr_if2_len = payload_len - 2; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + + cur = msgb_put(msg, amr_if2_len); + memcpy(cur, l1_payload+2, amr_if2_len); + + /* + * Audiocode's MGW doesn't like receiving CMRs that are not + * the same as the previous one. This means we need to patch + * the content here. + */ + if ((cur[0] & 0xF0) == 0xF0) + cur[0]= lchan->tch.last_cmr << 4; + else + lchan->tch.last_cmr = cur[0] >> 4; + + return msg; +} + +/*! \brief convert AMR from RTP payload to L1 format + * \param[out] l1_payload payload part of L1 buffer + * \param[in] rtp_payload pointer to RTP payload data + * \param[in] payload_len length of \a rtp_payload + * \returns number of \a l1_payload bytes filled + */ +static int rtppayload_to_l1_amr(uint8_t *l1_payload, const uint8_t *rtp_payload, + uint8_t payload_len, uint8_t ft) +{ + memcpy(l1_payload, rtp_payload, payload_len); + return payload_len; +} + +#define RTP_MSGB_ALLOC_SIZE 512 + +/*! \brief function for incoming RTP via TCH.req + * \param[in] rtp_pl buffer containing RTP payload + * \param[in] rtp_pl_len length of \a rtp_pl + * \param[in] use_cache Use cached payload instead of parsing RTP + * \param[in] marker RTP header Marker bit (indicates speech onset) + * \returns 0 if encoding result can be sent further to L1 without extra actions + * positive value if data is ready AND extra actions are required + * negative value otherwise (no data for L1 encoded) + * + * This function prepares a msgb with a L1 PH-DATA.req primitive and + * queues it into lchan->dl_tch_queue. + * + * Note that the actual L1 primitive header is not fully initialized + * yet, as things like the frame number, etc. are unknown at the time we + * pre-fill the primtive. + */ +int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len, + const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn, + bool use_cache, bool marker) +{ + uint8_t *payload_type; + uint8_t *l1_payload, ft; + int rc = 0; + bool is_sid = false; + + DEBUGP(DRTP, "%s RTP IN: %s\n", gsm_lchan_name(lchan), + osmo_hexdump(rtp_pl, rtp_pl_len)); + + payload_type = &data[0]; + l1_payload = &data[1]; + + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + if (lchan->type == GSM_LCHAN_TCH_F) { + *payload_type = GsmL1_TchPlType_Fr; + rc = rtppayload_to_l1_fr(l1_payload, + rtp_pl, rtp_pl_len); + if (rc && lchan->ts->trx->bts->dtxd) + is_sid = osmo_fr_check_sid(rtp_pl, rtp_pl_len); + } else{ + *payload_type = GsmL1_TchPlType_Hr; + rc = rtppayload_to_l1_hr(l1_payload, + rtp_pl, rtp_pl_len); + if (rc && lchan->ts->trx->bts->dtxd) + is_sid = osmo_hr_check_sid(rtp_pl, rtp_pl_len); + } + if (is_sid) + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, -1); + break; + case GSM48_CMODE_SPEECH_EFR: + *payload_type = GsmL1_TchPlType_Efr; + rc = rtppayload_to_l1_efr(l1_payload, rtp_pl, + rtp_pl_len); + /* FIXME: detect and save EFR SID */ + break; + case GSM48_CMODE_SPEECH_AMR: + if (use_cache) { + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload, lchan->tch.dtx.cache, + lchan->tch.dtx.len, ft); + *len = lchan->tch.dtx.len + 1; + return 0; + } + + rc = dtx_dl_amr_fsm_step(lchan, rtp_pl, rtp_pl_len, fn, + l1_payload, marker, len, &ft); + if (rc < 0) + return rc; + if (!dtx_dl_amr_enabled(lchan)) { + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + } + + /* DTX DL-specific logic below: */ + switch (lchan->tch.dtx.dl_amr_fsm->state) { + case ST_ONSET_V: + *payload_type = GsmL1_TchPlType_Amr_Onset; + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0); + *len = 3; + return 1; + case ST_VOICE: + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + case ST_SID_F1: + if (lchan->type == GSM_LCHAN_TCH_H) { /* AMR HR */ + *payload_type = GsmL1_TchPlType_Amr_SidFirstP1; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, + rtp_pl_len, ft); + return 0; + } + /* AMR FR */ + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + case ST_SID_F2: + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + case ST_F1_INH_V: + *payload_type = GsmL1_TchPlType_Amr_SidFirstInH; + *len = 3; + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0); + return 1; + case ST_U_INH_V: + *payload_type = GsmL1_TchPlType_Amr_SidUpdateInH; + *len = 3; + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0); + return 1; + case ST_SID_U: + case ST_U_NOINH: + return -EAGAIN; + case ST_FACCH: + return -EBADMSG; + default: + LOGP(DRTP, LOGL_ERROR, "Unhandled DTX DL AMR FSM state " + "%d\n", lchan->tch.dtx.dl_amr_fsm->state); + return -EINVAL; + } + break; + default: + /* we don't support CSD modes */ + rc = -1; + break; + } + + if (rc < 0) { + LOGP(DRTP, LOGL_ERROR, "%s unable to parse RTP payload\n", + gsm_lchan_name(lchan)); + return -EBADMSG; + } + + *len = rc + 1; + + DEBUGP(DRTP, "%s RTP->L1: %s\n", gsm_lchan_name(lchan), + osmo_hexdump(data, *len)); + return 0; +} + +static int is_recv_only(uint8_t speech_mode) +{ + return (speech_mode & 0xF0) == (1 << 4); +} + +/*! \brief receive a traffic L1 primitive for a given lchan */ +int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1p_msg); + GsmL1_PhDataInd_t *data_ind = &l1p->u.phDataInd; + uint8_t *payload, payload_type, payload_len, sid_first[9] = { 0 }; + struct msgb *rmsg = NULL; + struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)]; + + if (is_recv_only(lchan->abis_ip.speech_mode)) + return -EAGAIN; + + if (data_ind->msgUnitParam.u8Size < 1) { + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "chan_nr %d Rx Payload size 0\n", chan_nr); + /* Push empty payload to upper layers */ + rmsg = msgb_alloc_headroom(256, 128, "L1P-to-RTP"); + return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn, + data_ind->measParam.fBer * 10000, + data_ind->measParam.fLinkQuality * 10); + } + + payload_type = data_ind->msgUnitParam.u8Buffer[0]; + payload = data_ind->msgUnitParam.u8Buffer + 1; + payload_len = data_ind->msgUnitParam.u8Size - 1; + + switch (payload_type) { + case GsmL1_TchPlType_Fr: + case GsmL1_TchPlType_Efr: + if (lchan->type != GSM_LCHAN_TCH_F) + goto err_payload_match; + break; + case GsmL1_TchPlType_Hr: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + break; + case GsmL1_TchPlType_Amr: + if (lchan->type != GSM_LCHAN_TCH_H && + lchan->type != GSM_LCHAN_TCH_F) + goto err_payload_match; + break; + case GsmL1_TchPlType_Amr_Onset: + if (lchan->type != GSM_LCHAN_TCH_H && + lchan->type != GSM_LCHAN_TCH_F) + goto err_payload_match; + /* according to 3GPP TS 26.093 ONSET frames precede the first + speech frame of a speech burst - set the marker for next RTP + frame */ + lchan->rtp_tx_marker = true; + break; + case GsmL1_TchPlType_Amr_SidFirstP1: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P1 from L1 " + "(%d bytes)\n", payload_len); + break; + case GsmL1_TchPlType_Amr_SidFirstP2: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P2 from L1 " + "(%d bytes)\n", payload_len); + break; + case GsmL1_TchPlType_Amr_SidFirstInH: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + lchan->rtp_tx_marker = true; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_INH from L1 " + "(%d bytes)\n", payload_len); + break; + case GsmL1_TchPlType_Amr_SidUpdateInH: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + lchan->rtp_tx_marker = true; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_UPDATE_INH from L1 " + "(%d bytes)\n", payload_len); + break; + default: + LOGPFN(DL1P, LOGL_NOTICE, data_ind->u32Fn, "%s Rx Payload Type %s is unsupported\n", + gsm_lchan_name(lchan), + get_value_string(lc15bts_tch_pl_names, payload_type)); + break; + } + + + switch (payload_type) { + case GsmL1_TchPlType_Fr: + rmsg = l1_to_rtppayload_fr(payload, payload_len, lchan); + break; + case GsmL1_TchPlType_Hr: + rmsg = l1_to_rtppayload_hr(payload, payload_len, lchan); + break; + case GsmL1_TchPlType_Efr: + rmsg = l1_to_rtppayload_efr(payload, payload_len, lchan); + break; + case GsmL1_TchPlType_Amr: + rmsg = l1_to_rtppayload_amr(payload, payload_len, lchan); + break; + case GsmL1_TchPlType_Amr_SidFirstP1: + memcpy(sid_first, payload, payload_len); + int len = osmo_amr_rtp_enc(sid_first, 0, AMR_SID, AMR_GOOD); + if (len < 0) + return 0; + rmsg = l1_to_rtppayload_amr(sid_first, len, lchan); + break; + } + + if (rmsg) + return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn, + data_ind->measParam.fBer * 10000, + data_ind->measParam.fLinkQuality * 10); + + return 0; + +err_payload_match: + LOGPFN(DL1P, LOGL_ERROR, data_ind->u32Fn, "%s Rx Payload Type %s incompatible with lchan\n", + gsm_lchan_name(lchan), get_value_string(lc15bts_tch_pl_names, payload_type)); + return -EINVAL; +} + +struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan, uint32_t fn) +{ + struct msgb *msg; + GsmL1_Prim_t *l1p; + GsmL1_PhDataReq_t *data_req; + GsmL1_MsgUnitParam_t *msu_param; + uint8_t *payload_type; + uint8_t *l1_payload; + int rc; + + msg = l1p_msgb_alloc(); + if (!msg) + return NULL; + + l1p = msgb_l1prim(msg); + data_req = &l1p->u.phDataReq; + msu_param = &data_req->msgUnitParam; + payload_type = &msu_param->u8Buffer[0]; + l1_payload = &msu_param->u8Buffer[1]; + + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_AMR: + if (lchan->type == GSM_LCHAN_TCH_H && + dtx_dl_amr_enabled(lchan)) { + /* we have to explicitly handle sending SID FIRST P2 for + AMR HR in here */ + *payload_type = GsmL1_TchPlType_Amr_SidFirstP2; + rc = dtx_dl_amr_fsm_step(lchan, NULL, 0, fn, l1_payload, + false, &(msu_param->u8Size), + NULL); + if (rc == 0) + return msg; + } + *payload_type = GsmL1_TchPlType_Amr; + break; + case GSM48_CMODE_SPEECH_V1: + if (lchan->type == GSM_LCHAN_TCH_F) + *payload_type = GsmL1_TchPlType_Fr; + else + *payload_type = GsmL1_TchPlType_Hr; + break; + case GSM48_CMODE_SPEECH_EFR: + *payload_type = GsmL1_TchPlType_Efr; + break; + default: + msgb_free(msg); + return NULL; + } + + rc = repeat_last_sid(lchan, l1_payload, fn); + if (!rc) { + msgb_free(msg); + return NULL; + } + msu_param->u8Size = rc; + + return msg; +} diff --git a/src/osmo-bts-litecell15/utils.c b/src/osmo-bts-litecell15/utils.c new file mode 100644 index 0000000..8c3eb5a --- /dev/null +++ b/src/osmo-bts-litecell15/utils.c @@ -0,0 +1,115 @@ +/* Helper utilities that are used in OMLs */ + +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * (C) 2011-2013 by Harald Welte + * (C) 2013 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include "utils.h" + +#include +#include +#include + +#include "lc15bts.h" +#include "l1_if.h" + +int band_lc152osmo(GsmL1_FreqBand_t band) +{ + switch (band) { + case GsmL1_FreqBand_850: + return GSM_BAND_850; + case GsmL1_FreqBand_900: + return GSM_BAND_900; + case GsmL1_FreqBand_1800: + return GSM_BAND_1800; + case GsmL1_FreqBand_1900: + return GSM_BAND_1900; + default: + return -1; + } +} + +static int band_osmo2lc15(struct gsm_bts_trx *trx, enum gsm_band osmo_band) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + + /* check if the TRX hardware actually supports the given band */ + if (!(fl1h->hw_info.band_support & osmo_band)) + return -1; + + /* if yes, convert from osmcoom style band definition to L1 band */ + switch (osmo_band) { + case GSM_BAND_850: + return GsmL1_FreqBand_850; + case GSM_BAND_900: + return GsmL1_FreqBand_900; + case GSM_BAND_1800: + return GsmL1_FreqBand_1800; + case GSM_BAND_1900: + return GsmL1_FreqBand_1900; + default: + return -1; + } +} + +/** + * Select the band that matches the ARFCN. In general the ARFCNs + * for GSM1800 and GSM1900 overlap and one needs to specify the + * rightband. When moving between GSM900/GSM1800 and GSM850/1900 + * modifying the BTS configuration is a bit annoying. The auto-band + * configuration allows to ease with this transition. + */ +int lc15bts_select_lc15_band(struct gsm_bts_trx *trx, uint16_t arfcn) +{ + enum gsm_band band; + struct gsm_bts *bts = trx->bts; + + if (!bts->auto_band) + return band_osmo2lc15(trx, bts->band); + + /* + * We need to check what will happen now. + */ + band = gsm_arfcn2band(arfcn); + + /* if we are already on the right band return */ + if (band == bts->band) + return band_osmo2lc15(trx, bts->band); + + /* Check if it is GSM1800/GSM1900 */ + if (band == GSM_BAND_1800 && bts->band == GSM_BAND_1900) + return band_osmo2lc15(trx, bts->band); + + /* + * Now to the actual autobauding. We just want DCS/DCS and + * PCS/PCS for PCS we check for 850/1800 though + */ + if ((band == GSM_BAND_900 && bts->band == GSM_BAND_1800) + || (band == GSM_BAND_1800 && bts->band == GSM_BAND_900) + || (band == GSM_BAND_850 && bts->band == GSM_BAND_1900)) + return band_osmo2lc15(trx, band); + if (band == GSM_BAND_1800 && bts->band == GSM_BAND_850) + return band_osmo2lc15(trx, GSM_BAND_1900); + + /* give up */ + return -1; +} diff --git a/src/osmo-bts-litecell15/utils.h b/src/osmo-bts-litecell15/utils.h new file mode 100644 index 0000000..a2a2234 --- /dev/null +++ b/src/osmo-bts-litecell15/utils.h @@ -0,0 +1,13 @@ +#ifndef _UTILS_H +#define _UTILS_H + +#include +#include "lc15bts.h" + +struct gsm_bts_trx; + +int band_lc152osmo(GsmL1_FreqBand_t band); + +int lc15bts_select_lc15_band(struct gsm_bts_trx *trx, uint16_t arfcn); + +#endif diff --git a/src/osmo-bts-octphy/Makefile.am b/src/osmo-bts-octphy/Makefile.am new file mode 100644 index 0000000..ccdafaa --- /dev/null +++ b/src/osmo-bts-octphy/Makefile.am @@ -0,0 +1,13 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include $(OCTSDR2G_INCDIR) +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(ORTP_CFLAGS) +COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) $(ORTP_LIBS) + +EXTRA_DIST = l1_if.h l1_oml.h l1_utils.h octphy_hw_api.h octpkt.h + +bin_PROGRAMS = osmo-bts-octphy + +COMMON_SOURCES = main.c l1_if.c l1_oml.c l1_utils.c l1_tch.c octphy_hw_api.c octphy_vty.c octpkt.c + +osmo_bts_octphy_SOURCES = $(COMMON_SOURCES) +osmo_bts_octphy_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) + diff --git a/src/osmo-bts-octphy/l1_if.c b/src/osmo-bts-octphy/l1_if.c new file mode 100644 index 0000000..f178029 --- /dev/null +++ b/src/osmo-bts-octphy/l1_if.c @@ -0,0 +1,1800 @@ +/* Layer 1 (PHY) interface of osmo-bts OCTPHY integration */ + +/* Copyright (c) 2014 Octasic Inc. All rights reserved. + * Copyright (c) 2015-2016 Harald Welte + * + * based on a copy of osmo-bts-sysmo/l1_if.c, which is + * Copyright (C) 2011-2014 by Harald Welte + * Copyright (C) 2014 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "l1_if.h" +#include "l1_oml.h" +#include "l1_utils.h" + +#include "octpkt.h" +#include + +/* NOTE: The octphy GPRS frame number handling changed with + * OCTSDR-2G-02.07.00-B1314-BETA. From that version on, each ph_data_ind must + * subtract 3 from the frame number before passing the frame to the PCU */ +#define cOCTVC1_MAIN_VERSION_ID_FN_PARADIGM_CHG 0x41c0522 + +#include +#define OCTVC1_RC2STRING_DECLARE +#include +#define OCTVC1_ID2STRING_DECLARE +#include +#include +#define OCTVC1_OPT_DECLARE_DEFAULTS +#include +#include + +#define cPKTAPI_FIFO_ID_MSG 0xAAAA0001 + +/* maximum window of unacknowledged commands */ +#define UNACK_CMD_WINDOW 8 +/* maximum number of re-transmissions of a command */ +#define MAX_RETRANS 3 +/* timeout until which we expect PHY to respond */ +#define CMD_TIMEOUT 5 + +/* allocate a msgb for a Layer1 primitive */ +struct msgb *l1p_msgb_alloc(void) +{ + struct msgb *msg = msgb_alloc_headroom(1500, 24, "l1_prim"); + if (!msg) + return msg; + + msg->l2h = msg->data; + return msg; +} + +void l1if_fill_msg_hdr(tOCTVC1_MSG_HEADER *mh, struct msgb *msg, + struct octphy_hdl *fl1h, uint32_t msg_type, uint32_t api_cmd) +{ + octvc1_fill_msg_hdr(mh, msgb_l2len(msg), fl1h->session_id, + fl1h->next_trans_id++, 0 /* user_info */, + msg_type, 0, api_cmd); +} + +/* Map OSMOCOM BAND type to Octasic type */ +tOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM +osmocom_to_octphy_band(enum gsm_band osmo_band, unsigned int arfcn) +{ + switch (osmo_band) { + case GSM_BAND_450: + return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_450; + case GSM_BAND_850: + return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_850; + case GSM_BAND_900: + if (arfcn == 0) + return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_E_900; + else if (arfcn >= 955 && arfcn <= 974) + return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_R_900; + else if (arfcn >= 975 && arfcn <= 1023) + return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_E_900; + else + return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_P_900; + case GSM_BAND_1800: + return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_DCS_1800; + case GSM_BAND_1900: + return cOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM_PCS_1900; + default: + return -EINVAL; + } +}; + +struct gsm_bts_trx *trx_by_l1h(struct octphy_hdl *fl1h, unsigned int trx_id) +{ + struct phy_instance *pinst; + + pinst = phy_instance_by_num(fl1h->phy_link, trx_id); + if (!pinst) + return NULL; + + return pinst->trx; +} + +struct gsm_lchan *get_lchan_by_lchid(struct gsm_bts_trx *trx, + tOCTVC1_GSM_LOGICAL_CHANNEL_ID *lch_id) +{ + unsigned int lchan_idx; + + OSMO_ASSERT(lch_id->byTimeslotNb < ARRAY_SIZE(trx->ts)); + if (lch_id->bySubChannelNb == cOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM_ALL) { + switch (lch_id->bySAPI) { + case cOCTVC1_GSM_SAPI_ENUM_FCCH: + case cOCTVC1_GSM_SAPI_ENUM_SCH: + case cOCTVC1_GSM_SAPI_ENUM_BCCH: + case cOCTVC1_GSM_SAPI_ENUM_PCH_AGCH: + case cOCTVC1_GSM_SAPI_ENUM_RACH: + lchan_idx = 4; + break; + case cOCTVC1_GSM_SAPI_ENUM_CBCH: + /* it is always index 2 (3rd element), whether in a + * combined CCCH+SDCCH4 or in a SDCCH8 */ + lchan_idx = 2; + break; + default: + lchan_idx = 0; + break; + } + } else + lchan_idx = lch_id->bySubChannelNb; + + OSMO_ASSERT(lchan_idx < ARRAY_SIZE(trx->ts[0].lchan)); + + return &trx->ts[lch_id->byTimeslotNb].lchan[lchan_idx]; +} + + +/* TODO: Unify with sysmobts? */ +struct wait_l1_conf { + /* list of wait_l1_conf in the phy handle */ + struct llist_head list; + /* expiration timer */ + struct osmo_timer_list timer; + /* primtivie / command ID */ + uint32_t prim_id; + /* transaction ID */ + uint32_t trans_id; + /* copy of the msgb containing the command */ + struct msgb *cmd_msg; + /* call-back to call on response */ + l1if_compl_cb *cb; + /* data to hand to call-back on response */ + void *cb_data; + /* number of re-transmissions so far */ + uint32_t num_retrans; +}; + +static void release_wlc(struct wait_l1_conf *wlc) +{ + osmo_timer_del(&wlc->timer); + msgb_free(wlc->cmd_msg); + talloc_free(wlc); +} + +static void l1if_req_timeout(void *data) +{ + struct wait_l1_conf *wlc = data; + + /* FIXME: Implement re-transmission of command on timer expiration */ + + LOGP(DL1C, LOGL_FATAL, "Timeout waiting for L1 primitive %s\n", + get_value_string(octphy_cid_vals, wlc->prim_id)); + exit(23); +} + +/* FIXME: this should be in libosmocore */ +static struct llist_head *llist_first(struct llist_head *head) +{ + if (llist_empty(head)) + return NULL; + return head->next; +} + +static void check_refill_window(struct octphy_hdl *fl1h, struct wait_l1_conf *recent) +{ + struct wait_l1_conf *wlc; + int space = UNACK_CMD_WINDOW - fl1h->wlc_list_len; + int i; + + for (i = 0; i < space; i++) { + /* get head of queue */ + struct llist_head *first = llist_first(&fl1h->wlc_postponed); + struct msgb *msg; + if (!first) + break; + wlc = llist_entry(first, struct wait_l1_conf, list); + + /* remove from head of postponed queue */ + llist_del(&wlc->list); + fl1h->wlc_postponed_len--; + + /* add to window */ + llist_add_tail(&wlc->list, &fl1h->wlc_list); + fl1h->wlc_list_len++; + + if (wlc != recent) { + LOGP(DL1C, LOGL_INFO, "Txing formerly postponed " + "command %s (trans_id=%u)\n", + get_value_string(octphy_cid_vals, wlc->prim_id), + wlc->trans_id); + } + msg = msgb_copy(wlc->cmd_msg, "Tx from wlc_postponed"); + /* queue for execution and response handling */ + if (osmo_wqueue_enqueue(&fl1h->phy_wq, msg) != 0) { + LOGP(DL1C, LOGL_ERROR, "Tx Write queue full. dropping msg\n"); + llist_del(&wlc->list); + msgb_free(msg); + exit(24); + } + /* schedule a timer for CMD_TIMEOUT seconds. If PHY fails to + * respond, we terminate */ + osmo_timer_schedule(&wlc->timer, CMD_TIMEOUT, 0); + + } +} + +/* send a request(command) to L1, scheduling a call-back to be executed + * on receiving the response*/ +int l1if_req_compl(struct octphy_hdl *fl1h, struct msgb *msg, + l1if_compl_cb *cb, void *data) +{ + struct wait_l1_conf *wlc; + + /* assume that there is a VC1 Message header and that it + * contains a command ID in network byte order */ + tOCTVC1_MSG_HEADER *msg_hdr = (tOCTVC1_MSG_HEADER *) msg->l2h; + uint32_t type_r_cmdid = ntohl(msg_hdr->ul_Type_R_CmdId); + uint32_t cmd_id = (type_r_cmdid >> cOCTVC1_MSG_ID_BIT_OFFSET) & cOCTVC1_MSG_ID_BIT_MASK; + + LOGP(DL1C, LOGL_DEBUG, "l1if_req_compl(msg_len=%u, cmd_id=%s, trans_id=%u)\n", + msgb_length(msg), octvc1_id2string(cmd_id), + ntohl(msg_hdr->ulTransactionId)); + + /* push the two common headers in front */ + octvocnet_push_ctl_hdr(msg, cOCTVC1_FIFO_ID_MGW_CONTROL, + cPKTAPI_FIFO_ID_MSG, fl1h->socket_id); + octpkt_push_common_hdr(msg, cOCTVOCNET_PKT_FORMAT_CTRL, 0, + cOCTPKT_HDR_CONTROL_PROTOCOL_TYPE_ENUM_OCTVOCNET); + + wlc = talloc_zero(fl1h, struct wait_l1_conf); + wlc->cmd_msg = msg; + wlc->cb = cb; + wlc->cb_data = data; + wlc->prim_id = cmd_id; + wlc->trans_id = ntohl(msg_hdr->ulTransactionId); + wlc->timer.data = wlc; + wlc->timer.cb = l1if_req_timeout; + + /* unconditionally add t to the tail of postponed commands */ + llist_add_tail(&wlc->list, &fl1h->wlc_postponed); + fl1h->wlc_postponed_len++; + + /* check if the unacknowledged window has some space to transmit */ + check_refill_window(fl1h, wlc); + + /* if any messages are in the queue, it must be at least 'our' message, + * as we always enqueue from the tail */ + if (fl1h->wlc_postponed_len) { + fl1h->stats.wlc_postponed++; + LOGP(DL1C, LOGL_INFO, "Postponed command %s (trans_id=%u)\n", + get_value_string(octphy_cid_vals, cmd_id), wlc->trans_id); + } + + return 0; +} + +/* For OctPHY, this only about sending state changes to BSC */ +int l1if_activate_rf(struct gsm_bts_trx *trx, int on) +{ + int i; + if (on) { + /* signal availability */ + oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK); + oml_mo_tx_sw_act_rep(&trx->mo); + oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK); + oml_mo_tx_sw_act_rep(&trx->bb_transc.mo); + + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) + oml_mo_state_chg(&trx->ts[i].mo, NM_OPSTATE_DISABLED, + NM_AVSTATE_DEPENDENCY); + } else { + oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, + NM_AVSTATE_OFF_LINE); + oml_mo_state_chg(&trx->bb_transc.mo, NM_OPSTATE_DISABLED, + NM_AVSTATE_OFF_LINE); + } + + return 0; +} + +static enum gsm_phys_chan_config pick_pchan(struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_PDCH: + if (ts->flags & TS_F_PDCH_ACTIVE) + return GSM_PCHAN_PDCH; + return GSM_PCHAN_TCH_F; + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + return ts->dyn.pchan_is; + default: + return ts->pchan; + } +} + +static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts, + tOCTVC1_GSM_SAPI_ENUM sapi, uint8_t subCh, + uint8_t u8Tn, uint32_t u32Fn) +{ + uint8_t cbits = 0; + enum gsm_phys_chan_config pchan = pick_pchan(ts); + + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH); + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH); + + switch (sapi) { + case cOCTVC1_GSM_SAPI_ENUM_BCCH: + cbits = 0x10; + break; + case cOCTVC1_GSM_SAPI_ENUM_SACCH: + switch (pchan) { + case GSM_PCHAN_TCH_F: + cbits = 0x01; + break; + case GSM_PCHAN_TCH_H: + cbits = 0x02 + subCh; + break; + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + cbits = 0x04 + subCh; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + cbits = 0x08 + subCh; + break; + default: + LOGP(DL1C, LOGL_ERROR, "SACCH for pchan %d?\n", pchan); + return 0; + } + break; + case cOCTVC1_GSM_SAPI_ENUM_SDCCH: + switch (pchan) { + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + cbits = 0x04 + subCh; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + cbits = 0x08 + subCh; + break; + default: + LOGP(DL1C, LOGL_ERROR, "SDCCH for pchan %d?\n", pchan); + return 0; + } + break; + case cOCTVC1_GSM_SAPI_ENUM_PCH_AGCH: + cbits = 0x12; + break; + case cOCTVC1_GSM_SAPI_ENUM_TCHF: + cbits = 0x01; + break; + case cOCTVC1_GSM_SAPI_ENUM_TCHH: + cbits = 0x02 + subCh; + break; + case cOCTVC1_GSM_SAPI_ENUM_FACCHF: + cbits = 0x01; + break; + case cOCTVC1_GSM_SAPI_ENUM_FACCHH: + cbits = 0x02 + subCh; + break; + case cOCTVC1_GSM_SAPI_ENUM_PDTCH: + case cOCTVC1_GSM_SAPI_ENUM_PACCH: + switch (pchan) { + case GSM_PCHAN_PDCH: + cbits = 0x01; + break; + default: + LOGP(DL1C, LOGL_ERROR, "PDTCH for pchan %d?\n", pchan); + return 0; + } + break; + case cOCTVC1_GSM_SAPI_ENUM_PTCCH: + if (!L1SAP_IS_PTCCH(u32Fn)) { + LOGP(DL1C, LOGL_FATAL, "Not expecting PTCCH at frame " + "number other than 12, got it at %u (%u). " + "Please fix!\n", u32Fn % 52, u32Fn); + abort(); + } + switch (pchan) { + case GSM_PCHAN_PDCH: + cbits = 0x01; + break; + default: + LOGP(DL1C, LOGL_ERROR, "PTCCH for pchan %d?\n", pchan); + return 0; + } + break; + default: + return 0; + } + return ((cbits << 3) | u8Tn); +} + +static void data_req_from_rts_ind(tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *data_req, + const tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EVT *rts_ind) +{ + data_req->TrxId = rts_ind->TrxId; + data_req->LchId = rts_ind->LchId; + data_req->Data.ulFrameNumber = rts_ind->ulFrameNumber; + data_req->Data.ulPayloadType = cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_NONE; +} + +#if 0 +static void empty_req_from_rts_ind(tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CMD * empty_req, + const tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EVT *rts_ind) +{ + empty_req->TrxId = rts_ind->TrxId; + empty_req->LchId = rts_ind->LchId; + empty_req->ulFrameNumber = rts_ind->ulFrameNumber; +} +#endif + +/*********************************************************************** + * handle messages coming down from generic part + ***********************************************************************/ + + +static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap) +{ + struct phy_instance *pinst = trx_phy_instance(trx); + struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; + struct msgb *l1msg = NULL; + uint32_t u32Fn; + uint8_t u8Tn, subCh, sapi = 0; + uint8_t chan_nr, link_id; + int len; + int rc; + + if (!msg) { + LOGPFN(DL1C, LOGL_FATAL, l1sap->u.data.fn, "L1SAP PH-DATA.req without msg. " + "Please fix!\n"); + abort(); + } + + len = msgb_l2len(msg); + + chan_nr = l1sap->u.data.chan_nr; + link_id = l1sap->u.data.link_id; + u32Fn = l1sap->u.data.fn; + u8Tn = L1SAP_CHAN2TS(chan_nr); + subCh = 0xf1; + if (L1SAP_IS_LINK_SACCH(link_id)) { + sapi = cOCTVC1_GSM_SAPI_ENUM_SACCH; + if (!L1SAP_IS_CHAN_TCHF(chan_nr) && !L1SAP_IS_CHAN_PDCH(chan_nr)) + subCh = l1sap_chan2ss(chan_nr); + } else if (L1SAP_IS_CHAN_TCHF(chan_nr) || L1SAP_IS_CHAN_PDCH(chan_nr)) { + if (ts_is_pdch(&trx->ts[u8Tn])) { + if (L1SAP_IS_PTCCH(u32Fn)) { + sapi = cOCTVC1_GSM_SAPI_ENUM_PTCCH; + } else { + sapi = cOCTVC1_GSM_SAPI_ENUM_PDTCH; + } + } else { + sapi = cOCTVC1_GSM_SAPI_ENUM_FACCHF; + } + } else if (L1SAP_IS_CHAN_TCHH(chan_nr)) { + subCh = L1SAP_CHAN2SS_TCHH(chan_nr); + sapi = cOCTVC1_GSM_SAPI_ENUM_FACCHH; + } else if (L1SAP_IS_CHAN_SDCCH4(chan_nr)) { + subCh = L1SAP_CHAN2SS_SDCCH4(chan_nr); + sapi = cOCTVC1_GSM_SAPI_ENUM_SDCCH; + } else if (L1SAP_IS_CHAN_SDCCH8(chan_nr)) { + subCh = L1SAP_CHAN2SS_SDCCH8(chan_nr); + sapi = cOCTVC1_GSM_SAPI_ENUM_SDCCH; + } else if (L1SAP_IS_CHAN_BCCH(chan_nr)) { + sapi = cOCTVC1_GSM_SAPI_ENUM_BCCH; + } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) { + sapi = cOCTVC1_GSM_SAPI_ENUM_PCH_AGCH; + } else { + LOGPFN(DL1C, LOGL_NOTICE, u32Fn, "unknown prim %d op %d chan_nr %d link_id %d\n", + l1sap->oph.primitive, l1sap->oph.operation, chan_nr, link_id); + rc = -EINVAL; + goto done; + } + + if (len) { + /* create new PHY primitive in l1msg, copying payload */ + + l1msg = l1p_msgb_alloc(); + if (!l1msg) { + LOGPFN(DL1C, LOGL_FATAL, u32Fn, "L1SAP PH-DATA.req msg alloc failed\n"); + rc = -ENOMEM; + goto done; + } + + tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *data_req = + (tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *) + msgb_put(l1msg, sizeof(*data_req)); + + l1if_fill_msg_hdr(&data_req->Header, l1msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CID); + + data_req->TrxId.byTrxId = pinst->u.octphy.trx_id; + data_req->LchId.byTimeslotNb = u8Tn; + data_req->LchId.bySAPI = sapi; + data_req->LchId.bySubChannelNb = subCh; + data_req->LchId.byDirection = cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS; + data_req->Data.ulFrameNumber = u32Fn; + data_req->Data.ulDataLength = msgb_l2len(msg); + memcpy(data_req->Data.abyDataContent, msg->l2h, msgb_l2len(msg)); + + mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD_SWAP(data_req); + } else { + /* No data available, Don't send Empty frame to PHY */ + rc = 0; + goto done; + } + + rc = l1if_req_compl(fl1h, l1msg, NULL, NULL); +done: + return rc; +} + + +static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap) +{ + struct phy_instance *pinst = trx_phy_instance(trx); + struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; + struct gsm_lchan *lchan; + uint32_t u32Fn; + uint8_t u8Tn, subCh, sapi; + uint8_t chan_nr; + struct msgb *nmsg = NULL; + + chan_nr = l1sap->u.tch.chan_nr; + u32Fn = l1sap->u.tch.fn; + u8Tn = L1SAP_CHAN2TS(chan_nr); + if (L1SAP_IS_CHAN_TCHH(chan_nr)) { + subCh = L1SAP_CHAN2SS_TCHH(chan_nr); + sapi = cOCTVC1_GSM_SAPI_ENUM_TCHH; + } else { + subCh = 0xf1; + sapi = cOCTVC1_GSM_SAPI_ENUM_TCHF; + } + + lchan = get_lchan_by_chan_nr(trx, chan_nr); + + /* create new message and fill data */ + if (msg) { + nmsg = l1p_msgb_alloc(); + if (!nmsg) { + LOGPFN(DL1C, LOGL_FATAL, u32Fn, "L1SAP PH-TCH.req msg alloc failed\n"); + return -ENOMEM; + } + + msgb_pull(msg, sizeof(*l1sap)); + tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *data_req = + (tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *) + msgb_put(nmsg, sizeof(*data_req)); + + mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD_DEF(data_req); + + l1if_fill_msg_hdr(&data_req->Header, nmsg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CID); + + data_req->TrxId.byTrxId = pinst->u.octphy.trx_id; + data_req->LchId.byTimeslotNb = u8Tn; + data_req->LchId.bySAPI = sapi; + data_req->LchId.bySubChannelNb = subCh; + data_req->LchId.byDirection = + cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS; + data_req->Data.ulFrameNumber = u32Fn; + + l1if_tch_encode(lchan, + &data_req->Data.ulPayloadType, + data_req->Data.abyDataContent, + &data_req->Data.ulDataLength, + msg->data, msg->len); + + mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD_SWAP(data_req); + } else { + /* No data available, Don't send Empty frame to PHY */ + return 0; + } + + return l1if_req_compl(fl1h, nmsg, NULL, NULL); +} + +static int mph_info_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap) +{ + uint8_t chan_nr; + struct gsm_lchan *lchan; + int rc = 0; + + switch (l1sap->u.info.type) { + case PRIM_INFO_ACT_CIPH: + chan_nr = l1sap->u.info.u.ciph_req.chan_nr; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + if (l1sap->u.info.u.ciph_req.uplink) { + l1if_set_ciphering(lchan, 0); + lchan->ciph_state = LCHAN_CIPH_RX_REQ; + } + if (l1sap->u.info.u.ciph_req.downlink) { + l1if_set_ciphering(lchan, 1); + lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ; + } + if (l1sap->u.info.u.ciph_req.downlink + && l1sap->u.info.u.ciph_req.uplink) + lchan->ciph_state = LCHAN_CIPH_RXTX_REQ; + break; + case PRIM_INFO_ACTIVATE: + case PRIM_INFO_DEACTIVATE: + case PRIM_INFO_MODIFY: + chan_nr = l1sap->u.info.u.act_req.chan_nr; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + + if (l1sap->u.info.type == PRIM_INFO_ACTIVATE) + l1if_rsl_chan_act(lchan); + else if (l1sap->u.info.type == PRIM_INFO_MODIFY) { +#pragma message ("Mode Modify is currently not supported for Octasic PHY (OS#3015)") + /* l1if_rsl_mode_modify(lchan); */ + } else if (l1sap->u.info.u.act_req.sacch_only) + l1if_rsl_deact_sacch(lchan); + else + l1if_rsl_chan_rel(lchan); + break; + default: + LOGP(DL1C, LOGL_NOTICE, "unknown L1SAP MPH-INFO.req %d\n", + l1sap->u.info.type); + rc = -EINVAL; + } + + return rc; +} + +/* primitive from common part. We are taking ownership of msgb */ +int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) +{ + struct msgb *msg = l1sap->oph.msg; + int rc = 0; + + /* called functions MUST NOT take ownership of msgb, as it is + * free()d below */ + switch (OSMO_PRIM_HDR(&l1sap->oph)) { + case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST): + rc = ph_data_req(trx, msg, l1sap); + break; + case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST): + rc = ph_tch_req(trx, msg, l1sap); + break; + case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST): + rc = mph_info_req(trx, msg, l1sap); + break; + default: + LOGP(DL1C, LOGL_NOTICE, "L1SAP unknown prim %d op %d\n", + l1sap->oph.primitive, l1sap->oph.operation); + rc = -EINVAL; + } + + msgb_free(msg); + + return rc; +} + +static int trx_close_all_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data) +{ + tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_RSP *car = + (tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_RSP *) resp->l2h; + + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + + mOCTVC1_GSM_MSG_TRX_CLOSE_ALL_RSP_SWAP(car); + + /* we now know that the PHY link is connected */ + phy_link_state_set(fl1->phy_link, PHY_LINK_CONNECTED); + + msgb_free(resp); + + return 0; +} + +static int phy_link_trx_close_all(struct phy_link *plink) +{ + struct octphy_hdl *fl1h = plink->u.octphy.hdl; + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CMD *cac; + + cac = (tOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CMD *) + msgb_put(msg, sizeof(*cac)); + l1if_fill_msg_hdr(&cac->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CID); + + mOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CMD_SWAP(cac); + + return l1if_req_compl(fl1h, msg, trx_close_all_cb, NULL); +} + +int bts_model_phy_link_open(struct phy_link *plink) +{ + if (plink->u.octphy.hdl) + l1if_close(plink->u.octphy.hdl); + + phy_link_state_set(plink, PHY_LINK_CONNECTING); + + plink->u.octphy.hdl = l1if_open(plink); + if (!plink->u.octphy.hdl) { + phy_link_state_set(plink, PHY_LINK_SHUTDOWN); + return -1; + } + + /* do we need to iterate over the list of instances and do some + * instance-specific initialization? */ + + /* close all TRXs that might still exist in this link from + * previous execitions / sessions */ + phy_link_trx_close_all(plink); + + /* in the call-back to the above we will set the link state to + * connected */ + + return 0; +} + +int bts_model_init(struct gsm_bts *bts) +{ + LOGP(DL1C, LOGL_NOTICE, "model_init()\n"); + + bts->variant = BTS_OSMO_OCTPHY; + bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3); + + /* FIXME: what is the nominal transmit power of the PHY/board? */ + bts->c0->nominal_power = 15; + + gsm_bts_set_feature(bts, BTS_FEAT_GPRS); + gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS); +#if defined(cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_FCCH_SCH_BCCH_CCCH_SDCCH4_CBCH_SACCHC4) && defined(cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_SDCCH8_CBCH_SACCHC8) + gsm_bts_set_feature(bts, BTS_FEAT_CBCH); +#endif + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1); + + bts_model_vty_init(bts); + + return 0; +} + +/*********************************************************************** + * handling of messages coming up from PHY + ***********************************************************************/ + +/* When the measurement indication is received from the phy, the phy will + * automatically stamp it with the frame number that matches the frame + * number of the SACCH channel that marks the end of the measurement + * period. (e.g. fn%104=90, on a TCH/H, TS0). However, the upper layers + * expect the frame number to be aligned to the next SACCH frame after, + * after the end of the measurement period that has just passed. (e.g. + * (fn%104=10, on a TCH/H, TS0). The following function remaps the frame + * number in order to match the higher layers expectations. + * See also: 3GPP TS 05.02 Clause 7 Table 1 of 9 Mapping of logical channels + * onto physical channels (see subclauses 6.3, 6.4, 6.5) */ +static uint32_t translate_tch_meas_rep_fn104_reverse(uint32_t fn) +{ + uint8_t new_fn_mod; + uint8_t fn_mod; + + fn_mod = fn % 104; + + switch (fn_mod) { + case 103: + new_fn_mod = 25; + break; + case 12: + new_fn_mod = 38; + break; + case 25: + new_fn_mod = 51; + break; + case 38: + new_fn_mod = 64; + break; + case 51: + new_fn_mod = 77; + break; + case 64: + new_fn_mod = 90; + break; + case 77: + new_fn_mod = 103; + break; + case 90: + new_fn_mod = 12; + break; + default: + /* No translation for frame numbers + * fall out of the raster */ + new_fn_mod = fn_mod; + } + + return (fn - fn_mod) + new_fn_mod; +} + +static unsigned int oct_meas2ber10k(const tOCTVC1_GSM_MEASUREMENT_INFO *m) +{ + if (m->usBERTotalBitCnt != 0) { + return (unsigned int)((m->usBERCnt * BER_10K) / m->usBERTotalBitCnt); + } else { + return 0; + } +} + +static int oct_meas2rssi_dBm(const tOCTVC1_GSM_MEASUREMENT_INFO *m) +{ + /* rssi is in q8 format */ + return (m->sRSSIDbm >> 8); +} + +static void process_meas_res(struct gsm_bts_trx *trx, uint8_t chan_nr, + uint32_t fn, uint32_t data_len, + tOCTVC1_GSM_MEASUREMENT_INFO * m) +{ + struct osmo_phsap_prim l1sap; + + memset(&l1sap, 0, sizeof(l1sap)); + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, + PRIM_OP_INDICATION, NULL); + l1sap.u.info.type = PRIM_INFO_MEAS; + l1sap.u.info.u.meas_ind.chan_nr = chan_nr; + + /* Update Timing offset for valid radio block */ + if (data_len != 0) { + /* burst timing in 1x */ + l1sap.u.info.u.meas_ind.ta_offs_256bits = m->sBurstTiming4x*64; + } else { + /* FIXME, In current implementation, OCTPHY would send DATA_IND + * for all radio blocks (valid or invalid) But timing offset + * is only correct for valid block. so we need different + * counter to accumulate Timing offset.. even we add zero for + * invalid block.. timing offset average calucation would not + * correct. */ + l1sap.u.info.u.meas_ind.ta_offs_256bits = 0; + } + + l1sap.u.info.u.meas_ind.ber10k = oct_meas2ber10k(m); + + /* rssi is in q8 format */ + l1sap.u.info.u.meas_ind.inv_rssi = (uint8_t) oct_meas2rssi_dBm(m); + + /* copy logical frame number to MEAS IND data structure */ + l1sap.u.info.u.meas_ind.fn = translate_tch_meas_rep_fn104_reverse(fn); + + /* l1sap wants to take msgb ownership. However, as there is no + * msg, it will msgb_free(l1sap.oph.msg == NULL) */ + l1sap_up(trx, &l1sap); +} + +static void dump_meas_res(int ll, tOCTVC1_GSM_MEASUREMENT_INFO * m) +{ + LOGP(DMEAS, ll, + "Meas: RSSI %d dBm, Burst Timing %d Quarter of bits :%d, " + "BER Error Count %d , BER Toatal Bit count %d in last decoded frame\n", + m->sRSSIDbm, m->sBurstTiming, m->sBurstTiming4x, m->usBERCnt, + m->usBERTotalBitCnt); +} + +static int handle_mph_time_ind(struct octphy_hdl *fl1, uint8_t trx_id, uint32_t fn) +{ + struct gsm_bts_trx *trx = trx_by_l1h(fl1, trx_id); + struct osmo_phsap_prim l1sap; + + /* increment the primitive count for the alive timer */ + fl1->alive_prim_cnt++; + + /* ignore every time indication, except for c0 */ + if (trx != trx->bts->c0) + return 0; + + memset(&l1sap, 0, sizeof(l1sap)); + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, + PRIM_OP_INDICATION, NULL); + l1sap.u.info.type = PRIM_INFO_TIME; + l1sap.u.info.u.time_ind.fn = fn; + + l1sap_up(trx, &l1sap); + + return 0; +} + +static int handle_ph_readytosend_ind(struct octphy_hdl *fl1, + tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EVT *evt, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = trx_by_l1h(fl1, evt->TrxId.byTrxId); + struct gsm_bts *bts = trx->bts; + struct osmo_phsap_prim *l1sap; + struct gsm_time g_time; + uint8_t chan_nr, link_id; + uint32_t fn; + int rc; + uint32_t t3p; + uint8_t ts_num, sc, sapi; + + struct msgb *resp_msg; + tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *data_req; + + /* Retrive the data */ + fn = evt->ulFrameNumber; + ts_num = (uint8_t) evt->LchId.byTimeslotNb; + sc = (uint8_t) evt->LchId.bySubChannelNb; + sapi = (uint8_t) evt->LchId.bySAPI; + + gsm_fn2gsmtime(&g_time, fn); + + DEBUGPGT(DL1P, &g_time, "Rx PH-RTS.ind SAPI=%s\n", + get_value_string(octphy_l1sapi_names, sapi)); + + /* in case we need to forward primitive to common part */ + chan_nr = chan_nr_by_sapi(&trx->ts[ts_num], sapi, sc, ts_num, fn); + if (chan_nr) { + if (sapi == cOCTVC1_GSM_SAPI_ENUM_SACCH) + link_id = LID_SACCH; + else + link_id = LID_DEDIC; + + rc = msgb_trim(l1p_msg, sizeof(*l1sap)); + if (rc < 0) + MSGB_ABORT(l1p_msg, "No room for primitive\n"); + l1sap = msgb_l1sap_prim(l1p_msg); + if (sapi == cOCTVC1_GSM_SAPI_ENUM_TCHF + || sapi == cOCTVC1_GSM_SAPI_ENUM_TCHH) { + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH_RTS, + PRIM_OP_INDICATION, l1p_msg); + l1sap->u.data.link_id = link_id; + l1sap->u.tch.chan_nr = chan_nr; + l1sap->u.tch.fn = fn; + } else { + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RTS, + PRIM_OP_INDICATION, l1p_msg); + l1sap->u.data.link_id = link_id; + l1sap->u.data.chan_nr = chan_nr; + l1sap->u.data.fn = fn; + } + + l1sap_up(trx, l1sap); + + /* return '1' to indicate l1sap_up has taken msgb ownership */ + return 1; + } + + /* in all other cases, we need to allocate a new PH-DATA.ind + * primitive msgb and start to fill it */ + resp_msg = l1p_msgb_alloc(); + data_req = (tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD *) + msgb_put(resp_msg, sizeof(*data_req)); + + mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD_DEF(data_req); + + l1if_fill_msg_hdr(&data_req->Header, resp_msg, fl1, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CID); + + data_req_from_rts_ind(data_req, evt); + + switch (sapi) { + /* TODO: SCH via L1SAP */ + case cOCTVC1_GSM_SAPI_ENUM_SCH: + /* compute T3prime */ + t3p = (g_time.t3 - 1) / 10; + /* fill SCH burst with data */ + data_req->Data.ulDataLength = 4; + data_req->Data.abyDataContent[0] = + (bts->bsic << 2) | (g_time.t1 >> 9); + data_req->Data.abyDataContent[1] = (g_time.t1 >> 1); + data_req->Data.abyDataContent[2] = + (g_time.t1 << 7) | (g_time.t2 << 2) | (t3p >> 1); + data_req->Data.abyDataContent[3] = (t3p & 1); + break; + case cOCTVC1_GSM_SAPI_ENUM_CBCH: + rc = bts_cbch_get(bts, data_req->Data.abyDataContent, &g_time); + data_req->Data.ulDataLength = 23; /* GSM MAX BLK SIZE */ + break; + case cOCTVC1_GSM_SAPI_ENUM_PRACH: +#if 0 + /* in case we decide to send an empty frame... */ + + tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CMD + *empty_frame_req = + (tOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CMD + *) msgSendBuffer; + + empty_req_from_rts_ind(empty_frame_req, evt); + + /* send empty frame request */ + rc = Logical_Channel_Empty_Frame_Cmd(empty_frame_req); + if (cOCTVC1_RC_OK != rc) { + LOGPGT(DL1P, LOGL_ERROR, &g_time, + "Sending Empty Frame Request Failed! (%s)\n", + octvc1_rc2string(rc)); + } + break; +#endif + default: + LOGPGT(DL1P, LOGL_ERROR, &g_time, "SAPI %s not handled via L1SAP!\n", + get_value_string(octphy_l1sapi_names, sapi)); +#if 0 + data_req->Data.ulDataLength = GSM_MACBLOCK_LEN; + memcpy(data_req->Data.abyDataContent, fill_frame, + GSM_MACBLOCK_LEN); +#endif + break; + } + + mOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CMD_SWAP(data_req); + + return l1if_req_compl(fl1, resp_msg, NULL, NULL); +} + +static int handle_ph_data_ind(struct octphy_hdl *fl1, + tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EVT *data_ind, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = trx_by_l1h(fl1, data_ind->TrxId.byTrxId); + uint8_t chan_nr, link_id; + struct osmo_phsap_prim *l1sap; + uint32_t fn; + uint8_t *data; + uint16_t len; + int16_t snr; + int rc; + + uint8_t sapi = (uint8_t) data_ind->LchId.bySAPI; + uint8_t ts_num = (uint8_t) data_ind->LchId.byTimeslotNb; + uint8_t sc = (uint8_t) data_ind->LchId.bySubChannelNb; + + /* Need to combine two 16bit MSB and LSB to form 32bit FN */ + fn = data_ind->Data.ulFrameNumber; + + /* chan_nr and link_id */ + chan_nr = chan_nr_by_sapi(&trx->ts[ts_num], sapi, sc, ts_num, fn); + if (!chan_nr) { + LOGPFN(DL1C, LOGL_ERROR, fn, "Rx PH-DATA.ind for unknown L1 SAPI %s\n", + get_value_string(octphy_l1sapi_names, sapi)); + return ENOTSUP; + } + + if (sapi == cOCTVC1_GSM_SAPI_ENUM_SACCH) + link_id = LID_SACCH; + else + link_id = LID_DEDIC; + + memset(&l1sap, 0, sizeof(l1sap)); + + /* uplink measurement */ + process_meas_res(trx, chan_nr, fn, data_ind->Data.ulDataLength, + &data_ind->MeasurementInfo); + + DEBUGPFN(DL1C, fn, "Rx PH-DATA.ind %s: %s data_len:%d \n", + get_value_string(octphy_l1sapi_names, sapi), + osmo_hexdump(data_ind->Data.abyDataContent, data_ind->Data.ulDataLength), + data_ind->Data.ulDataLength); + + /* check for TCH */ + if (sapi == cOCTVC1_GSM_SAPI_ENUM_TCHF || + sapi == cOCTVC1_GSM_SAPI_ENUM_TCHH) { + /* TCH speech frame handling */ + rc = l1if_tch_rx(trx, chan_nr, data_ind); + return rc; + } + + /* get data pointer and length */ + data = data_ind->Data.abyDataContent; + len = data_ind->Data.ulDataLength; + /* pull lower header part before data */ + msgb_pull(l1p_msg, data - l1p_msg->data); + /* trim remaining data to it's size, to get rid of upper header part */ + rc = msgb_trim(l1p_msg, len); + if (rc < 0) + MSGB_ABORT(l1p_msg, "No room for primitive data\n"); + l1p_msg->l2h = l1p_msg->data; + /* push new l1 header */ + l1p_msg->l1h = msgb_push(l1p_msg, sizeof(*l1sap)); + /* fill header */ + l1sap = msgb_l1sap_prim(l1p_msg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA, + PRIM_OP_INDICATION, l1p_msg); + l1sap->u.data.link_id = link_id; + l1sap->u.data.chan_nr = chan_nr; + +#if (cOCTVC1_MAIN_VERSION_ID >= cOCTVC1_MAIN_VERSION_ID_FN_PARADIGM_CHG) + if (sapi == cOCTVC1_GSM_SAPI_ENUM_PDTCH) { + /* FIXME::PCU is expecting encode frame number*/ + l1sap->u.data.fn = fn - 3; + } else + l1sap->u.data.fn = fn; +#else + l1sap->u.data.fn = fn; +#endif + + l1sap->u.data.rssi = oct_meas2rssi_dBm(&data_ind->MeasurementInfo); + l1sap->u.data.ber10k = oct_meas2ber10k(&data_ind->MeasurementInfo); + + /* burst timing in 1x but PCU is expecting 4X */ + l1sap->u.data.ta_offs_256bits = data_ind->MeasurementInfo.sBurstTiming4x*64; + snr = data_ind->MeasurementInfo.sSNRDb; + /* FIXME: better converion formulae for SnR -> C / I? + l1sap->u.data.lqual_cb = (snr ? snr : (snr - 65536)) * 10 / 256; + LOGP(DL1C, LOGL_ERROR, "SnR: raw %d, computed %d\n", snr, l1sap->u.data.lqual_cb); + */ + l1sap->u.data.lqual_cb = (snr ? snr : (snr - 65536)) * 100; + l1sap->u.data.pdch_presence_info = PRES_INFO_BOTH; /* FIXME: consider EDGE support */ + + l1sap_up(trx, l1sap); + + /* return '1' to indicate that l1sap_up has taken msgb ownership */ + return 1; +} + +static int handle_ph_rach_ind(struct octphy_hdl *fl1, + tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EVT *ra_ind, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = trx_by_l1h(fl1, ra_ind->TrxId.byTrxId); + struct osmo_phsap_prim *l1sap; + int rc; + struct ph_rach_ind_param rach_ind_param; + + dump_meas_res(LOGL_DEBUG, &ra_ind->MeasurementInfo); + + if (ra_ind->ulMsgLength != 1) { + LOGPFN(DL1C, LOGL_ERROR, ra_ind->ulFrameNumber, + "Rx PH-RACH.ind has lenghth %d > 1\n", ra_ind->ulMsgLength); + msgb_free(l1p_msg); + return 0; + } + + /* We need to evaluate ra_ind before below msgb_trim(), since that invalidates *ra_ind. */ + rach_ind_param = (struct ph_rach_ind_param) { + /* .chan_nr set below */ + .ra = ra_ind->abyMsg[0], + /* .acc_delay set below */ + .fn = ra_ind->ulFrameNumber, + .is_11bit = 0, + /* .burst_type remains unset */ + .rssi = oct_meas2rssi_dBm(&ra_ind->MeasurementInfo), + .ber10k = oct_meas2ber10k(&ra_ind->MeasurementInfo), + .acc_delay_256bits = ra_ind->MeasurementInfo.sBurstTiming4x * 64, + }; + + if (ra_ind->LchId.bySubChannelNb == cOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM_ALL && + ra_ind->LchId.bySAPI == cOCTVC1_GSM_SAPI_ENUM_RACH) { + rach_ind_param.chan_nr = 0x88; + } else { + struct gsm_lchan *lchan = get_lchan_by_lchid(trx, &ra_ind->LchId); + rach_ind_param.chan_nr = gsm_lchan2chan_nr(lchan); + } + + /* check for under/overflow / sign */ + if (ra_ind->MeasurementInfo.sBurstTiming < 0) + rach_ind_param.acc_delay = 0; + else + rach_ind_param.acc_delay = ra_ind->MeasurementInfo.sBurstTiming; + + /* msgb_trim() invalidates ra_ind, make that abundantly clear: */ + ra_ind = NULL; + rc = msgb_trim(l1p_msg, sizeof(*l1sap)); + if (rc < 0) + MSGB_ABORT(l1p_msg, "No room for primitive\n"); + l1sap = msgb_l1sap_prim(l1p_msg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, + l1p_msg); + l1sap->u.rach_ind = rach_ind_param; + + l1sap_up(trx, l1sap); + + /* return '1' to indicate l1sap_up has taken msgb ownership */ + return 1; +} + +static int rx_gsm_trx_time_ind(struct msgb *msg) +{ + struct octphy_hdl *fl1h = msg->dst; + tOCTVC1_GSM_MSG_TRX_TIME_INDICATION_EVT *tind = + (tOCTVC1_GSM_MSG_TRX_TIME_INDICATION_EVT *) msg->l2h; + + mOCTVC1_GSM_MSG_TRX_TIME_INDICATION_EVT_SWAP(tind); + + return handle_mph_time_ind(fl1h, tind->TrxId.byTrxId, tind->ulFrameNumber); +} + +/* mark this message as RETRANSMIT of a previous msg */ +static void msg_set_retrans_flag(struct msgb *msg) +{ + tOCTVC1_MSG_HEADER *mh = (tOCTVC1_MSG_HEADER *) msg->l2h; + uint32_t type_r_cmdid = ntohl(mh->ul_Type_R_CmdId); + type_r_cmdid |= cOCTVC1_MSG_RETRANSMIT_FLAG; + mh->ul_Type_R_CmdId = htonl(type_r_cmdid); +} + +/* re-transmit all commands in the window that have a transaction ID lower than + * trans_id */ +static int retransmit_wlc_upto(struct octphy_hdl *fl1h, uint32_t trans_id) +{ + struct wait_l1_conf *wlc; + int count = 0; + + LOGP(DL1C, LOGL_INFO, "Retransmitting up to trans_id=%u\n", trans_id); + + /* trans_id represents the trans_id of the just-received response, we + * therefore need to re-send any commands with a lower trans_id */ + llist_for_each_entry(wlc, &fl1h->wlc_list, list) { + if (wlc->trans_id <= trans_id) { + struct msgb *msg; + if (wlc->num_retrans >= MAX_RETRANS) { + LOGP(DL1C, LOGL_ERROR, "Command %s: maximum " + "number of retransmissions reached\n", + get_value_string(octphy_cid_vals, + wlc->prim_id)); + exit(24); + } + wlc->num_retrans++; + msg = msgb_copy(wlc->cmd_msg, "PHY CMD Retrans"); + msg_set_retrans_flag(msg); + osmo_wqueue_enqueue(&fl1h->phy_wq, msg); + osmo_timer_schedule(&wlc->timer, CMD_TIMEOUT, 0); + count++; + LOGP(DL1C, LOGL_INFO, "Re-transmitting %s " + "(trans_id=%u, attempt %u)\n", + get_value_string(octphy_cid_vals, wlc->prim_id), + wlc->trans_id, wlc->num_retrans); + } + } + + return count; +} + +/* Receive a response (to a prior command) from the PHY */ +static int rx_octvc1_resp(struct msgb *msg, uint32_t msg_id, uint32_t trans_id) +{ + tOCTVC1_MSG_HEADER *mh = (tOCTVC1_MSG_HEADER *) msg->l2h; + struct llist_head *first; + uint32_t return_code = ntohl(mh->ulReturnCode); + struct octphy_hdl *fl1h = msg->dst; + struct wait_l1_conf *wlc = NULL; + int rc; + + LOGP(DL1C, LOGL_DEBUG, "rx_octvc1_resp(msg_id=%s, trans_id=%u)\n", + octvc1_rc2string(msg_id), trans_id); + + /* check if the response is for the oldest (first) entry in wlc_list */ + first = llist_first(&fl1h->wlc_list); + if (first) { + wlc = llist_entry(first, struct wait_l1_conf, list); + if (wlc->trans_id == trans_id) { + /* process the received response */ + llist_del(&wlc->list); + fl1h->wlc_list_len--; + if (wlc->cb) { + /* call-back function must take msgb + * ownership. */ + rc = wlc->cb(fl1h, msg, wlc->cb_data); + } else { + rc = 0; + msgb_free(msg); + } + release_wlc(wlc); + /* check if there are postponed wlcs and re-fill the window */ + check_refill_window(fl1h, NULL); + return rc; + } + } + + LOGP(DL1C, LOGL_NOTICE, "Sequence error: Rx response (cmd=%s, trans_id=%u) " + "for cmd != oldest entry in window (trans_id=%u)!!\n", + get_value_string(octphy_cid_vals, msg_id), trans_id, + wlc ? wlc->trans_id : 0); + + /* check if the response is for any of the other entries in wlc_list */ + llist_for_each_entry(wlc, &fl1h->wlc_list, list) { + if (wlc->prim_id == msg_id && wlc->trans_id == trans_id) { + /* it is assumed that all of the previous response + * message(s) have been lost, and we need to + * re-transmit older messages from the window */ + rc = retransmit_wlc_upto(fl1h, trans_id); + fl1h->stats.retrans_cmds_trans_id += rc; + /* do not process the received response, we rather wait + * for the in-order retransmissions to arrive */ + msgb_free(msg); + return 0; + } + } + + /* ignore unhandled responses that went ok, but let the user know about + * failing ones. */ + if (return_code != cOCTVC1_RC_OK) { + LOGP(DL1C, LOGL_NOTICE, "Rx Unexpected response %s (trans_id=%u)\n", + get_value_string(octphy_cid_vals, msg_id), trans_id); + } + msgb_free(msg); + return 0; + +} + +static int rx_gsm_clockmgr_status_ind(struct msgb *msg) +{ + struct octphy_hdl *fl1h = msg->dst; + tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATUS_CHANGE_EVT *evt = + (tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATUS_CHANGE_EVT *) msg->l2h; + mOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATUS_CHANGE_EVT_SWAP(evt); + + LOGP(DL1C, LOGL_NOTICE, "Rx ClkMgr Status Change Event: " + "%s -> %s\n", + get_value_string(octphy_clkmgr_state_vals, evt->ulPreviousState), + get_value_string(octphy_clkmgr_state_vals, evt->ulState)); + + fl1h->clkmgr_state = evt->ulState; + + return 0; +} + +static int rx_gsm_trx_status_ind(struct msgb *msg) +{ + tOCTVC1_GSM_MSG_TRX_STATUS_CHANGE_EVT *evt = + (tOCTVC1_GSM_MSG_TRX_STATUS_CHANGE_EVT *) msg->l2h; + + mOCTVC1_GSM_MSG_TRX_STATUS_CHANGE_EVT_SWAP(evt); + + if (evt->ulStatus == cOCTVC1_GSM_TRX_STATUS_ENUM_RADIO_READY) + LOGP(DL1C, LOGL_INFO, "Rx TRX Status Event: READY\n"); + else + LOGP(DL1C, LOGL_ERROR, "Rx TRX Status Event: %u\n", + evt->ulStatus); + + return 0; +} + +/* DATA indication from PHY */ +static int rx_gsm_trx_lchan_data_ind(struct msgb *msg) +{ + tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EVT *evt = + (tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EVT *) msg->l2h; + mOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EVT_SWAP(evt); + + return handle_ph_data_ind(msg->dst, evt, msg); +} + +/* Ready-to-Send indication from PHY */ +static int rx_gsm_trx_rts_ind(struct msgb *msg) +{ + tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EVT *evt = + (tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EVT *) msg->l2h; + mOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EVT_SWAP(evt); + + return handle_ph_readytosend_ind(msg->dst, evt, msg); +} + +/* RACH receive indication from PHY */ +static int rx_gsm_trx_rach_ind(struct msgb *msg) +{ + tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EVT *evt = + (tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EVT *) msg->l2h; + mOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EVT_SWAP(evt); + + return handle_ph_rach_ind(msg->dst, evt, msg); +} + +/* Receive a notification (indication) from PHY */ +static int rx_octvc1_notif(struct msgb *msg, uint32_t msg_id) +{ + const char *evt_name = get_value_string(octphy_eid_vals, msg_id); + struct octphy_hdl *fl1h = msg->dst; + int rc = 0; + + if (!fl1h->opened) { + LOGP(DL1P, LOGL_NOTICE, "Rx NOTIF %s: Ignoring as PHY TRX " + "hasn't been re-opened yet\n", evt_name); + msgb_free(msg); + return 0; + } + + LOGP(DL1P, LOGL_DEBUG, "Rx NOTIF %s\n", evt_name); + + /* called functions MUST NOT take ownership of the msgb, + * as it is free()d below - unless they return 1 */ + switch (msg_id) { + case cOCTVC1_GSM_MSG_TRX_TIME_INDICATION_EID: + rc = rx_gsm_trx_time_ind(msg); + break; + case cOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATUS_CHANGE_EID: + rc = rx_gsm_clockmgr_status_ind(msg); + break; + case cOCTVC1_GSM_MSG_TRX_STATUS_CHANGE_EID: + rc = rx_gsm_trx_status_ind(msg); + break; + case cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EID: + rc = rx_gsm_trx_lchan_data_ind(msg); + break; + case cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EID: + rc = rx_gsm_trx_rts_ind(msg); + break; + case cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EID: + rc = rx_gsm_trx_rach_ind(msg); + break; + case cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RAW_DATA_INDICATION_EID: + LOGP(DL1P, LOGL_NOTICE, "Rx Unhandled event %s (%u)\n", + evt_name, msg_id); + break; + default: + LOGP(DL1P, LOGL_NOTICE, "Rx Unknown event %s (%u)\n", + evt_name, msg_id); + } + + /* Special return value '1' means: do not free */ + if (rc != 1) + msgb_free(msg); + + return rc; +} + +static int rx_octvc1_event_msg(struct msgb *msg) +{ + tOCTVC1_EVENT_HEADER *eh = (tOCTVC1_EVENT_HEADER *) msg->l2h; + uint32_t event_id = ntohl(eh->ulEventId); + uint32_t length = ntohl(eh->ulLength); + /* DO NOT YET SWAP HEADER HERE, as downstream functions want to + * swap it */ + + /* OCTSDKAN5001 Chapter 6.1 */ + if (length < 12 || length > 1024) { + LOGP(DL1C, LOGL_ERROR, "Rx EVENT length %u invalid\n", length); + msgb_free(msg); + return -1; + } + + /* verify / ensure length */ + if (msgb_l2len(msg) < length) { + LOGP(DL1C, LOGL_ERROR, "Rx EVENT msgb_l2len(%u) < " + "event_msg_length (%u)\n", msgb_l2len(msg), length); + msgb_free(msg); + return -1; + } + + return rx_octvc1_notif(msg, event_id); +} + +/* Receive a supervisory message from the PHY */ +static int rx_octvc1_supv(struct msgb *msg, uint32_t msg_id, uint32_t trans_id) +{ + struct octphy_hdl *fl1h = msg->dst; + tOCTVC1_MSG_HEADER *mh = (tOCTVC1_MSG_HEADER *) msg->l2h; + tOCTVC1_CTRL_MSG_MODULE_REJECT_SPV *rej; + uint32_t return_code = ntohl(mh->ulReturnCode); + uint32_t rejected_msg_id; + int rc; + + switch (msg_id) { + case cOCTVC1_CTRL_MSG_MODULE_REJECT_SID: + rej = (tOCTVC1_CTRL_MSG_MODULE_REJECT_SPV *) mh; + mOCTVC1_CTRL_MSG_MODULE_REJECT_SPV_SWAP(rej); + rejected_msg_id = (rej->ulRejectedCmdId >> cOCTVC1_MSG_ID_BIT_OFFSET) & + cOCTVC1_MSG_ID_BIT_MASK; + LOGP(DL1C, LOGL_NOTICE, "Rx REJECT_SID (TID=%u, " + "ExpectedTID=0x%08x, RejectedCmdID=%s)\n", + trans_id, rej->ulExpectedTransactionId, + get_value_string(octphy_cid_vals, rejected_msg_id)); + rc = retransmit_wlc_upto(fl1h, trans_id); + fl1h->stats.retrans_cmds_supv += rc; + break; + default: + LOGP(DL1C, LOGL_NOTICE, "Rx unhandled supervisory msg_id " + "%u: ReturnCode:%u\n", msg_id, return_code); + break; + } + + return 0; +} + +static int rx_octvc1_ctrl_msg(struct msgb *msg) +{ + tOCTVC1_MSG_HEADER *mh = (tOCTVC1_MSG_HEADER *) msg->l2h; + uint32_t length = ntohl(mh->ulLength); + uint32_t type_r_cmdid = ntohl(mh->ul_Type_R_CmdId); + uint32_t msg_type = (type_r_cmdid >> cOCTVC1_MSG_TYPE_BIT_OFFSET) & + cOCTVC1_MSG_TYPE_BIT_MASK; + uint32_t msg_id = (type_r_cmdid >> cOCTVC1_MSG_ID_BIT_OFFSET) & + cOCTVC1_MSG_ID_BIT_MASK; + uint32_t return_code = ntohl(mh->ulReturnCode); + const char *msg_name = get_value_string(octphy_cid_vals, msg_id); + + /* DO NOT YET SWAP HEADER HERE, as downstream functions want to + * swap it */ + + /* FIXME: OCTSDKAN5001 Chapter 3.1 states max size is 1024, but we see + * larger messages in practise */ + if (length < 24 || length > 2048) { + LOGP(DL1C, LOGL_ERROR, "Rx CTRL length %u invalid\n", length); + msgb_free(msg); + return -1; + } + + /* verify / ensure length */ + if (msgb_l2len(msg) < length) { + LOGP(DL1C, LOGL_ERROR, "Rx CTRL msgb_l2len(%u) < " + "ctrl_msg_length (%u)\n", msgb_l2len(msg), length); + msgb_free(msg); + return -1; + } + + LOGP(DL1P, LOGL_DEBUG, "Rx %s.resp (rc=%s(%x))\n", msg_name, + octvc1_rc2string(return_code), return_code); + + if (return_code != cOCTVC1_RC_OK) { + LOGP(DL1P, LOGL_ERROR, "%s failed, rc=%s\n", + msg_name, octvc1_rc2string(return_code)); + } + + /* called functions must take ownership of msgb */ + switch (msg_type) { + case cOCTVC1_MSG_TYPE_RESPONSE: + return rx_octvc1_resp(msg, msg_id, ntohl(mh->ulTransactionId)); + case cOCTVC1_MSG_TYPE_SUPERVISORY: + return rx_octvc1_supv(msg, msg_id, ntohl(mh->ulTransactionId)); + case cOCTVC1_MSG_TYPE_NOTIFICATION: + case cOCTVC1_MSG_TYPE_COMMAND: + LOGP(DL1C, LOGL_NOTICE, "Rx unhandled msg_type %s (%u)\n", + msg_name, msg_type); + msgb_free(msg); + break; + default: + LOGP(DL1P, LOGL_NOTICE, "Rx unknown msg_type %s (%u)\n", + msg_name, msg_type); + msgb_free(msg); + } + + return 0; +} + +static int rx_octvc1_data_f_msg(struct msgb *msg) +{ + tOCTVOCNET_PKT_DATA_F_HEADER *datafh = + (tOCTVOCNET_PKT_DATA_F_HEADER *) msg->l2h; + uint32_t log_obj_port = ntohl(datafh->VocNetHeader.ulLogicalObjPktPort); + + msg->l2h = (uint8_t *) datafh + sizeof(*datafh); + + if (log_obj_port == + cOCTVOCNET_PKT_DATA_LOGICAL_OBJ_PKT_PORT_EVENT_SESSION) { + uint32_t sub_type = ntohl(datafh->ulSubType) & 0xF; + if (sub_type == cOCTVOCNET_PKT_SUBTYPE_API_EVENT) { + /* called function must take msgb ownership */ + return rx_octvc1_event_msg(msg); + } else { + LOGP(DL1C, LOGL_ERROR, "Unknown DATA_F " + "subtype 0x%x\n", sub_type); + } + } else { + LOGP(DL1C, LOGL_ERROR, "Unknown logical object pkt port 0x%x\n", + log_obj_port); + } + + msgb_free(msg); + return 0; +} + +/* main receive routine for messages coming up from OCTPHY */ +static int rx_octphy_msg(struct msgb *msg) +{ + tOCTVOCNET_PKT_CTL_HEADER *ctlh; + int rc = 0; + + /* we assume that the packets start right with the OCTPKT header + * and that the ethernet hardware header has already been + * stripped before */ + msg->l1h = msg->data; + + uint32_t ch = ntohl(*(uint32_t *) msg->data); + uint32_t format = (ch >> cOCTVOCNET_PKT_FORMAT_BIT_OFFSET) + & cOCTVOCNET_PKT_FORMAT_BIT_MASK; + uint32_t len = (ch >> cOCTVOCNET_PKT_LENGTH_BIT_OFFSET) + & cOCTVOCNET_PKT_LENGTH_MASK; + + if (len > msgb_length(msg)) { + LOGP(DL1C, LOGL_ERROR, "Received length (%u) < length " + "as per packet header (%u): %s\n", msgb_length(msg), + len, osmo_hexdump(msgb_data(msg), msgb_length(msg))); + msgb_free(msg); + return -1; + } + + /* we first need to decode the common OCTPKT header and dispatch + * based on contrl (command/resp) or data (event=indication) */ + switch (format) { + case cOCTVOCNET_PKT_FORMAT_CTRL: + ctlh = (tOCTVOCNET_PKT_CTL_HEADER *) (msg->l1h + 4); + /* FIXME: check src/dest fifo, socket ID */ + msg->l2h = (uint8_t *) ctlh + sizeof(*ctlh); + /* called function must take msgb ownership */ + rc = rx_octvc1_ctrl_msg(msg); + break; + case cOCTVOCNET_PKT_FORMAT_F: + msg->l2h = msg->l1h + 4; + /* called function must take msgb ownership */ + rc = rx_octvc1_data_f_msg(msg); + break; + default: + LOGP(DL1C, LOGL_ERROR, "Rx Unknown pkt_format 0x%x\n", + format); + msgb_free(msg); + break; + } + + return rc; +} + +void bts_model_phy_link_set_defaults(struct phy_link *plink) +{ + /* configure some reasonable defaults, to be overridden by VTY */ + plink->u.octphy.rf_port_index = 0; + plink->u.octphy.rx_gain_db = 70; + plink->u.octphy.tx_atten_db = 0; +} + +void bts_model_phy_instance_set_defaults(struct phy_instance *pinst) +{ + pinst->u.octphy.trx_id = pinst->num; +} + +/*********************************************************************** + * octphy socket / main loop integration + ***********************************************************************/ + +static int octphy_read_cb(struct osmo_fd *ofd) +{ + struct sockaddr_ll sll; + socklen_t sll_len = sizeof(sll); + int rc; + struct msgb *msg = msgb_alloc_headroom(1500, 24, "PHY Rx"); + + if (!msg) + return -ENOMEM; + + /* this is the fl1h over which the message was received */ + msg->dst = ofd->data; + + rc = recvfrom(ofd->fd, msg->data, msgb_tailroom(msg), 0, + (struct sockaddr *) &sll, &sll_len); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "Error in recvfrom(): %s\n", + strerror(errno)); + msgb_free(msg); + return rc; + } + msgb_put(msg, rc); + + return rx_octphy_msg(msg); +} + +static int octphy_write_cb(struct osmo_fd *fd, struct msgb *msg) +{ + struct octphy_hdl *fl1h = fd->data; + int rc; + + /* send the message down the socket */ + rc = sendto(fd->fd, msg->data, msgb_length(msg), 0, + (struct sockaddr *) &fl1h->phy_addr, + sizeof(fl1h->phy_addr)); + + /* core write uqueue takes care of free() */ + if (rc < 0) { + LOGP(DL1P, LOGL_ERROR, "Tx to PHY has failed: %s\n", + strerror(errno)); + } + + return rc; +} + +struct octphy_hdl *l1if_open(struct phy_link *plink) +{ + struct octphy_hdl *fl1h; + struct ifreq ifr; + int sfd, rc; + char *phy_dev = plink->u.octphy.netdev_name; + + fl1h = talloc_zero(plink, struct octphy_hdl); + if (!fl1h) + return NULL; + + INIT_LLIST_HEAD(&fl1h->wlc_list); + INIT_LLIST_HEAD(&fl1h->wlc_postponed); + fl1h->phy_link = plink; + + if (!phy_dev) { + LOGP(DL1C, LOGL_ERROR, "You have to specify a octphy net-device\n"); + talloc_free(fl1h); + return NULL; + } + + LOGP(DL1C, LOGL_NOTICE, "Opening L1 interface for OctPHY (%s)\n", + phy_dev); + + sfd = osmo_sock_packet_init(SOCK_DGRAM, cOCTPKT_HDR_ETHERTYPE, + phy_dev, OSMO_SOCK_F_NONBLOCK); + if (sfd < 0) { + LOGP(DL1C, LOGL_FATAL, "Error opening PHY socket: %s\n", + strerror(errno)); + talloc_free(fl1h); + return NULL; + } + + /* resolve the string device name to an ifindex */ + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, phy_dev, sizeof(ifr.ifr_name)); + rc = ioctl(sfd, SIOCGIFINDEX, &ifr); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "Error using network device %s: %s\n", + phy_dev, strerror(errno)); + close(sfd); + talloc_free(fl1h); + return NULL; + } + + fl1h->session_id = rand(); + + /* set fl1h->phy_addr, which we use as sendto() destination */ + fl1h->phy_addr.sll_family = AF_PACKET; + fl1h->phy_addr.sll_protocol = htons(cOCTPKT_HDR_ETHERTYPE); + fl1h->phy_addr.sll_ifindex = ifr.ifr_ifindex; + fl1h->phy_addr.sll_hatype = ARPHRD_ETHER; + fl1h->phy_addr.sll_halen = ETH_ALEN; + /* plink->phy_addr.sll_addr is filled by bts_model_vty code */ + memcpy(fl1h->phy_addr.sll_addr, plink->u.octphy.phy_addr.sll_addr, + ETH_ALEN); + + /* Write queue / osmo_fd registration */ + osmo_wqueue_init(&fl1h->phy_wq, 10); + fl1h->phy_wq.write_cb = octphy_write_cb; + fl1h->phy_wq.read_cb = octphy_read_cb; + fl1h->phy_wq.bfd.fd = sfd; + fl1h->phy_wq.bfd.when = BSC_FD_READ; + fl1h->phy_wq.bfd.cb = osmo_wqueue_bfd_cb; + fl1h->phy_wq.bfd.data = fl1h; + rc = osmo_fd_register(&fl1h->phy_wq.bfd); + if (rc < 0) { + close(sfd); + talloc_free(fl1h); + return NULL; + } + + return fl1h; +} + +int l1if_close(struct octphy_hdl *fl1h) +{ + osmo_fd_unregister(&fl1h->phy_wq.bfd); + close(fl1h->phy_wq.bfd.fd); + talloc_free(fl1h); + + return 0; +} diff --git a/src/osmo-bts-octphy/l1_if.h b/src/osmo-bts-octphy/l1_if.h new file mode 100644 index 0000000..0960482 --- /dev/null +++ b/src/osmo-bts-octphy/l1_if.h @@ -0,0 +1,117 @@ +#pragma once + +#include +#include +#include + +#include + +#include +#include + +#include +#include + +#include +#include + +#include + +#define BER_10K 10000 + +struct octphy_hdl { + /* MAC address of the PHY */ + struct sockaddr_ll phy_addr; + + /* packet socket to talk with PHY */ + struct osmo_wqueue phy_wq; + + /* address parameters of the PHY */ + uint32_t session_id; + uint32_t next_trans_id; + uint32_t socket_id; + + /* clock manager state */ + uint32_t clkmgr_state; + + struct { + struct { + char *name; + char *description; + char *version; + } app; + struct { + char *platform; + char *version; + } system; + } info; + + /* This is a list of outstanding commands sent to the PHY, for which we + * currently still wait for a response. Represented by 'struct + * wait_l1_conf' in l1_if.c - Octasic calls this the 'Unacknowledged + * Command Window' */ + struct llist_head wlc_list; + int wlc_list_len; + struct { + /* messages retransmitted due to discontinuity of transaction + * ID in responses from PHY */ + uint32_t retrans_cmds_trans_id; + /* messages retransmitted due to supervisory messages by PHY */ + uint32_t retrans_cmds_supv; + /* number of commands/wlcs that we ever had to postpone */ + uint32_t wlc_postponed; + } stats; + + /* This is a list of wait_la_conf that OsmoBTS wanted to transmit to + * the PHY, but which couldn't yet been sent as the unacknowledged + * command window was full. */ + struct llist_head wlc_postponed; + int wlc_postponed_len; + + /* back pointer to the PHY link */ + struct phy_link *phy_link; + + struct osmo_timer_list alive_timer; + uint32_t alive_prim_cnt; + + /* were we already (re)opened after OsmoBTS start */ + int opened; +}; + +void l1if_fill_msg_hdr(tOCTVC1_MSG_HEADER *mh, struct msgb *msg, + struct octphy_hdl *fl1h, uint32_t msg_type, uint32_t api_cmd); + +typedef int l1if_compl_cb(struct octphy_hdl *fl1, struct msgb *l1_msg, void *data); + +/* send a request primitive to the L1 and schedule completion call-back */ +int l1if_req_compl(struct octphy_hdl *fl1h, struct msgb *msg, + l1if_compl_cb *cb, void *data); + +#include +struct gsm_lchan *get_lchan_by_lchid(struct gsm_bts_trx *trx, + tOCTVC1_GSM_LOGICAL_CHANNEL_ID *lch_id); + +struct octphy_hdl *l1if_open(struct phy_link *plink); +int l1if_close(struct octphy_hdl *hdl); + +int l1if_trx_open(struct gsm_bts_trx *trx); +int l1if_trx_close_all(struct gsm_bts *bts); +int l1if_enable_events(struct gsm_bts_trx *trx); + +int l1if_activate_rf(struct gsm_bts_trx *trx, int on); + +int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, + tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EVT * + data_ind); + +struct gsm_bts_trx *trx_by_l1h(struct octphy_hdl *fl1h, unsigned int trx_id); + +struct msgb *l1p_msgb_alloc(void); + +/* tch.c */ +void l1if_tch_encode(struct gsm_lchan *lchan, uint32_t *payload_type, + uint8_t *data, uint32_t *len, const uint8_t *rtp_pl, + unsigned int rtp_pl_len); + +tOCTVC1_RADIO_STANDARD_FREQ_BAND_GSM_ENUM +osmocom_to_octphy_band(enum gsm_band osmo_band, unsigned int arfcn); diff --git a/src/osmo-bts-octphy/l1_oml.c b/src/osmo-bts-octphy/l1_oml.c new file mode 100644 index 0000000..018a4f9 --- /dev/null +++ b/src/osmo-bts-octphy/l1_oml.c @@ -0,0 +1,1767 @@ +/* Layer 1 (PHY) interface of osmo-bts OCTPHY integration */ + +/* Copyright (c) 2014 Octasic Inc. All rights reserved. + * Copyright (c) 2015-2016 Harald Welte + * + * based on a copy of osmo-bts-sysmo/l1_oml.c, which is + * Copyright (C) 2011 by Harald Welte + * Copyright (C) 2013-2014 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "l1_if.h" +#include "l1_oml.h" +#include "l1_utils.h" +#include "octphy_hw_api.h" +#include "btsconfig.h" + +#include +#include +#include +#include +#include +#include + +bool no_fw_check = 0; + +#define LOGPTRX(byTrxId, level, fmt, args...) \ + LOGP(DL1C, level, "(byTrxId %u) " fmt, byTrxId, ## args) + +/* Map OSMOCOM logical channel type to OctPHY Logical channel type */ +static tOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM pchan_to_logChComb[_GSM_PCHAN_MAX] = +{ + [GSM_PCHAN_NONE] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_EMPTY, + [GSM_PCHAN_CCCH] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_FCCH_SCH_BCCH_CCCH, + [GSM_PCHAN_CCCH_SDCCH4] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_FCCH_SCH_BCCH_CCCH_SDCCH4_SACCHC4, + [GSM_PCHAN_TCH_F] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_TCHF_FACCHF_SACCHTF, + [GSM_PCHAN_TCH_H] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_TCHH_FACCHH_SACCHTH, + [GSM_PCHAN_SDCCH8_SACCH8C] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_SDCCH8_SACCHC8, + // TODO - watch out below two!!! + [GSM_PCHAN_PDCH] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_PDTCHF_PACCHF_PTCCHF, + [GSM_PCHAN_TCH_F_PDCH] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_PDTCHF_PACCHF_PTCCHF, +#ifdef cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_FCCH_SCH_BCCH_CCCH_SDCCH4_CBCH_SACCHC4 + [GSM_PCHAN_CCCH_SDCCH4_CBCH] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_FCCH_SCH_BCCH_CCCH_SDCCH4_CBCH_SACCHC4, +#endif +#ifdef cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_SDCCH8_CBCH_SACCHC8 + [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_SDCCH8_CBCH_SACCHC8, +#endif + [GSM_PCHAN_UNKNOWN] = cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_EMPTY +}; + +enum sapi_cmd_type { + SAPI_CMD_ACTIVATE, + SAPI_CMD_CONFIG_CIPHERING, + SAPI_CMD_CONFIG_LOGCH_PARAM, + SAPI_CMD_SACCH_REL_MARKER, + SAPI_CMD_REL_MARKER, + SAPI_CMD_DEACTIVATE, +}; + +struct sapi_cmd { + struct llist_head entry; + tOCTVC1_GSM_SAPI_ENUM sapi; + tOCTVC1_GSM_DIRECTION_ENUM dir; + enum sapi_cmd_type type; + int (*callback) (struct gsm_lchan * lchan, int status); +}; + +struct sapi_dir { + tOCTVC1_GSM_SAPI_ENUM sapi; + tOCTVC1_GSM_DIRECTION_ENUM dir; +}; + +static const struct sapi_dir ccch_sapis[] = { + {cOCTVC1_GSM_SAPI_ENUM_FCCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_SCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_BCCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_PCH_AGCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_RACH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, +}; + +static const struct sapi_dir tchf_sapis[] = { + {cOCTVC1_GSM_SAPI_ENUM_TCHF, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_TCHF, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_FACCHF, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_FACCHF, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, +}; + +static const struct sapi_dir tchh_sapis[] = { + {cOCTVC1_GSM_SAPI_ENUM_TCHH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_TCHH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_FACCHH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_FACCHH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, +}; + +static const struct sapi_dir sdcch_sapis[] = { + {cOCTVC1_GSM_SAPI_ENUM_SDCCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_SDCCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, +}; + +static const struct sapi_dir cbch_sapis[] = { + {cOCTVC1_GSM_SAPI_ENUM_CBCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + /* Does the CBCH really have a SACCH in Downlink */ + {cOCTVC1_GSM_SAPI_ENUM_SACCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, +}; + +static const struct sapi_dir pdtch_sapis[] = { + {cOCTVC1_GSM_SAPI_ENUM_PDTCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_PDTCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_PTCCH, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS}, + {cOCTVC1_GSM_SAPI_ENUM_PTCCH, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS}, +}; + +struct lchan_sapis { + const struct sapi_dir *sapis; + uint32_t num_sapis; +}; + +static const struct lchan_sapis sapis_for_lchan[_GSM_LCHAN_MAX] = { + [GSM_LCHAN_SDCCH] = { + .sapis = sdcch_sapis, + .num_sapis = ARRAY_SIZE(sdcch_sapis), + }, + [GSM_LCHAN_TCH_F] = { + .sapis = tchf_sapis, + .num_sapis = ARRAY_SIZE(tchf_sapis), + }, + [GSM_LCHAN_TCH_H] = { + .sapis = tchh_sapis, + .num_sapis = ARRAY_SIZE(tchh_sapis), + }, + [GSM_LCHAN_CCCH] = { + .sapis = ccch_sapis, + .num_sapis = ARRAY_SIZE(ccch_sapis), + }, + [GSM_LCHAN_PDTCH] = { + .sapis = pdtch_sapis, + .num_sapis = ARRAY_SIZE(pdtch_sapis), + }, + [GSM_LCHAN_CBCH] = { + .sapis = cbch_sapis, + .num_sapis = ARRAY_SIZE(cbch_sapis), + }, +}; + +static const uint8_t trx_rqd_attr[] = { NM_ATT_RF_MAXPOWR_R }; + +extern uint8_t rach_detected_LA_g; +extern uint8_t rach_detected_Other_g; + +static int opstart_compl(struct gsm_abis_mo *mo) +{ + /* TODO: Send NACK in case of error! */ + + /* Set to Operational State: Enabled */ + oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); + + /* hack to auto-activate all SAPIs for the BCCH/CCCH on TS0 */ + if (mo->obj_class == NM_OC_CHANNEL && mo->obj_inst.trx_nr == 0 && + mo->obj_inst.ts_nr == 7) { + struct gsm_lchan *cbch = gsm_bts_get_cbch(mo->bts); + mo->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind = + LCHAN_REL_ACT_OML; + lchan_activate(&mo->bts->c0->ts[0].lchan[CCCH_LCHAN]); + if (cbch) { + cbch->rel_act_kind = LCHAN_REL_ACT_OML; + lchan_activate(cbch); + } + } + + /* Send OPSTART ack */ + return oml_mo_opstart_ack(mo); +} + +static +tOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM lchan_to_GsmL1_SubCh_t(const struct gsm_lchan + * lchan) +{ + switch (lchan->ts->pchan) { + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + if (lchan->type == GSM_LCHAN_CCCH) + return cOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM_ALL; + /* fall-through */ + case GSM_PCHAN_TCH_H: + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + return (tOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM) lchan->nr; + case GSM_PCHAN_NONE: + case GSM_PCHAN_CCCH: + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_PDCH: + case GSM_PCHAN_UNKNOWN: + default: + return cOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM_ALL; + } + return cOCTVC1_GSM_ID_SUB_CHANNEL_NB_ENUM_ALL; +} + +static void clear_amr_params(tOCTVC1_GSM_LOGICAL_CHANNEL_CONFIG * p_Config) +{ + /* common for the SIGN, V1 and EFR: */ + int i; + p_Config->byCmiPhase = 0; + p_Config->byInitRate = cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_UNSET; + /* 4 AMR active codec set */ + for (i = 0; i < cOCTVC1_GSM_RATE_LIST_SIZE; i++) + p_Config->abyRate[i] = cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_UNSET; +} + +static void lchan2lch_par(struct gsm_lchan *lchan, + tOCTVC1_GSM_LOGICAL_CHANNEL_CONFIG * p_Config) +{ + struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr; + struct gsm48_multi_rate_conf *mr_conf = + (struct gsm48_multi_rate_conf *)amr_mrc->gsm48_ie; + int j; + + LOGP(DL1C, LOGL_INFO, "%s: %s tch_mode=0x%02x\n", + gsm_lchan_name(lchan), __FUNCTION__, lchan->tch_mode); + + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: + /* we have to set some TCH payload type even if we don't + * know yet what codec we will use later on */ + if (lchan->type == GSM_LCHAN_TCH_F) { + clear_amr_params(p_Config); + } + break; + + case GSM48_CMODE_SPEECH_V1: + clear_amr_params(p_Config); + break; + + case GSM48_CMODE_SPEECH_EFR: + clear_amr_params(p_Config); + break; + + case GSM48_CMODE_SPEECH_AMR: + p_Config->byCmiPhase = 1; /* FIXME? */ + p_Config->byInitRate = + (tOCTVC1_GSM_AMR_CODEC_MODE_ENUM) + amr_get_initial_mode(lchan); + + /* initialize to clean state */ + for (j = 0; j < cOCTVC1_GSM_RATE_LIST_SIZE; j++) + p_Config->abyRate[j] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_UNSET; + + j = 0; + if (mr_conf->m4_75) + p_Config->abyRate[j++] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_4_75; + + if (j >= cOCTVC1_GSM_RATE_LIST_SIZE) + break; + + if (mr_conf->m5_15) + p_Config->abyRate[j++] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_5_15; + + if (j >= cOCTVC1_GSM_RATE_LIST_SIZE) + break; + + if (mr_conf->m5_90) + p_Config->abyRate[j++] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_5_90; + + if (j >= cOCTVC1_GSM_RATE_LIST_SIZE) + break; + + if (mr_conf->m6_70) + p_Config->abyRate[j++] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_6_70; + + if (j >= cOCTVC1_GSM_RATE_LIST_SIZE) + break; + + if (mr_conf->m7_40) + p_Config->abyRate[j++] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_7_40; + + if (j >= cOCTVC1_GSM_RATE_LIST_SIZE) + break; + + if (mr_conf->m7_95) + p_Config->abyRate[j++] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_7_95; + + if (j >= cOCTVC1_GSM_RATE_LIST_SIZE) + break; + + if (mr_conf->m10_2) + p_Config->abyRate[j++] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_10_2; + + if (j >= cOCTVC1_GSM_RATE_LIST_SIZE) + break; + + if (mr_conf->m12_2) + p_Config->abyRate[j++] = + cOCTVC1_GSM_AMR_CODEC_MODE_ENUM_RATE_12_2; + break; + + case GSM48_CMODE_DATA_14k5: + case GSM48_CMODE_DATA_12k0: + case GSM48_CMODE_DATA_6k0: + case GSM48_CMODE_DATA_3k6: + LOGP(DL1C, LOGL_ERROR, "%s: CSD not supported!\n", + gsm_lchan_name(lchan)); + break; + + } +} + +/*********************************************************************** + * CORE SAPI QUEUE HANDLING + ***********************************************************************/ + +static void sapi_queue_dispatch(struct gsm_lchan *lchan, int status); +static void sapi_queue_send(struct gsm_lchan *lchan); + +static void sapi_clear_queue(struct llist_head *queue) +{ + struct sapi_cmd *next, *tmp; + + llist_for_each_entry_safe(next, tmp, queue, entry) { + llist_del(&next->entry); + talloc_free(next); + } +} + +static int lchan_act_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data) +{ + tOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_RSP *ar = + (tOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_RSP *) resp->l2h; + struct gsm_bts_trx *trx; + struct gsm_lchan *lchan; + uint8_t sapi; + uint8_t direction; + uint8_t status; + + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + + mOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_RSP_SWAP(ar); + trx = trx_by_l1h(fl1, ar->TrxId.byTrxId); + if (!trx) { + LOGPTRX(ar->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during lchan activation\n"); + return -EINVAL; + } + + lchan = get_lchan_by_lchid(trx, &ar->LchId); + sapi = ar->LchId.bySAPI; + direction = ar->LchId.byDirection; + + LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.conf (%s ", + gsm_lchan_name(lchan), + get_value_string(octphy_l1sapi_names, sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(octphy_dir_names, direction)); + + if (ar->Header.ulReturnCode != cOCTVC1_RC_OK) { + LOGP(DL1C, LOGL_ERROR, "Error activating L1 SAPI %s\n", + get_value_string(octphy_l1sapi_names, sapi)); + status = LCHAN_SAPI_S_ERROR; + } else { + status = LCHAN_SAPI_S_ASSIGNED; + } + + switch (direction) { + case cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS: + lchan->sapis_dl[sapi] = status; + break; + case cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS: + lchan->sapis_ul[sapi] = status; + break; + default: + LOGP(DL1C, LOGL_ERROR, "Unknown direction %d\n", + ar->LchId.byDirection); + break; + } + + if (llist_empty(&lchan->sapi_cmds)) { + LOGP(DL1C, LOGL_ERROR, + "%s Got activation confirmation with empty queue\n", + gsm_lchan_name(lchan)); + goto err; + } + + sapi_queue_dispatch(lchan, ar->Header.ulReturnCode); + +err: + msgb_free(resp); + + return 0; +} + +static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx); + struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CMD *lac; + + lac = (tOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CMD *) + msgb_put(msg, sizeof(*lac)); + l1if_fill_msg_hdr(&lac->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CID); + + lac->TrxId.byTrxId = pinst->u.octphy.trx_id; + lac->LchId.byTimeslotNb = lchan->ts->nr; + lac->LchId.bySubChannelNb = lchan_to_GsmL1_SubCh_t(lchan); + lac->LchId.bySAPI = cmd->sapi; + lac->LchId.byDirection = cmd->dir; + + lac->Config.byTimingAdvance = lchan->rqd_ta; + lac->Config.byBSIC = lchan->ts->trx->bts->bsic; + + lchan2lch_par(lchan, &lac->Config); + + mOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CMD_SWAP(lac); + + LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.req (%s ", + gsm_lchan_name(lchan), + get_value_string(octphy_l1sapi_names, cmd->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(octphy_dir_names, cmd->dir)); + + return l1if_req_compl(fl1h, msg, lchan_act_compl_cb, NULL); +} + + +static tOCTVC1_GSM_CIPHERING_ID_ENUM rsl2l1_ciph[] = { + [0] = cOCTVC1_GSM_CIPHERING_ID_ENUM_UNUSED, + [1] = cOCTVC1_GSM_CIPHERING_ID_ENUM_A5_0, + [2] = cOCTVC1_GSM_CIPHERING_ID_ENUM_A5_1, + [3] = cOCTVC1_GSM_CIPHERING_ID_ENUM_A5_2, + [4] = cOCTVC1_GSM_CIPHERING_ID_ENUM_A5_3 +}; + +static int set_ciph_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data) +{ + tOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_RSP *pcr = + (tOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_RSP *) resp->l2h; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + struct gsm_lchan *lchan; + + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + + mOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_RSP_SWAP(pcr); + + if (pcr->Header.ulReturnCode != cOCTVC1_RC_OK) { + LOGP(DL1C, LOGL_ERROR, "Error: Cipher Request Failed!\n\n"); + LOGP(DL1C, LOGL_ERROR, "Exiting... \n\n"); + msgb_free(resp); + exit(-1); + } + + trx = trx_by_l1h(fl1, pcr->TrxId.byTrxId); + if (!trx) { + LOGPTRX(pcr->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during cipher mode activation\n"); + return -EINVAL; + } + + OSMO_ASSERT(pcr->TrxId.byTrxId == trx->nr); + ts = &trx->ts[pcr->PchId.byTimeslotNb]; + /* for some strange reason the response does not tell which + * sub-channel, only th request contains this information :( */ + lchan = &ts->lchan[(unsigned long) data]; + + /* TODO: This state machine should be shared accross BTS models? */ + switch (lchan->ciph_state) { + case LCHAN_CIPH_RX_REQ: + lchan->ciph_state = LCHAN_CIPH_RX_CONF; + break; + case LCHAN_CIPH_RX_CONF_TX_REQ: + lchan->ciph_state = LCHAN_CIPH_RXTX_CONF; + break; + case LCHAN_CIPH_RXTX_REQ: + lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ; + break; + case LCHAN_CIPH_NONE: + break; + default: + LOGPC(DL1C, LOGL_INFO, "unhandled state %u\n", lchan->ciph_state); + } + + if (llist_empty(&lchan->sapi_cmds)) { + LOGP(DL1C, LOGL_ERROR, + "%s Got ciphering conf with empty queue\n", + gsm_lchan_name(lchan)); + goto err; + } + sapi_queue_dispatch(lchan, pcr->Header.ulReturnCode); + +err: + msgb_free(resp); + return 0; +} + +static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx); + struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CMD *pcc; + + pcc = (tOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CMD *) + msgb_put(msg, sizeof(*pcc)); + l1if_fill_msg_hdr(&pcc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CID); + + pcc->TrxId.byTrxId = pinst->u.octphy.trx_id; + pcc->PchId.byTimeslotNb = lchan->ts->nr; + pcc->ulSubchannelNb = lchan_to_GsmL1_SubCh_t(lchan); + pcc->ulDirection = cmd->dir; + pcc->Config.ulCipherId = rsl2l1_ciph[lchan->encr.alg_id]; + memcpy(pcc->Config.abyKey, lchan->encr.key, lchan->encr.key_len); + + LOGP(DL1C, LOGL_INFO, "%s SET_CIPHERING (ALG=%u %s)\n", + gsm_lchan_name(lchan), pcc->Config.ulCipherId, + get_value_string(octphy_dir_names, pcc->ulDirection)); + + mOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CMD_SWAP(pcc); + + /* we have to save the lchan number in this strange way, as the + * PHY does not return the ulSubchannelNr in the response to + * this command */ + return l1if_req_compl(fl1h, msg, set_ciph_compl_cb, (void *)(unsigned long) lchan->nr); +} + + +/** + * Queue and possible execute a SAPI command. Return 1 in case the command was + * already executed and 0 in case if it was only put into the queue + */ +static int queue_sapi_command(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + int start = llist_empty(&lchan->sapi_cmds); + llist_add_tail(&cmd->entry, &lchan->sapi_cmds); + + if (!start) + return 0; + + sapi_queue_send(lchan); + return 1; +} + +static int mph_info_chan_confirm(struct gsm_lchan *lchan, + enum osmo_mph_info_type type, uint8_t cause) +{ + struct osmo_phsap_prim l1sap; + + memset(&l1sap, 0, sizeof(l1sap)); + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_CONFIRM, + NULL); + l1sap.u.info.type = type; + l1sap.u.info.u.act_cnf.chan_nr = gsm_lchan2chan_nr(lchan); + l1sap.u.info.u.act_cnf.cause = cause; + + return l1sap_up(lchan->ts->trx, &l1sap); +} + +static int sapi_deactivate_cb(struct gsm_lchan *lchan, int status) +{ + /* FIXME: Error handling. There is no NACK... */ + if (status != cOCTVC1_RC_OK && lchan->state == LCHAN_S_REL_REQ) { + LOGP(DL1C, LOGL_ERROR, + "%s is now broken. Stopping the release.\n", + gsm_lchan_name(lchan)); + lchan_set_state(lchan, LCHAN_S_BROKEN); + sapi_clear_queue(&lchan->sapi_cmds); + mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0); + return -1; + } + + if (!llist_empty(&lchan->sapi_cmds)) + return 0; + + /* Don't send an REL ACK on SACCH deactivate */ + if (lchan->state != LCHAN_S_REL_REQ) + return 0; + + lchan_set_state(lchan, LCHAN_S_NONE); + mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0); + return 0; +} + +static int enqueue_sapi_deact_cmd(struct gsm_lchan *lchan, int sapi, int dir) +{ + struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + + cmd->sapi = sapi; + cmd->dir = dir; + cmd->type = SAPI_CMD_DEACTIVATE; + cmd->callback = sapi_deactivate_cb; + return queue_sapi_command(lchan, cmd); +} + +/* + * Release the SAPI if it was allocated. E.g. the SACCH might be already + * deactivated or during a hand-over the TCH was not allocated yet. + */ +static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir) +{ + /* check if we should schedule a release */ + if (dir == cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS) { + if (lchan->sapis_dl[sapi] != LCHAN_SAPI_S_ASSIGNED) + return 0; + lchan->sapis_dl[sapi] = LCHAN_SAPI_S_REL; + } else if (dir == cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS) { + if (lchan->sapis_ul[sapi] != LCHAN_SAPI_S_ASSIGNED) + return 0; + lchan->sapis_ul[sapi] = LCHAN_SAPI_S_REL; + } + /* now schedule the command and maybe dispatch it */ + return enqueue_sapi_deact_cmd(lchan, sapi, dir); +} + +static int lchan_deactivate_sapis(struct gsm_lchan *lchan) +{ + struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx); + struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; + const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type]; + int i, res; + + res = 0; + + /* The order matters.. the Facch needs to be released first */ + for (i = s4l->num_sapis - 1; i >= 0; i--) { + /* Stop the alive timer once we deactivate the SCH */ + if (s4l->sapis[i].sapi == cOCTVC1_GSM_SAPI_ENUM_SCH) + osmo_timer_del(&fl1h->alive_timer); + + /* Release if it was allocated */ + res |= check_sapi_release(lchan, s4l->sapis[i].sapi, s4l->sapis[i].dir); + } + + /* nothing was queued */ + if (res == 0) { + LOGP(DL1C, LOGL_ERROR, "%s all SAPIs already released?\n", + gsm_lchan_name(lchan)); + lchan_set_state(lchan, LCHAN_S_BROKEN); + mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0); + } + + return res; +} + +static int lchan_deact_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data) +{ + tOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_RSP *ldr = + (tOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_RSP *) resp->l2h; + struct gsm_bts_trx *trx; + struct gsm_lchan *lchan; + struct sapi_cmd *cmd; + uint8_t status; + + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + + mOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_RSP_SWAP(ldr); + trx = trx_by_l1h(fl1, ldr->TrxId.byTrxId); + if (!trx) { + LOGPTRX(ldr->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during lchan deactivation\n"); + return -EINVAL; + } + + lchan = get_lchan_by_lchid(trx, &ldr->LchId); + + LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.conf (%s ", + gsm_lchan_name(lchan), + get_value_string(octphy_l1sapi_names, ldr->LchId.bySAPI)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(octphy_dir_names, ldr->LchId.byDirection)); + + if (ldr->Header.ulReturnCode == cOCTVC1_RC_OK) { + DEBUGP(DL1C, "Successful deactivation of L1 SAPI %s on TS %u\n", + get_value_string(octphy_l1sapi_names, ldr->LchId.bySAPI), + ldr->LchId.byTimeslotNb); + status = LCHAN_SAPI_S_NONE; + } else { + LOGP(DL1C, LOGL_ERROR, + "Error deactivating L1 SAPI %s on TS %u\n", + get_value_string(octphy_l1sapi_names, ldr->LchId.bySAPI), + ldr->LchId.byTimeslotNb); + status = LCHAN_SAPI_S_ERROR; + } + + switch (ldr->LchId.byDirection) { + case cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS: + lchan->sapis_dl[ldr->LchId.bySAPI] = status; + break; + case cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS: + lchan->sapis_ul[ldr->LchId.bySAPI] = status; + break; + } + + if (llist_empty(&lchan->sapi_cmds)) { + LOGP(DL1C, LOGL_ERROR, + "%s Got de-activation confirmation with empty queue\n", + gsm_lchan_name(lchan)); + goto err; + } + + cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry); + if (cmd->sapi != ldr->LchId.bySAPI || + cmd->dir != ldr->LchId.byDirection || + cmd->type != SAPI_CMD_DEACTIVATE) { + LOGP(DL1C, LOGL_ERROR, + "%s Confirmation mismatch (%d, %d) (%d, %d)\n", + gsm_lchan_name(lchan), cmd->sapi, cmd->dir, + ldr->LchId.bySAPI, ldr->LchId.byDirection); + goto err; + } + + sapi_queue_dispatch(lchan, status); + +err: + msgb_free(resp); + return 0; +} + +static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx); + struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_CMD *ldc; + + ldc = (tOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_CMD *) + msgb_put(msg, sizeof(*ldc)); + l1if_fill_msg_hdr(&ldc->Header, msg, fl1h,cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_CID); + + ldc->TrxId.byTrxId = pinst->u.octphy.trx_id; + ldc->LchId.byTimeslotNb = lchan->ts->nr; + ldc->LchId.bySubChannelNb = lchan_to_GsmL1_SubCh_t(lchan); + ldc->LchId.byDirection = cmd->dir; + ldc->LchId.bySAPI = cmd->sapi; + + mOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_CMD_SWAP(ldc); + + LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.req (%s ", + gsm_lchan_name(lchan), + get_value_string(octphy_l1sapi_names, cmd->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(octphy_dir_names, cmd->dir)); + + return l1if_req_compl(fl1h, msg, lchan_deact_compl_cb, NULL); + +} + +/** + * Execute the first SAPI command of the queue. In case of the markers + * this method is re-entrant so we need to make sure to remove a command + * from the list before calling a function that will queue a command. + * + * \return 0 in case no Gsm Request was sent, 1 otherwise + */ + +static int sapi_queue_exeute(struct gsm_lchan *lchan) +{ + int res = 0; + struct sapi_cmd *cmd; + + cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry); + + switch (cmd->type) { + case SAPI_CMD_ACTIVATE: + mph_send_activate_req(lchan, cmd); + res = 1; + break; + case SAPI_CMD_CONFIG_CIPHERING: + mph_send_config_ciphering(lchan, cmd); + res = 1; + break; + case SAPI_CMD_CONFIG_LOGCH_PARAM: + /* TODO: Mode modif not supported by OctPHY currently */ + /* mph_send_config_logchpar(lchan, cmd); */ + res = 1; + break; + case SAPI_CMD_SACCH_REL_MARKER: + llist_del(&cmd->entry); + talloc_free(cmd); + res = + check_sapi_release(lchan, cOCTVC1_GSM_SAPI_ENUM_SACCH, + cOCTVC1_GSM_ID_DIRECTION_ENUM_TX_BTS_MS); + res |= + check_sapi_release(lchan, cOCTVC1_GSM_SAPI_ENUM_SACCH, + cOCTVC1_GSM_ID_DIRECTION_ENUM_RX_BTS_MS); + break; + case SAPI_CMD_REL_MARKER: + llist_del(&cmd->entry); + talloc_free(cmd); + res = lchan_deactivate_sapis(lchan); + break; + case SAPI_CMD_DEACTIVATE: + res = mph_send_deactivate_req(lchan, cmd); + res = 1; + break; + default: + LOGP(DL1C, LOGL_NOTICE, + "Unimplemented command type %d\n", cmd->type); + llist_del(&cmd->entry); + talloc_free(cmd); + res = 0; + abort(); + break; + } + + return res; +} + +static void sapi_queue_send(struct gsm_lchan *lchan) +{ + int res; + + do { + res = sapi_queue_exeute(lchan); + } while (res == 0 && !llist_empty(&lchan->sapi_cmds)); +} + +static void sapi_queue_dispatch(struct gsm_lchan *lchan, int status) +{ + int end; + struct sapi_cmd *cmd = llist_entry(lchan->sapi_cmds.next, + struct sapi_cmd, entry); + llist_del(&cmd->entry); + end = llist_empty(&lchan->sapi_cmds); + + if (cmd->callback) + cmd->callback(lchan, status); + talloc_free(cmd); + + if (end || llist_empty(&lchan->sapi_cmds)) { + LOGP(DL1C, LOGL_NOTICE, + "%s End of queue encountered. Now empty? %d\n", + gsm_lchan_name(lchan), llist_empty(&lchan->sapi_cmds)); + return; + } + + sapi_queue_send(lchan); +} + +/* we regularly check if the L1 is still sending us primitives. + if not, we simply stop the BTS program (and be re-spawned) */ +static void alive_timer_cb(void *data) +{ + struct octphy_hdl *fl1h = data; + + if (fl1h->alive_prim_cnt == 0) { + LOGP(DL1C, LOGL_FATAL, "L1 is no longer sending primitives!\n"); + exit(23); + } + fl1h->alive_prim_cnt = 0; + osmo_timer_schedule(&fl1h->alive_timer, 5, 0); +} + +/*********************************************************************** + * RSL DEACTIVATE SACCH + ***********************************************************************/ + +static void enqueue_sacch_rel_marker(struct gsm_lchan *lchan) +{ + struct sapi_cmd *cmd; + + /* remember we need to check if the SACCH is allocated */ + cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + cmd->type = SAPI_CMD_SACCH_REL_MARKER; + queue_sapi_command(lchan, cmd); +} + +int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan) +{ + enqueue_sacch_rel_marker(lchan); + return 0; +} + +int l1if_rsl_deact_sacch(struct gsm_lchan *lchan) +{ + /* Only de-activate the SACCH if the lchan is active */ + if (lchan->state != LCHAN_S_ACTIVE) + return 0; + return bts_model_lchan_deactivate_sacch(lchan); +} + + +/*********************************************************************** + * RSL CHANNEL RELEASE + ***********************************************************************/ + +static void enqueue_rel_marker(struct gsm_lchan *lchan) +{ + struct sapi_cmd *cmd; + + /* remember we need to release all active SAPIs */ + cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + cmd->type = SAPI_CMD_REL_MARKER; + queue_sapi_command(lchan, cmd); +} + +int bts_model_lchan_deactivate(struct gsm_lchan *lchan) +{ + lchan_set_state(lchan, LCHAN_S_REL_REQ); + enqueue_rel_marker(lchan); + return 0; +} + +int l1if_rsl_chan_rel(struct gsm_lchan *lchan) +{ + /* A duplicate RF Release Request, ignore it */ + if (lchan->state == LCHAN_S_REL_REQ) + return 0; + lchan_deactivate(lchan); + return 0; +} + + +/*********************************************************************** + * SET CIPHERING + ***********************************************************************/ + +static void enqueue_sapi_ciphering_cmd(struct gsm_lchan *lchan, int dir) +{ + struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + + cmd->dir = dir; + cmd->type = SAPI_CMD_CONFIG_CIPHERING; + queue_sapi_command(lchan, cmd); +} + +int l1if_set_ciphering(struct gsm_lchan *lchan, int dir_downlink) +{ + int dir; + + // ignore the request when the channel is not active + if (lchan->state != LCHAN_S_ACTIVE) + return -1; + + if (dir_downlink) + dir = cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS; + else + dir = cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS; + + enqueue_sapi_ciphering_cmd(lchan, dir); + + return 0; +} + + +/*********************************************************************** + * RSL MODE MODIFY + ***********************************************************************/ + +/* Mode modify is currently not supported by OctPHY */ +static void enqueue_sapi_logchpar_cmd(struct gsm_lchan *lchan, int dir) +{ + struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + + cmd->dir = dir; + cmd->type = SAPI_CMD_CONFIG_LOGCH_PARAM; + queue_sapi_command(lchan, cmd); +} + + +/* Mode modify is currently not supported by OctPHY */ +static int tx_confreq_logchpar(struct gsm_lchan *lchan, uint8_t direction) +{ + enqueue_sapi_logchpar_cmd(lchan, direction); + + return 0; +} + +/* Mode modify is currently not supported by OctPHY */ +int l1if_rsl_mode_modify(struct gsm_lchan *lchan) +{ + if (lchan->state != LCHAN_S_ACTIVE) + return -1; + + /* channel mode, encryption and/or multirate have changed */ + + /* update multi-rate config */ + tx_confreq_logchpar(lchan, cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS); + tx_confreq_logchpar(lchan, cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS); + + /* FIXME: update encryption */ + + return 0; +} + + +/*********************************************************************** + * LCHAN / SAPI ACTIVATION + ***********************************************************************/ + +static int sapi_activate_cb(struct gsm_lchan *lchan, int status) +{ + if (status != cOCTVC1_RC_OK) { + lchan_set_state(lchan, LCHAN_S_BROKEN); + sapi_clear_queue(&lchan->sapi_cmds); + mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, + RSL_ERR_EQUIPMENT_FAIL); + return -1; + } + + if (!llist_empty(&lchan->sapi_cmds)) + return 0; + + if (lchan->state != LCHAN_S_ACT_REQ) + return 0; + + lchan_set_state(lchan, LCHAN_S_ACTIVE); + + mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, 0); + + return 0; +} + +static void enqueue_sapi_act_cmd(struct gsm_lchan *lchan, int sapi, int dir) +{ + struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + + cmd->sapi = sapi; + cmd->dir = dir; + cmd->type = SAPI_CMD_ACTIVATE; + cmd->callback = sapi_activate_cb; + queue_sapi_command(lchan, cmd); +} + +int lchan_activate(struct gsm_lchan *lchan) +{ + struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx); + struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; + const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type]; + unsigned int i; + + lchan_set_state(lchan, LCHAN_S_ACT_REQ); + + DEBUGP(DL1C, "lchan_act called\n"); + + if (!llist_empty(&lchan->sapi_cmds)) + LOGP(DL1C, LOGL_ERROR, + "%s Trying to activate lchan, but commands in queue\n", + gsm_lchan_name(lchan)); + + for (i = 0; i < s4l->num_sapis; i++) { + int sapi = s4l->sapis[i].sapi; + int dir = s4l->sapis[i].dir; + + if (sapi == cOCTVC1_GSM_SAPI_ENUM_SCH) { + /* once we activate the SCH, we should get MPH-TIME.ind */ + fl1h->alive_timer.cb = alive_timer_cb; + fl1h->alive_timer.data = fl1h; + fl1h->alive_prim_cnt = 0; + osmo_timer_schedule(&fl1h->alive_timer, 5, 0); + } + enqueue_sapi_act_cmd(lchan, sapi, dir); + } + + lchan_init_lapdm(lchan); + + return 0; +} + +int l1if_rsl_chan_act(struct gsm_lchan *lchan) +{ + lchan_activate(lchan); + return 0; +} + +#define talloc_replace(dst, ctx, src) \ + do { \ + if (dst) \ + talloc_free(dst); \ + dst = talloc_strdup(ctx, (const char *) src); \ + } while (0) + +static int app_info_sys_compl_cb(struct octphy_hdl *fl1h, struct msgb *resp, void *data) +{ + tOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_RSP *aisr = + (tOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_RSP *) resp->l2h; + + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + + mOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_RSP_SWAP(aisr); + + LOGP(DL1C, LOGL_INFO, "Rx APP-INFO-SYSTEM.resp (platform='%s', version='%s')\n", + aisr->szPlatform, aisr->szVersion); + +#if OCTPHY_MULTI_TRX == 1 + LOGP(DL1C, LOGL_INFO, "Note: compiled with multi-trx support.\n"); +#else + LOGP(DL1C, LOGL_INFO, "Note: compiled without multi-trx support.\n"); +#endif + + talloc_replace(fl1h->info.system.platform, fl1h, aisr->szPlatform); + talloc_replace(fl1h->info.system.version, fl1h, aisr->szVersion); + + msgb_free(resp); + + return 0; +} + +int l1if_check_app_sys_version(struct gsm_bts_trx *trx) +{ + struct phy_instance *pinst = trx_phy_instance(trx); + struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CMD *ais; + + ais = (tOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CMD *) + msgb_put(msg, sizeof(*ais)); + mOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CMD_DEF(ais); + l1if_fill_msg_hdr(&ais->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CID); + + mOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CMD_SWAP(ais); + + LOGP(DL1C, LOGL_INFO, "Tx APP-INFO-SYSTEM.req\n"); + + return l1if_req_compl(fl1h, msg, app_info_sys_compl_cb, pinst); +} + +static int app_info_compl_cb(struct octphy_hdl *fl1h, struct msgb *resp, + void *data) +{ + char ver_hdr[32]; + struct phy_instance *pinst = data; + tOCTVC1_MAIN_MSG_APPLICATION_INFO_RSP *air = + (tOCTVC1_MAIN_MSG_APPLICATION_INFO_RSP *) resp->l2h; + + snprintf(ver_hdr, sizeof(ver_hdr), "%02i.%02i.%02i-B%i", + cOCTVC1_MAIN_VERSION_MAJOR, cOCTVC1_MAIN_VERSION_MINOR, + cOCTVC1_MAIN_VERSION_MAINTENANCE, cOCTVC1_MAIN_VERSION_BUILD); + + mOCTVC1_MAIN_MSG_APPLICATION_INFO_RSP_SWAP(air); + + LOGP(DL1C, LOGL_INFO, + "Rx APP-INFO.resp (name='%s', desc='%s', ver='%s', ver_hdr='%s')\n", + air->szName, air->szDescription, air->szVersion, ver_hdr); + + /* Check if the firmware version of the DSP matches the header files + * that were used to compile osmo-bts */ + if (strcmp(air->szVersion, ver_hdr) != 0) { + LOGP(DL1C, LOGL_ERROR, + "Invalid header-file-version / dsp-firmware-version combination\n"); + LOGP(DL1C, LOGL_ERROR, + "Expected firmware version: %s\n", ver_hdr); + LOGP(DL1C, LOGL_ERROR, + "Actual firmware version: %s\n", air->szVersion); + + if (!no_fw_check) { + LOGP(DL1C, LOGL_ERROR, + "use option -I to override the check (not recommened)\n"); + LOGP(DL1C, LOGL_ERROR, + "exiting...\n"); + exit(1); + } + } + + talloc_replace(fl1h->info.app.name, fl1h, air->szName); + talloc_replace(fl1h->info.app.description, fl1h, air->szDescription); + talloc_replace(fl1h->info.app.version, fl1h, air->szVersion); + OSMO_ASSERT(strlen(ver_hdr) < sizeof(pinst->version)); + osmo_strlcpy(pinst->version, ver_hdr, strlen(ver_hdr)); + + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + msgb_free(resp); + + return 0; +} + +int l1if_check_app_version(struct gsm_bts_trx *trx) +{ + struct phy_instance *pinst = trx_phy_instance(trx); + struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_MAIN_MSG_APPLICATION_INFO_CMD *ai; + + ai = (tOCTVC1_MAIN_MSG_APPLICATION_INFO_CMD *) msgb_put(msg, sizeof(*ai)); + mOCTVC1_MAIN_MSG_APPLICATION_INFO_CMD_DEF(ai); + l1if_fill_msg_hdr(&ai->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_MAIN_MSG_APPLICATION_INFO_CID); + + mOCTVC1_MAIN_MSG_APPLICATION_INFO_CMD_SWAP(ai); + + LOGP(DL1C, LOGL_INFO, "Tx APP-INFO.req\n"); + + return l1if_req_compl(fl1h, msg, app_info_compl_cb, pinst); +} + +static int trx_close_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data) +{ + tOCTVC1_GSM_MSG_TRX_CLOSE_RSP *car = + (tOCTVC1_GSM_MSG_TRX_CLOSE_RSP *) resp->l2h; + + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + + mOCTVC1_GSM_MSG_TRX_CLOSE_RSP_SWAP(car); + + LOGP(DL1C, LOGL_INFO, "Rx TRX-CLOSE.conf(%u)\n", car->TrxId.byTrxId); + + msgb_free(resp); + + return 0; +} + +static int trx_close(struct gsm_bts_trx *trx) +{ + struct phy_instance *pinst = trx_phy_instance(trx); + struct phy_link *plink = pinst->phy_link; + struct octphy_hdl *fl1h = plink->u.octphy.hdl; + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_GSM_MSG_TRX_CLOSE_CMD *cac; + + cac = (tOCTVC1_GSM_MSG_TRX_CLOSE_CMD *) + msgb_put(msg, sizeof(*cac)); + l1if_fill_msg_hdr(&cac->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_CLOSE_CID); + + cac->TrxId.byTrxId = pinst->u.octphy.trx_id; + + LOGP(DL1C, LOGL_INFO, "Tx TRX-CLOSE.req(%u)\n", cac->TrxId.byTrxId); + + mOCTVC1_GSM_MSG_TRX_CLOSE_CMD_SWAP(cac); + + return l1if_req_compl(fl1h, msg, trx_close_cb, NULL); +} + +/* call-back once the TRX_OPEN_CID response arrives */ +static int trx_open_compl_cb(struct octphy_hdl *fl1h, struct msgb *resp, void *data) +{ + struct gsm_bts_trx *trx; + + tOCTVC1_GSM_MSG_TRX_OPEN_RSP *or = + (tOCTVC1_GSM_MSG_TRX_OPEN_RSP *) resp->l2h; + + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + + mOCTVC1_GSM_MSG_TRX_OPEN_RSP_SWAP(or); + trx = trx_by_l1h(fl1h, or->TrxId.byTrxId); + if (!trx) { + LOGPTRX(or->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during TRX opening procedure -- abort\n"); + exit(1); + } + + LOGP(DL1C, LOGL_INFO, "TRX-OPEN.resp(trx=%u) = %s\n", + trx->nr, octvc1_rc2string(or->Header.ulReturnCode)); + + /* FIXME: check for ulReturnCode == OK */ + if (or->Header.ulReturnCode != cOCTVC1_RC_OK) { + LOGP(DL1C, LOGL_ERROR, "TRX-OPEN failed: %s\n", + octvc1_rc2string(or->Header.ulReturnCode)); + msgb_free(resp); + exit(1); + } + + msgb_free(resp); + + opstart_compl(&trx->mo); + + octphy_hw_get_pcb_info(fl1h); + octphy_hw_get_rf_port_info(fl1h, 0); + octphy_hw_get_rf_ant_rx_config(fl1h, 0, 0); + octphy_hw_get_rf_ant_tx_config(fl1h, 0, 0); + octphy_hw_get_rf_ant_rx_config(fl1h, 0, 1); + octphy_hw_get_rf_ant_tx_config(fl1h, 0, 1); + octphy_hw_get_clock_sync_info(fl1h); + fl1h->opened = 1; + + return 0; +} + +int l1if_trx_open(struct gsm_bts_trx *trx) +{ + /* putting it all together */ + struct phy_instance *pinst = trx_phy_instance(trx); + struct phy_link *plink = pinst->phy_link; + struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_GSM_MSG_TRX_OPEN_CMD *oc; + + oc = (tOCTVC1_GSM_MSG_TRX_OPEN_CMD *) msgb_put(msg, sizeof(*oc)); + l1if_fill_msg_hdr(&oc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_OPEN_CID); + oc->ulRfPortIndex = plink->u.octphy.rf_port_index; + oc->TrxId.byTrxId = pinst->u.octphy.trx_id; + oc->Config.ulBand = osmocom_to_octphy_band(trx->bts->band, trx->arfcn); + oc->Config.usArfcn = trx->arfcn; + +#if OCTPHY_MULTI_TRX == 1 + if (pinst->u.octphy.trx_id) + oc->Config.usCentreArfcn = plink->u.octphy.center_arfcn; + else { + oc->Config.usCentreArfcn = trx->arfcn; + plink->u.octphy.center_arfcn = trx->arfcn; + } + oc->Config.usBcchArfcn = trx->bts->c0->arfcn; +#endif + oc->Config.usTsc = trx->bts->bsic & 0x7; + oc->RfConfig.ulRxGainDb = plink->u.octphy.rx_gain_db; + /* FIXME: compute this based on nominal transmit power, etc. */ + if (plink->u.octphy.tx_atten_flag) { + oc->RfConfig.ulTxAttndB = plink->u.octphy.tx_atten_db; + } else { + /* Take the Tx Attn received in set radio attribures + * x4 is for the value in db */ + oc->RfConfig.ulTxAttndB = (trx->max_power_red) << 2; + } + +#if OCTPHY_USE_ANTENNA_ID == 1 + oc->RfConfig.ulTxAntennaId = plink->u.octphy.tx_ant_id; + oc->RfConfig.ulRxAntennaId = plink->u.octphy.rx_ant_id; +#endif + +#if OCTPHY_MULTI_TRX == 1 + LOGP(DL1C, LOGL_INFO, "Tx TRX-OPEN.req(trx=%u, rf_port=%u, arfcn=%u, " + "center=%u, tsc=%u, rx_gain=%u, tx_atten=%u)\n", + oc->TrxId.byTrxId, oc->ulRfPortIndex, oc->Config.usArfcn, + oc->Config.usCentreArfcn, oc->Config.usTsc, oc->RfConfig.ulRxGainDb, + oc->RfConfig.ulTxAttndB); +#else + LOGP(DL1C, LOGL_INFO, "Tx TRX-OPEN.req(trx=%u, rf_port=%u, arfcn=%u, " + "tsc=%u, rx_gain=%u, tx_atten=%u)\n", + oc->TrxId.byTrxId, oc->ulRfPortIndex, oc->Config.usArfcn, + oc->Config.usTsc, oc->RfConfig.ulRxGainDb, + oc->RfConfig.ulTxAttndB); +#endif + + mOCTVC1_GSM_MSG_TRX_OPEN_CMD_SWAP(oc); + + return l1if_req_compl(fl1h, msg, trx_open_compl_cb, NULL); +} + +uint32_t trx_get_hlayer1(struct gsm_bts_trx * trx) +{ + return 0; +} + +static int trx_init(struct gsm_bts_trx *trx) +{ + if (!gsm_abis_mo_check_attr(&trx->mo, trx_rqd_attr, + ARRAY_SIZE(trx_rqd_attr))) { + /* HACK: spec says we need to decline, but openbsc + * doesn't deal with this very well */ + return oml_mo_opstart_ack(&trx->mo); + /* return oml_mo_opstart_nack(&trx->mo, NM_NACK_CANT_PERFORM); */ + } + + l1if_check_app_version(trx); + l1if_check_app_sys_version(trx); + + return l1if_trx_open(trx); +} + +/*********************************************************************** + * PHYSICAL CHANNE ACTIVATION + ***********************************************************************/ + +static int pchan_act_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data) +{ + tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP *ar = + (tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP *) resp->l2h; + uint8_t ts_nr; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + struct gsm_abis_mo *mo; + + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + + mOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP_SWAP(ar); + trx = trx_by_l1h(fl1, ar->TrxId.byTrxId); + if (!trx) { + LOGPTRX(ar->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during physical channel activation -- abort\n"); + exit(1); + } + + ts_nr = ar->PchId.byTimeslotNb; + OSMO_ASSERT(ts_nr <= ARRAY_SIZE(trx->ts)); + + ts = &trx->ts[ts_nr]; + + LOGP(DL1C, LOGL_INFO, "PCHAN-ACT.conf(trx=%u, ts=%u, chcomb=%u) = %s\n", + ts->trx->nr, ts->nr, ts->pchan, + octvc1_rc2string(ar->Header.ulReturnCode)); + + if (ar->Header.ulReturnCode != cOCTVC1_RC_OK) { + LOGP(DL1C, LOGL_ERROR, + "PCHAN-ACT failed: %s\n\n", + octvc1_rc2string(ar->Header.ulReturnCode)); + LOGP(DL1C, LOGL_ERROR, "Exiting... \n\n"); + msgb_free(resp); + exit(-1); + } + + trx = ts->trx; + mo = &trx->ts[ar->PchId.byTimeslotNb].mo; + + msgb_free(resp); + + return opstart_compl(mo); +} + +static int ts_connect_as(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config pchan, + l1if_compl_cb * cb, void *data) +{ + struct phy_instance *pinst = trx_phy_instance(ts->trx); + struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CMD *oc = + (tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CMD *) oc; + + oc = (tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CMD*) + msgb_put(msg, sizeof(*oc)); + l1if_fill_msg_hdr(&oc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CID); + + oc->TrxId.byTrxId = pinst->u.octphy.trx_id; + oc->PchId.byTimeslotNb = ts->nr; + oc->ulChannelType = pchan_to_logChComb[pchan]; + + /* TODO: how should we know the payload type here? Also, why + * would the payload type have to be the same for both halves of + * a TCH/H ? */ + switch (oc->ulChannelType) { + case cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_TCHF_FACCHF_SACCHTF: + case cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_PDTCHF_PACCHF_PTCCHF: + oc->ulPayloadType = cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_FULL_RATE; + break; + case cOCTVC1_GSM_LOGICAL_CHANNEL_COMBINATION_ENUM_TCHH_FACCHH_SACCHTH: + oc->ulPayloadType = cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_HALF_RATE; + break; + } + + LOGP(DL1C, LOGL_INFO, "PCHAN-ACT.req(trx=%u, ts=%u, chcomb=%u)\n", + ts->trx->nr, ts->nr, pchan); + + mOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CMD_SWAP(oc); + + return l1if_req_compl(fl1h, msg, cb, data); +} + +/* Dynamic timeslots: Disconnect callback, reports completed disconnection + * to higher layers */ +static int ts_disconnect_cb(struct octphy_hdl *fl1, struct msgb *resp, + void *data) +{ + tOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_RSP *ar = + (tOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_RSP *) resp->l2h; + uint8_t ts_nr; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + + trx = trx_by_l1h(fl1, ar->TrxId.byTrxId); + if (!trx) { + LOGPTRX(ar->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id during ts disconnection\n"); + return -EINVAL; + } + + ts_nr = ar->PchId.byTimeslotNb; + ts = &trx->ts[ts_nr]; + + cb_ts_disconnected(ts); + + return 0; +} + +/* Dynamic timeslots: Connect callback, reports completed disconnection to + * higher layers */ +static int ts_connect_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data) +{ + tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP *ar = + (tOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP *) resp->l2h; + uint8_t ts_nr; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + + /* in a completion call-back, we take msgb ownership and must + * release it before returning */ + + mOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_RSP_SWAP(ar); + trx = trx_by_l1h(fl1, ar->TrxId.byTrxId); + if (!trx) { + LOGPTRX(ar->TrxId.byTrxId, LOGL_ERROR, "response with unexpected physical transceiver-id while connecting ts\n"); + return -EINVAL; + } + + ts_nr = ar->PchId.byTimeslotNb; + OSMO_ASSERT(ts_nr <= ARRAY_SIZE(trx->ts)); + + ts = &trx->ts[ts_nr]; + + LOGP(DL1C, LOGL_INFO, "PCHAN-ACT.conf(trx=%u, ts=%u, chcomb=%u) = %s\n", + ts->trx->nr, ts->nr, ts->pchan, + octvc1_rc2string(ar->Header.ulReturnCode)); + + if (ar->Header.ulReturnCode != cOCTVC1_RC_OK) { + LOGP(DL1C, LOGL_ERROR, + "PCHAN-ACT failed: %s\n\n", + octvc1_rc2string(ar->Header.ulReturnCode)); + LOGP(DL1C, LOGL_ERROR, "Exiting... \n\n"); + msgb_free(resp); + exit(-1); + } + + msgb_free(resp); + + cb_ts_connected(ts); + + return 0; +} + +/*********************************************************************** + * BTS MODEL CALLBACKS + ***********************************************************************/ + +int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) +{ + /* TODO: How to do this ? */ + return 0; +} + +int gsm_abis_mo_check_attr(const struct gsm_abis_mo *mo, + const uint8_t * attr_ids, unsigned int num_attr_ids) +{ + unsigned int i; + + if (!mo->nm_attr) + return 0; + + for (i = 0; i < num_attr_ids; i++) { + if (!TLVP_PRESENT(mo->nm_attr, attr_ids[i])) + return 0; + } + return 1; +} + +int bts_model_oml_estab(struct gsm_bts *bts) +{ + int i; + for (i = 0; i < bts->num_trx; i++) { + struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, i); + l1if_activate_rf(trx, 1); + } + return 0; +} + +int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo, + void *obj, uint8_t adm_state) +{ + int rc; + + struct gsm_bts_trx *trx; + struct phy_instance *pinst; + struct octphy_hdl *fl1h; + + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + + trx = ((struct gsm_bts_trx *)obj); + pinst = trx_phy_instance(trx); + fl1h = pinst->phy_link->u.octphy.hdl; + + if (mo->procedure_pending) { + LOGP(DL1C, LOGL_ERROR, "Discarding adm change command: " + "pending procedure on TRX %d\n", trx->nr); + return 0; + } + mo->procedure_pending = 1; + switch (adm_state) { + case NM_STATE_LOCKED: + + pinst->u.octphy.trx_locked = 1; + + /* Stop heartbeat check */ + osmo_timer_del(&fl1h->alive_timer); + + bts_model_trx_deact_rf(trx); + + /* Close TRX */ + rc = bts_model_trx_close(trx); + if (rc != 0) { + LOGP(DL1C, LOGL_ERROR, + "Cannot close TRX %d, it is already closed.\n", + trx->nr); + } + break; + + case NM_STATE_UNLOCKED: + + if (pinst->u.octphy.trx_locked) { + pinst->u.octphy.trx_locked = 0; + l1if_activate_rf(trx, 1); + } + + break; + + default: + break; + } + + mo->procedure_pending = 0; + break; + + default: + /* blindly accept all state changes */ + break; + } + + mo->nm_state.administrative = adm_state; + return oml_mo_statechg_ack(mo); +} + +int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) +{ + return l1if_activate_rf(trx, 0); +} + +int bts_model_trx_close(struct gsm_bts_trx *trx) +{ + /* FIXME: close only one TRX */ + return trx_close(trx); +} + + +/* callback from OML */ +int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, + struct tlv_parsed *old_attr, + struct tlv_parsed *new_attr, void *obj) +{ + /* FIXME: check if the attributes are valid */ + return 0; +} + +/* callback from OML */ +int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, + struct tlv_parsed *new_attr, int kind, void *obj) +{ + if (kind == NM_OC_RADIO_CARRIER) { + struct gsm_bts_trx *trx = obj; + /*struct octphy_hdl *fl1h = trx_octphy_hdl(trx); */ + + power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0); + } + return oml_fom_ack_nack(msg, 0); +} + + +/* callback from OML */ +int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj) +{ + int rc = -1; + struct gsm_bts_trx_ts *ts; + + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + rc = trx_init(obj); + break; + case NM_OC_CHANNEL: + ts = (struct gsm_bts_trx_ts*) obj; + rc = ts_connect_as(ts, ts->pchan, pchan_act_compl_cb, NULL); + break; + case NM_OC_BTS: + case NM_OC_SITE_MANAGER: + case NM_OC_BASEB_TRANSC: + case NM_OC_GPRS_NSE: + case NM_OC_GPRS_CELL: + case NM_OC_GPRS_NSVC: + oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, -1); + rc = oml_mo_opstart_ack(mo); + break; + default: + rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP); + } + return rc; +} + +int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm) +{ +#pragma message ("Implement bts_model_change_power based on TRX_MODIFY_RF_CID (OS#3016)") + return 0; +} + +int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) +{ + struct phy_instance *pinst = trx_phy_instance(ts->trx); + struct octphy_hdl *fl1h = pinst->phy_link->u.octphy.hdl; + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_CMD *oc = + (tOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_CMD *) oc; + + oc = (tOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_CMD *) + msgb_put(msg, sizeof(*oc)); + l1if_fill_msg_hdr(&oc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_CID); + + oc->TrxId.byTrxId = pinst->u.octphy.trx_id; + oc->PchId.byTimeslotNb = ts->nr; + + LOGP(DL1C, LOGL_INFO, "PCHAN-DEACT.req(trx=%u, ts=%u, chcomb=%u)\n", + ts->trx->nr, ts->nr, ts->pchan); + + mOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_CMD_SWAP(oc); + + return l1if_req_compl(fl1h, msg, ts_disconnect_cb, NULL); +} + +int bts_model_ts_connect(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config as_pchan) +{ + if (as_pchan == GSM_PCHAN_TCH_F_PDCH + || as_pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + LOGP(DL1C, LOGL_ERROR, + "%s Requested TS connect as %s," + " expected a specific pchan instead\n", + gsm_ts_and_pchan_name(ts), gsm_pchan_name(as_pchan)); + exit(1); + return -EINVAL; + } + + return ts_connect_as(ts, as_pchan, ts_connect_cb, NULL); +} diff --git a/src/osmo-bts-octphy/l1_oml.h b/src/osmo-bts-octphy/l1_oml.h new file mode 100644 index 0000000..4729df5 --- /dev/null +++ b/src/osmo-bts-octphy/l1_oml.h @@ -0,0 +1,18 @@ +#pragma once + +#include "l1_if.h" + +/* channel control */ +int l1if_rsl_chan_act(struct gsm_lchan *lchan); +int l1if_rsl_chan_rel(struct gsm_lchan *lchan); +int l1if_rsl_deact_sacch(struct gsm_lchan *lchan); +int l1if_rsl_mode_modify(struct gsm_lchan *lchan); + +int l1if_set_ciphering(struct gsm_lchan *lchan, int dir_downlink); + +uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx); + +int gsm_abis_mo_check_attr(const struct gsm_abis_mo *mo, + const uint8_t * attr_ids, unsigned int num_attr_ids); + +int lchan_activate(struct gsm_lchan *lchan); diff --git a/src/osmo-bts-octphy/l1_tch.c b/src/osmo-bts-octphy/l1_tch.c new file mode 100644 index 0000000..df0469d --- /dev/null +++ b/src/osmo-bts-octphy/l1_tch.c @@ -0,0 +1,283 @@ +/* Traffic Channel (TCH) part of osmo-bts OCTPHY integration */ + +/* Copyright (c) 2014 Octasic Inc. All rights reserved. + * Copyright (c) 2015 Harald Welte + * + * based on a copy of osmo-bts-sysmo/l1_tch.c, which is + * Copyright (C) 2011-2013 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include + +#include "l1_if.h" + +struct msgb *l1_to_rtppayload_fr(uint8_t *l1_payload, uint8_t payload_len) +{ + struct msgb *msg; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + + /* step1: reverse the bit-order of each payload byte */ + osmo_revbytebits_buf(l1_payload, payload_len); + + cur = msgb_put(msg, GSM_FR_BYTES); + + /* step2: we need to shift the entire L1 payload by 4 bits right */ + osmo_nibble_shift_right(cur, l1_payload, GSM_FR_BITS / 4); + + cur[0] |= 0xD0; + + return msg; +} + +/*! \brief convert GSM-FR from RTP payload to L1 format + * \param[out] l1_payload payload part of L1 buffer + * \param[in] rtp_payload pointer to RTP payload data + * \param[in] payload_len length of \a rtp_payload + * \returns number of \a l1_payload bytes filled + */ +int rtppayload_to_l1_fr(uint8_t *l1_payload, const uint8_t *rtp_payload, + unsigned int payload_len) +{ + /* step2: we need to shift the RTP payload left by one nibble */ + osmo_nibble_shift_left_unal(l1_payload, rtp_payload, GSM_FR_BITS / 4); + + /* step1: reverse the bit-order of each payload byte */ + osmo_revbytebits_buf(l1_payload, payload_len); + return GSM_FR_BYTES; +} + +static struct msgb *l1_to_rtppayload_hr(uint8_t *l1_payload, uint8_t payload_len) +{ + struct msgb *msg; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + + if (payload_len != GSM_HR_BYTES) { + LOGP(DL1P, LOGL_ERROR, "L1 HR frame length %u != expected %u\n", + payload_len, GSM_HR_BYTES); + return NULL; + } + + cur = msgb_put(msg, GSM_HR_BYTES); + memcpy(cur, l1_payload, GSM_HR_BYTES); + + /* reverse the bit-order of each payload byte */ + osmo_revbytebits_buf(cur, GSM_HR_BYTES); + + return msg; +} + +/*! \brief convert GSM-FR from RTP payload to L1 format + * \param[out] l1_payload payload part of L1 buffer + * \param[in] rtp_payload pointer to RTP payload data + * \param[in] payload_len length of \a rtp_payload + * \returns number of \a l1_payload bytes filled + */ +static int rtppayload_to_l1_hr(uint8_t *l1_payload, const uint8_t *rtp_payload, + unsigned int payload_len) +{ + + if (payload_len != GSM_HR_BYTES) { + LOGP(DL1P, LOGL_ERROR, "RTP HR frame length %u != expected %u\n", + payload_len, GSM_HR_BYTES); + return 0; + } + + memcpy(l1_payload, rtp_payload, GSM_HR_BYTES); + + /* reverse the bit-order of each payload byte */ + osmo_revbytebits_buf(l1_payload, GSM_HR_BYTES); + + return GSM_HR_BYTES; +} + + +/* brief receive a traffic L1 primitive for a given lchan */ +int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, + tOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EVT * + data_ind) +{ + uint32_t payload_type = data_ind->Data.ulPayloadType; + uint8_t *payload = data_ind->Data.abyDataContent; + + uint32_t fn = data_ind->Data.ulFrameNumber; + uint16_t b_total = data_ind->MeasurementInfo.usBERTotalBitCnt; + uint16_t b_error = data_ind->MeasurementInfo.usBERCnt; + uint16_t ber10k = b_total ? BER_10K * b_error / b_total : 0; + int16_t lqual_cb = 0; /* FIXME: check min_qual_norm! */ + + uint8_t payload_len; + struct msgb *rmsg = NULL; + struct gsm_lchan *lchan = + &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)]; + + if (data_ind->Data.ulDataLength < 1) { + LOGPFN(DL1P, LOGL_DEBUG, fn, "chan_nr %d Rx Payload size 0\n", chan_nr); + /* Push empty payload to upper layers */ + rmsg = msgb_alloc_headroom(256, 128, "L1P-to-RTP"); + return add_l1sap_header(trx, rmsg, lchan, chan_nr, + data_ind->Data.ulFrameNumber, + ber10k, lqual_cb); + } + + payload_len = data_ind->Data.ulDataLength; + + switch (payload_type) { + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_FULL_RATE: + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_ENH_FULL_RATE: + if (lchan->type != GSM_LCHAN_TCH_F) + goto err_payload_match; + break; + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_HALF_RATE: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + break; + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_AMR_FULL_RATE: + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_AMR_HALF_RATE: + if (lchan->type != GSM_LCHAN_TCH_H && + lchan->type != GSM_LCHAN_TCH_F) + goto err_payload_match; + break; + default: + LOGPFN(DL1P, LOGL_NOTICE, fn, "%s Rx Payload Type %d is unsupported\n", + gsm_lchan_name(lchan), payload_type); + break; + } + + LOGPFN(DL1P, LOGL_DEBUG, fn, "%s Rx codec frame (%u): %s\n", gsm_lchan_name(lchan), + payload_len, osmo_hexdump(payload, payload_len)); + + switch (payload_type) { + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_FULL_RATE: + rmsg = l1_to_rtppayload_fr(payload, payload_len); + break; + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_HALF_RATE: + rmsg = l1_to_rtppayload_hr(payload, payload_len); + break; + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_ENH_FULL_RATE: + /* Currently not supported */ +#if 0 + rmsg = l1_to_rtppayload_efr(payload, payload_len); + break; +#endif + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_AMR_FULL_RATE: + case cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_AMR_HALF_RATE: + /* Currently not supported */ +#if 0 + rmsg = l1_to_rtppayload_amr(payload, payload_len, + &lchan->tch.amr_mr); +#else + LOGPFN(DL1P, LOGL_ERROR, fn, "OctPHY only supports FR!\n"); + return -1; +#endif + break; + } + + if (rmsg) + return add_l1sap_header(trx, rmsg, lchan, chan_nr, + data_ind->Data.ulFrameNumber, + ber10k, lqual_cb); + + return 0; + +err_payload_match: + LOGPFN(DL1P, LOGL_ERROR, fn, "%s Rx Payload Type %d incompatible with lchan\n", + gsm_lchan_name(lchan), payload_type); + return -EINVAL; +} + +#define RTP_MSGB_ALLOC_SIZE 512 + +/*! \brief function for incoming RTP via TCH.req + * \param rs RTP Socket + * \param[in] rtp_pl buffer containing RTP payload + * \param[in] rtp_pl_len length of \a rtp_pl + * + * This function prepares a msgb with a L1 PH-DATA.req primitive and + * queues it into lchan->dl_tch_queue. + * + * Note that the actual L1 primitive header is not fully initialized + * yet, as things like the frame number, etc. are unknown at the time we + * pre-fill the primtive. + */ +void l1if_tch_encode(struct gsm_lchan *lchan, uint32_t *payload_type, + uint8_t *data, uint32_t *len, const uint8_t *rtp_pl, + unsigned int rtp_pl_len) +{ + uint8_t *l1_payload; + int rc = -1; + + DEBUGP(DRTP, "%s RTP IN: %s\n", gsm_lchan_name(lchan), + osmo_hexdump(rtp_pl, rtp_pl_len)); + + l1_payload = &data[0]; + + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + if (lchan->type == GSM_LCHAN_TCH_F) { + *payload_type = cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_FULL_RATE; + rc = rtppayload_to_l1_fr(l1_payload, + rtp_pl, rtp_pl_len); + } else { + *payload_type = cOCTVC1_GSM_PAYLOAD_TYPE_ENUM_HALF_RATE; + /* Not supported currently */ + rc = rtppayload_to_l1_hr(l1_payload, + rtp_pl, rtp_pl_len); + } + break; + case GSM48_CMODE_SPEECH_EFR: + /* Not supported currently */ + case GSM48_CMODE_SPEECH_AMR: + /* Not supported currently */ + LOGP(DRTP, LOGL_ERROR, "OctPHY only supports FR!\n"); + default: + /* we don't support CSD modes */ + rc = -1; + break; + } + + if (rc < 0) { + LOGP(DRTP, LOGL_ERROR, "%s unable to parse RTP payload\n", + gsm_lchan_name(lchan)); + return; + } + + *len = rc; + + DEBUGP(DRTP, "%s RTP->L1: %s\n", gsm_lchan_name(lchan), + osmo_hexdump(data, *len)); +} diff --git a/src/osmo-bts-octphy/l1_utils.c b/src/osmo-bts-octphy/l1_utils.c new file mode 100644 index 0000000..8a8e155 --- /dev/null +++ b/src/osmo-bts-octphy/l1_utils.c @@ -0,0 +1,141 @@ +/* Layer 1 (PHY) Utilities of osmo-bts OCTPHY integration */ + +/* Copyright (c) 2014 Octasic Inc. All rights reserved. + * Copyright (c) 2015 Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include "l1_utils.h" +#include +#include +#include +#include + +const struct value_string octphy_l1sapi_names[23] = +{ + { cOCTVC1_GSM_SAPI_ENUM_IDLE, "IDLE" }, + { cOCTVC1_GSM_SAPI_ENUM_FCCH, "FCCH" }, + { cOCTVC1_GSM_SAPI_ENUM_SCH, "SCH" }, + { cOCTVC1_GSM_SAPI_ENUM_SACCH, "SACCH" }, + { cOCTVC1_GSM_SAPI_ENUM_SDCCH, "SDCCH" }, + { cOCTVC1_GSM_SAPI_ENUM_BCCH, "BCCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PCH_AGCH,"PCH_AGCH" }, + { cOCTVC1_GSM_SAPI_ENUM_CBCH, "CBCH" }, + { cOCTVC1_GSM_SAPI_ENUM_RACH, "RACH" }, + { cOCTVC1_GSM_SAPI_ENUM_TCHF, "TCH/F" }, + { cOCTVC1_GSM_SAPI_ENUM_FACCHF, "FACCH/F" }, + { cOCTVC1_GSM_SAPI_ENUM_TCHH, "TCH/H" }, + { cOCTVC1_GSM_SAPI_ENUM_FACCHH, "FACCH/H" }, + { cOCTVC1_GSM_SAPI_ENUM_NCH, "NCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PDTCH, "PDTCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PACCH, "PACCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PBCCH, "PBCCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PAGCH, "PAGCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PPCH, "PPCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PNCH, "PNCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PTCCH, "PTCCH" }, + { cOCTVC1_GSM_SAPI_ENUM_PRACH, "PRACH" }, + { 0, NULL } +}; + +const struct value_string octphy_dir_names[5] = +{ + { cOCTVC1_GSM_DIRECTION_ENUM_NONE, "None" }, + { cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS, "TX_BTS_MS(DL)" }, + { cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS, "RX_BTS_MS(UL)" }, + { cOCTVC1_GSM_DIRECTION_ENUM_TX_BTS_MS | cOCTVC1_GSM_DIRECTION_ENUM_RX_BTS_MS, "BOTH" }, + { 0, NULL } +}; + +const struct value_string octphy_clkmgr_state_vals[8] = { + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNINITIALIZE, "UNINITIALIZED" }, + +/* Note: Octasic renamed cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNUSED to + * cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_IDLE. The following ifdef + * statement ensures that older headers still work. */ +#ifdef cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNUSED + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNUSED, "UNUSED" }, +#else + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_IDLE, "IDLE" }, +#endif + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_NO_EXT_CLOCK, "NO_EXT_CLOCK" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_LOCKED, "LOCKED" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNLOCKED, "UNLOCKED" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_ERROR, "ERROR" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_DISABLE, "DISABLED" }, + { 0, NULL } +}; + +const struct value_string octphy_cid_vals[37] = { + { cOCTVC1_GSM_MSG_TRX_OPEN_CID, "TRX-OPEN" }, + { cOCTVC1_GSM_MSG_TRX_CLOSE_CID, "TRX-CLOSE" }, + { cOCTVC1_GSM_MSG_TRX_STATUS_CID, "TRX-STATUS" }, + { cOCTVC1_GSM_MSG_TRX_INFO_CID, "TRX-INFO" }, + { cOCTVC1_GSM_MSG_TRX_RESET_CID, "TRX-RESET" }, + { cOCTVC1_GSM_MSG_TRX_MODIFY_CID, "TRX-MODIFY" }, + { cOCTVC1_GSM_MSG_TRX_LIST_CID, "TRX-LIST" }, + { cOCTVC1_GSM_MSG_TRX_CLOSE_ALL_CID, "TRX-CLOSE-ALL" }, + { cOCTVC1_GSM_MSG_TRX_START_RECORD_CID, "RECORD-START" }, + { cOCTVC1_GSM_MSG_TRX_STOP_RECORD_CID, "RECORD-STOP" }, + { cOCTVC1_GSM_MSG_TRX_ACTIVATE_LOGICAL_CHANNEL_CID, "LCHAN-ACT" }, + { cOCTVC1_GSM_MSG_TRX_DEACTIVATE_LOGICAL_CHANNEL_CID, "LCHAN-DEACT" }, + { cOCTVC1_GSM_MSG_TRX_STATUS_LOGICAL_CHANNEL_CID, "LCHAN-STATUS" }, + { cOCTVC1_GSM_MSG_TRX_INFO_LOGICAL_CHANNEL_CID, "LCHAN-INFO" }, + { cOCTVC1_GSM_MSG_TRX_LIST_LOGICAL_CHANNEL_CID, "LCHAN-LIST" }, + { cOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_EMPTY_FRAME_CID, + "LCHAN-EMPTY-FRAME" }, + { cOCTVC1_GSM_MSG_TRX_REQUEST_LOGICAL_CHANNEL_DATA_CID, "LCHAN-DATA" }, + { cOCTVC1_GSM_MSG_TRX_ACTIVATE_PHYSICAL_CHANNEL_CID, "PCHAN-ACT" }, + { cOCTVC1_GSM_MSG_TRX_DEACTIVATE_PHYSICAL_CHANNEL_CID, "PCHAN-DEACT" }, + { cOCTVC1_GSM_MSG_TRX_STATUS_PHYSICAL_CHANNEL_CID, "PCHAN-STATUS" }, + { cOCTVC1_GSM_MSG_TRX_RESET_PHYSICAL_CHANNEL_CID, "PCHAN-RESET" }, + { cOCTVC1_GSM_MSG_TRX_LIST_PHYSICAL_CHANNEL_CID, "PCHAN-LIST" }, + { cOCTVC1_GSM_MSG_TRX_INFO_PHYSICAL_CHANNEL_CID, "PCHAN-INFO" }, + { cOCTVC1_GSM_MSG_TRX_MODIFY_PHYSICAL_CHANNEL_CIPHERING_CID, + "PCHAN-CIPH-MODIFY" }, + { cOCTVC1_GSM_MSG_TRX_INFO_PHYSICAL_CHANNEL_CIPHERING_CID, + "PCHAN-CIPH-INFO" }, + { cOCTVC1_GSM_MSG_TRX_INFO_PHYSICAL_CHANNEL_MEASUREMENT_CID, + "PCHAN-MEASUREMENT" }, + { cOCTVC1_GSM_MSG_TRX_INFO_RF_CID, "RF-INFO" }, + { cOCTVC1_GSM_MSG_TRX_MODIFY_RF_CID, "RF-MODIFY" }, + { cOCTVC1_GSM_MSG_TAP_FILTER_LIST_CID, "TAP-FILTER-LIST" }, + { cOCTVC1_GSM_MSG_TAP_FILTER_INFO_CID, "TAP-FILTER-INFO" }, + { cOCTVC1_GSM_MSG_TAP_FILTER_STATS_CID, "TAP-FILTER-STATS" }, + { cOCTVC1_GSM_MSG_TAP_FILTER_MODIFY_CID, "TAP-FILTER-MODIFY" }, + { cOCTVC1_MAIN_MSG_APPLICATION_INFO_CID, "MAIN_MSG_APP_INFO" }, + { cOCTVC1_MAIN_MSG_APPLICATION_INFO_SYSTEM_CID, "MAIN_MSG_APP_INFO_SYSTEM" }, + { cOCTVC1_GSM_MSG_TRX_START_LOGICAL_CHANNEL_RAW_DATA_INDICATIONS_CID, + "LCHAN-RAW-DATA-START" }, + { cOCTVC1_GSM_MSG_TRX_STOP_LOGICAL_CHANNEL_RAW_DATA_INDICATIONS_CID, + "LCHAN-RAW-DATA-STOP" }, + { 0, NULL } +}; + +const struct value_string octphy_eid_vals[7] = { + { cOCTVC1_GSM_MSG_TRX_TIME_INDICATION_EID, "TIME.ind" }, + { cOCTVC1_GSM_MSG_TRX_STATUS_CHANGE_EID, "TRX-STATUS-CHG.ind" }, + { cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_DATA_INDICATION_EID, + "LCHAN-DATA.ind" }, + { cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_READY_TO_SEND_INDICATION_EID, + "LCHAN-RTS.ind" }, + { cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RACH_INDICATION_EID, + "LCHAN-RACH.ind" }, + { cOCTVC1_GSM_MSG_TRX_LOGICAL_CHANNEL_RAW_DATA_INDICATION_EID, + "LCHAN-RAW-DATA.ind" }, + { 0, NULL } +}; diff --git a/src/osmo-bts-octphy/l1_utils.h b/src/osmo-bts-octphy/l1_utils.h new file mode 100644 index 0000000..d1a8717 --- /dev/null +++ b/src/osmo-bts-octphy/l1_utils.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +const struct value_string octphy_l1sapi_names[23]; +const struct value_string octphy_dir_names[5]; +const struct value_string octphy_clkmgr_state_vals[8]; +const struct value_string octphy_cid_vals[37]; +const struct value_string octphy_eid_vals[7]; diff --git a/src/osmo-bts-octphy/main.c b/src/osmo-bts-octphy/main.c new file mode 100644 index 0000000..928a4c8 --- /dev/null +++ b/src/osmo-bts-octphy/main.c @@ -0,0 +1,101 @@ +/* Main program of osmo-bts for OCTPHY-2G */ + +/* Copyright (c) 2014 Octasic Inc. All rights reserved. + * Copyright (c) 2015 Harald Welte + * + * based on a copy of osmo-bts-sysmo/main.c, which is + * Copyright (C) 2011-2013 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "l1_if.h" + +#define RF_LOCK_PATH "/var/lock/bts_rf_lock" + +extern int pcu_direct; +extern bool no_fw_check; + +int bts_model_print_help() +{ + printf(" -I --no-fw-check Override firmware version check\n"); + return 0; +} + +int bts_model_handle_options(int argc, char **argv) +{ + int num_errors = 0; + + while (1) { + int option_idx = 0, c; + static const struct option long_options[] = { + /* specific to this hardware */ + { "no-fw-check", 0, 0, 'I' }, + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "I", + long_options, &option_idx); + if (c == -1) + break; + + switch (c) { + case 'I': + no_fw_check = true; + break; + default: + num_errors++; + break; + } + } + + return num_errors; +} + +void bts_model_abis_close(struct gsm_bts *bts) +{ + /* for now, we simply terminate the program and re-spawn */ + bts_shutdown(bts, "Abis close"); +} + +int main(int argc, char **argv) +{ + return bts_main(argc, argv); +} diff --git a/src/osmo-bts-octphy/octphy_hw_api.c b/src/osmo-bts-octphy/octphy_hw_api.c new file mode 100644 index 0000000..6da038b --- /dev/null +++ b/src/osmo-bts-octphy/octphy_hw_api.c @@ -0,0 +1,404 @@ +/* Layer 1 (PHY) interface of osmo-bts OCTPHY integration */ + +/* Copyright (c) 2015 Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include + +#include +#include + +#include + +#include "l1_if.h" +#include "l1_oml.h" +#include "l1_utils.h" +#include "octphy_hw_api.h" + +#include +#include +#include + +/* Chapter 12.1 */ +static int get_pcb_info_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, void *data) +{ + tOCTVC1_HW_MSG_PCB_INFO_RSP *pir = + (tOCTVC1_HW_MSG_PCB_INFO_RSP *) resp->l2h; + + mOCTVC1_HW_MSG_PCB_INFO_RSP_SWAP(pir); + + LOGP(DL1C, LOGL_INFO, "HW-PCB-INFO.resp: Name=%s %s, Serial=%s, " + "FileName=%s, InfoSource=%u, InfoState=%u, GpsName=%s, " + "WiFiName=%s\n", pir->szName, pir->ulDeviceId ? "SEC" : "PRI", + pir->szSerial, pir->szFilename, pir->ulInfoSource, + pir->ulInfoState, pir->szGpsName, pir->szWifiName); + + msgb_free(resp); + return 0; +} + +/* Chapter 12.1 */ +int octphy_hw_get_pcb_info(struct octphy_hdl *fl1h) +{ + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_HW_MSG_PCB_INFO_CMD *pic; + + pic = (tOCTVC1_HW_MSG_PCB_INFO_CMD *) msgb_put(msg, sizeof(*pic)); + + l1if_fill_msg_hdr(&pic->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_HW_MSG_PCB_INFO_CID); + + mOCTVC1_HW_MSG_PCB_INFO_CMD_SWAP(pic); + + return l1if_req_compl(fl1h, msg, get_pcb_info_compl_cb, NULL); +} + +/* Chapter 12.9 */ +static int rf_port_info_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, + void *data) +{ + tOCTVC1_HW_MSG_RF_PORT_INFO_RSP *pir = + (tOCTVC1_HW_MSG_RF_PORT_INFO_RSP *) resp->l2h; + + mOCTVC1_HW_MSG_RF_PORT_INFO_RSP_SWAP(pir); + + LOGP(DL1C, LOGL_INFO, "RF-PORT-INFO.resp Idx=%u, InService=%u, " + "hOwner=0x%x, Id=%u, FreqMin=%u, FreqMax=%u\n", + pir->ulPortIndex, pir->ulInService, pir->hOwner, + pir->ulPortInterfaceId, pir->ulFrequencyMinKhz, + pir->ulFrequencyMaxKhz); + + msgb_free(resp); + return 0; +} + +/* Chapter 12.9 */ +int octphy_hw_get_rf_port_info(struct octphy_hdl *fl1h, uint32_t index) +{ + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_HW_MSG_RF_PORT_INFO_CMD *pic; + + pic = (tOCTVC1_HW_MSG_RF_PORT_INFO_CMD *) msgb_put(msg, sizeof(*pic)); + + l1if_fill_msg_hdr(&pic->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_HW_MSG_RF_PORT_INFO_CID); + + pic->ulPortIndex = index; + + mOCTVC1_HW_MSG_RF_PORT_INFO_CMD_SWAP(pic); + + return l1if_req_compl(fl1h, msg, rf_port_info_compl_cb, NULL); +} + +/* Chapter 12.10 */ +static int rf_port_stats_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, + void *data) +{ + struct octphy_hw_get_cb_data *get_cb_data; + + tOCTVC1_HW_MSG_RF_PORT_STATS_RSP *psr = + (tOCTVC1_HW_MSG_RF_PORT_STATS_RSP *) resp->l2h; + + mOCTVC1_HW_MSG_RF_PORT_STATS_RSP_SWAP(psr); + + LOGP(DL1C, LOGL_INFO, "RF-PORT-STATS.resp Idx=%u RadioStandard=%s, " + "Rx(Bytes=%u, Overflow=%u, AvgBps=%u, Period=%uus, Freq=%u) " + "Tx(Bytes=%i, Underflow=%u, AvgBps=%u, Period=%uus, Freq=%u)\n", + psr->ulPortIndex, + get_value_string(radio_std_vals, psr->ulRadioStandard), + psr->RxStats.ulRxByteCnt, psr->RxStats.ulRxOverflowCnt, + psr->RxStats.ulRxAverageBytePerSecond, + psr->RxStats.ulRxAveragePeriodUs, +#if OCTPHY_USE_FREQUENCY == 1 + psr->RxStats.Frequency.ulValue, +#else + psr->RxStats.ulFrequencyKhz, +#endif + psr->TxStats.ulTxByteCnt, psr->TxStats.ulTxUnderflowCnt, + psr->TxStats.ulTxAverageBytePerSecond, + psr->TxStats.ulTxAveragePeriodUs, +#if OCTPHY_USE_FREQUENCY == 1 + psr->TxStats.Frequency.ulValue); +#else + psr->TxStats.ulFrequencyKhz); +#endif + + get_cb_data = (struct octphy_hw_get_cb_data*) data; + get_cb_data->cb(resp,get_cb_data->data); + + msgb_free(resp); + return 0; +} + +/* Chapter 12.10 */ +int octphy_hw_get_rf_port_stats(struct octphy_hdl *fl1h, uint32_t index, + struct octphy_hw_get_cb_data *cb_data) +{ + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_HW_MSG_RF_PORT_STATS_CMD *psc; + + psc = (tOCTVC1_HW_MSG_RF_PORT_STATS_CMD *) msgb_put(msg, sizeof(*psc)); + + l1if_fill_msg_hdr(&psc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_HW_MSG_RF_PORT_STATS_CID); + + psc->ulPortIndex = index; + psc->ulResetStatsFlag = cOCT_FALSE; + + mOCTVC1_HW_MSG_RF_PORT_STATS_CMD_SWAP(psc); + + return l1if_req_compl(fl1h, msg, rf_port_stats_compl_cb, cb_data); +} + +static const struct value_string rx_gain_mode_vals[] = { + { cOCTVC1_RADIO_RX_GAIN_CTRL_MODE_ENUM_MGC, "Manual" }, + { cOCTVC1_RADIO_RX_GAIN_CTRL_MODE_ENUM_AGC_FAST_ATK, "Automatic (fast)" }, + { cOCTVC1_RADIO_RX_GAIN_CTRL_MODE_ENUM_AGC_SLOW_ATK, "Automatic (slow)" }, + { 0, NULL } +}; + +/* Chapter 12.13 */ +static int rf_ant_rx_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, + void *data) +{ + tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_RSP *arc = + (tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_RSP *) resp->l2h; + + mOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_RSP_SWAP(arc); + + LOGP(DL1C, LOGL_INFO, "ANT-RX-CONFIG.resp(Port=%u, Ant=%u): %s, " + "Gain %d dB, GainCtrlMode=%s\n", + arc->ulPortIndex, arc->ulAntennaIndex, +#ifdef OCTPHY_USE_RX_CONFIG + arc->RxConfig.ulEnableFlag ? "Enabled" : "Disabled", + arc->RxConfig.lRxGaindB/512, + get_value_string(rx_gain_mode_vals, arc->RxConfig.ulRxGainMode)); +#else + arc->ulEnableFlag ? "Enabled" : "Disabled", + arc->lRxGaindB/512, + get_value_string(rx_gain_mode_vals, arc->ulRxGainMode)); +#endif + msgb_free(resp); + return 0; +} + +/* Chapter 12.13 */ +int octphy_hw_get_rf_ant_rx_config(struct octphy_hdl *fl1h, uint32_t port_idx, + uint32_t ant_idx) +{ + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_CMD *psc; + + psc = (tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_CMD *) + msgb_put(msg, sizeof(*psc)); + + l1if_fill_msg_hdr(&psc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_CID); + + psc->ulPortIndex = port_idx; + psc->ulAntennaIndex = ant_idx; + + mOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_RX_CONFIG_CMD_SWAP(psc); + + return l1if_req_compl(fl1h, msg, rf_ant_rx_compl_cb, NULL); + +} + +/* Chapter 12.14 */ +static int rf_ant_tx_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, + void *data) +{ + tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_RSP *atc = + (tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_RSP *) resp->l2h; + + mOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_RSP_SWAP(atc); + + LOGP(DL1C, LOGL_INFO, "ANT-TX-CONFIG.resp(Port=%u, Ant=%u): %s, " + "Gain %d dB\n", + atc->ulPortIndex, atc->ulAntennaIndex, +#ifdef OCTPHY_USE_TX_CONFIG + atc->TxConfig.ulEnableFlag? "Enabled" : "Disabled", + atc->TxConfig.lTxGaindB/512); +#else + atc->ulEnableFlag ? "Enabled" : "Disabled", + atc->lTxGaindB/512); + +#endif + msgb_free(resp); + return 0; +} + +/* Chapter 12.14 */ +int octphy_hw_get_rf_ant_tx_config(struct octphy_hdl *fl1h, uint32_t port_idx, + uint32_t ant_idx) +{ + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_CMD *psc; + + psc = (tOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_CMD *) + msgb_put(msg, sizeof(*psc)); + + l1if_fill_msg_hdr(&psc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_CID); + + psc->ulPortIndex = port_idx; + psc->ulAntennaIndex = ant_idx; + + mOCTVC1_HW_MSG_RF_PORT_INFO_ANTENNA_TX_CONFIG_CMD_SWAP(psc); + + return l1if_req_compl(fl1h, msg, rf_ant_tx_compl_cb, NULL); + +} + +static const struct value_string clocksync_source_vals[] = { + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_ENUM_FREQ_1HZ, "1 Hz" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_ENUM_FREQ_10MHZ, "10 MHz" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_ENUM_FREQ_30_72MHZ, "30.72 MHz" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_ENUM_FREQ_1HZ_EXT, "1 Hz (ext)"}, + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_ENUM_NONE, "None" }, + { 0, NULL } +}; + +#if OCTPHY_USE_CLK_SOURCE_SELECTION == 1 +static const struct value_string clocksync_sel_vals[] = { + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_SELECTION_ENUM_AUTOSELECT, + "Autoselect" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_SELECTION_ENUM_CONFIG_FILE, + "Config File" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_SOURCE_SELECTION_ENUM_HOST_APPLICATION, + "Host Application" }, + { 0, NULL } +}; +#endif + +/* Chapter 12.15 */ +static int get_clock_sync_compl_cb(struct octphy_hdl *fl1, struct msgb *resp, + void *data) +{ + tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_RSP *cir = + (tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_RSP *) resp->l2h; + + mOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_RSP_SWAP(cir); + + LOGP(DL1C, LOGL_INFO, "CLOCK-SYNC-MGR-INFO.resp Reference=%s ", + get_value_string(clocksync_source_vals, cir->ulClkSourceRef)); + +#if OCTPHY_USE_CLK_SOURCE_SELECTION == 1 + LOGPC(DL1C, LOGL_INFO, "Selection=%s)\n", + get_value_string(clocksync_sel_vals, cir->ulClkSourceSelection)); +#else + LOGPC(DL1C, LOGL_INFO, "Clock Drift= %u Us\n", + cir->ulMaxDriftDurationUs); +#endif + + msgb_free(resp); + return 0; +} + +/* Chapter 12.15 */ +int octphy_hw_get_clock_sync_info(struct octphy_hdl *fl1h) +{ + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_CMD *cic; + + cic = (tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_CMD *) + msgb_put(msg, sizeof(*cic)); + l1if_fill_msg_hdr(&cic->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_CID); + + mOCTVC1_HW_MSG_CLOCK_SYNC_MGR_INFO_CMD_SWAP(cic); + + return l1if_req_compl(fl1h, msg, get_clock_sync_compl_cb, NULL); +} + +/* Chapter 12.16 */ +static int get_clock_sync_stats_cb(struct octphy_hdl *fl1, struct msgb *resp, + void *data) +{ + struct octphy_hw_get_cb_data *get_cb_data; + + tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP *csr = + (tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP *) resp->l2h; + + mOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP_SWAP(csr); + + LOGP(DL1C, LOGL_INFO, "CLOCK-SYNC-MGR-STATS.resp"); + LOGPC(DL1C, LOGL_INFO, " State=%s,", + get_value_string(clocksync_state_vals, csr->ulState)); +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_CLOCK_ERROR == 1 + LOGPC(DL1C, LOGL_INFO, " ClockError=%d,", csr->lClockError); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DROPPED_CYCLES == 1 + LOGPC(DL1C, LOGL_INFO, " DroppedCycles=%d,", csr->lDroppedCycles); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_PLL_FREQ_HZ == 1 + LOGPC(DL1C, LOGL_INFO, " PllFreqHz=%u,", csr->ulPllFreqHz); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_PLL_FRACTIONAL_FREQ_HZ == 1 + LOGPC(DL1C, LOGL_INFO, " PllFract=%u,", csr->ulPllFractionalFreqHz); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SLIP_CNT == 1 + LOGPC(DL1C, LOGL_INFO, " SlipCnt=%u,", csr->ulSlipCnt); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SYNC_LOSS_CNT == 1 + LOGPC(DL1C, LOGL_INFO, " SyncLosses=%u,", csr->ulSyncLossCnt); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SYNC_LOSSE_CNT == 1 + LOGPC(DL1C, LOGL_INFO, " SyncLosses=%u,", csr->ulSyncLosseCnt); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SOURCE_STATE == 1 + LOGPC(DL1C, LOGL_INFO, " SourceState=%u,", csr->ulSourceState); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DAC_STATE == 1 + LOGPC(DL1C, LOGL_INFO, " CLOCK-SYNC-MGR-STATS.resp State=%s,", + get_value_string(clocksync_dac_vals, csr->ulDacState)); +#endif + LOGPC(DL1C, LOGL_INFO, " LOCK-SYNC-MGR-USR-PROCESS.resp State=%s,", + get_value_string(usr_process_id, csr->ulOwnerProcessUid)); + LOGPC(DL1C, LOGL_INFO, " DacValue=%u,", csr->ulDacValue); +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DRIFT_ELAPSE_TIME_US == 1 + LOGPC(DL1C, LOGL_INFO, " DriftElapseTime=%u Us,", + csr->ulDriftElapseTimeUs); +#endif + LOGPC(DL1C, LOGL_INFO, "\n"); + + get_cb_data = (struct octphy_hw_get_cb_data*) data; + get_cb_data->cb(resp,get_cb_data->data); + + msgb_free(resp); + return 0; +} + +/* Chapter 12.16 */ +int octphy_hw_get_clock_sync_stats(struct octphy_hdl *fl1h, + struct octphy_hw_get_cb_data *cb_data) +{ + struct msgb *msg = l1p_msgb_alloc(); + tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_CMD *csc; + + csc = (tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_CMD *) + msgb_put(msg, sizeof(*csc)); + l1if_fill_msg_hdr(&csc->Header, msg, fl1h, cOCTVC1_MSG_TYPE_COMMAND, + cOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_CID); + + mOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_CMD_SWAP(csc); + + return l1if_req_compl(fl1h, msg, get_clock_sync_stats_cb, cb_data); +} + diff --git a/src/osmo-bts-octphy/octphy_hw_api.h b/src/osmo-bts-octphy/octphy_hw_api.h new file mode 100644 index 0000000..625fe86 --- /dev/null +++ b/src/osmo-bts-octphy/octphy_hw_api.h @@ -0,0 +1,84 @@ +#pragma once + +#include +#include "l1_if.h" +#include + +static const struct value_string radio_std_vals[] = { + { cOCTVC1_RADIO_STANDARD_ENUM_GSM, "GSM" }, + { cOCTVC1_RADIO_STANDARD_ENUM_UMTS, "UMTS" }, + { cOCTVC1_RADIO_STANDARD_ENUM_LTE, "LTE" }, + { cOCTVC1_RADIO_STANDARD_ENUM_INVALID, "INVALID" }, + { 0, NULL } +}; + +static const struct value_string clocksync_state_vals[] = { + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNINITIALIZE, + "Uninitialized" }, +/* Note: Octasic renamed cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNUSED to + * cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_IDLE. The following ifdef + * statement ensures that older headers still work. */ +#ifdef cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNUSED + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNUSED, "Unused" }, +#else + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_IDLE, "Idle" }, +#endif + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_NO_EXT_CLOCK, + "No External Clock" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_LOCKED, "Locked" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_UNLOCKED,"Unlocked" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_ERROR, "Error" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_DISABLE, "Disabled" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_STATE_ENUM_LOSS_EXT_CLOCK, + "Loss of Ext Clock" }, + { 0, NULL } +}; + +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DAC_STATE == 1 +static const struct value_string clocksync_dac_vals[] = { + { cOCTVC1_HW_CLOCK_SYNC_MGR_DAC_STATE_ENUM_UNUSED, "Unused" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_DAC_STATE_ENUM_MASTER, "Master" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_DAC_STATE_ENUM_SLAVE, "Slave" }, + { cOCTVC1_HW_CLOCK_SYNC_MGR_DAC_STATE_ENUM_FREE_RUNNING, "Free_Run"}, + { 0, NULL } +}; +#endif + +static const struct value_string usr_process_id[] = { + { cOCTVC1_USER_ID_PROCESS_ENUM_INVALID, "Invalid" }, + { cOCTVC1_USER_ID_PROCESS_ENUM_MAIN_APP, "MainApp" }, + { cOCTVC1_USER_ID_PROCESS_ENUM_MAIN_ROUTER, "MainRouter" }, + { cOCTVC1_USER_ID_PROCESS_ENUM_GSM_DL_0, "DL"}, + { cOCTVC1_USER_ID_PROCESS_ENUM_GSM_ULIM_0, "ULIM" }, + { cOCTVC1_USER_ID_PROCESS_ENUM_GSM_ULOM_0, "ULOM" }, + { cOCTVC1_USER_ID_PROCESS_ENUM_GSM_SCHED_0, "SCHED" }, +#ifdef cOCTVC1_USER_ID_PROCESS_ENUM_GSM_DECOMB + { cOCTVC1_USER_ID_PROCESS_ENUM_GSM_DECOMB, "DECOMB"}, +#endif +#ifdef cOCTVC1_USER_ID_PROCESS_ENUM_GSM_ULEQ + { cOCTVC1_USER_ID_PROCESS_ENUM_GSM_ULEQ, "ULEQ" }, +#endif +#ifdef cOCTVC1_USER_ID_PROCESS_ENUM_GSM_TEST + { cOCTVC1_USER_ID_PROCESS_ENUM_GSM_TEST, "TEST"}, +#endif + { 0, NULL } +}; + +typedef void octphy_hw_get_cb(struct msgb *resp, void *data); + +struct octphy_hw_get_cb_data { + octphy_hw_get_cb* cb; + void *data; +}; + +int octphy_hw_get_pcb_info(struct octphy_hdl *fl1h); +int octphy_hw_get_rf_port_info(struct octphy_hdl *fl1h, uint32_t index); +int octphy_hw_get_rf_port_stats(struct octphy_hdl *fl1h, uint32_t index, + struct octphy_hw_get_cb_data *cb_data); +int octphy_hw_get_rf_ant_rx_config(struct octphy_hdl *fl1h, uint32_t port_idx, + uint32_t ant_idx); +int octphy_hw_get_rf_ant_tx_config(struct octphy_hdl *fl1h, uint32_t port_idx, + uint32_t ant_idx); +int octphy_hw_get_clock_sync_info(struct octphy_hdl *fl1h); +int octphy_hw_get_clock_sync_stats(struct octphy_hdl *fl1h, + struct octphy_hw_get_cb_data *cb_data); diff --git a/src/osmo-bts-octphy/octphy_vty.c b/src/osmo-bts-octphy/octphy_vty.c new file mode 100644 index 0000000..e5e8eba --- /dev/null +++ b/src/osmo-bts-octphy/octphy_vty.c @@ -0,0 +1,437 @@ +/* VTY interface for osmo-bts OCTPHY integration */ + +/* (C) 2015-2016 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "l1_if.h" +#include "l1_utils.h" +#include "octphy_hw_api.h" + +#define TRX_STR "Transceiver related commands\n" "TRX number\n" + +#define SHOW_TRX_STR \ + SHOW_STR \ + TRX_STR + +#define OCT_STR "OCTPHY Um interface\n" + +static struct gsm_bts *vty_bts; + +/* configuration */ + +DEFUN(cfg_phy_hwaddr, cfg_phy_hwaddr_cmd, + "octphy hw-addr HWADDR", + OCT_STR "Configure the hardware addess of the OCTPHY\n" + "hardware address in aa:bb:cc:dd:ee:ff format\n") +{ + struct phy_link *plink = vty->index; + int rc; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Can only reconfigure a PHY link that is down%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + rc = osmo_macaddr_parse(plink->u.octphy.phy_addr.sll_addr, argv[0]); + if (rc < 0) + return CMD_WARNING; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_netdev, cfg_phy_netdev_cmd, + "octphy net-device NAME", + OCT_STR "Configure the hardware device towards the OCTPHY\n" + "Ethernet device name\n") +{ + struct phy_link *plink = vty->index; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Can only reconfigure a PHY link that is down%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + if (plink->u.octphy.netdev_name) + talloc_free(plink->u.octphy.netdev_name); + plink->u.octphy.netdev_name = talloc_strdup(plink, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_rf_port_idx, cfg_phy_rf_port_idx_cmd, + "octphy rf-port-index <0-255>", + OCT_STR "Configure the RF Port for this TRX\n" + "RF Port Index\n") +{ + struct phy_link *plink = vty->index; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Can only reconfigure a PHY link that is down%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + plink->u.octphy.rf_port_index = atoi(argv[0]); + + return CMD_SUCCESS; +} + +#if OCTPHY_USE_ANTENNA_ID == 1 +DEFUN(cfg_phy_rx_ant_id, cfg_phy_rx_ant_id_cmd, + "octphy rx-ant-id <0-1>", + OCT_STR "Configure the RX Antenna for this TRX\n" "RX Antenna Id\n") +{ + struct phy_link *plink = vty->index; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Can only reconfigure a PHY link that is down%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + plink->u.octphy.rx_ant_id = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_tx_ant_id, cfg_phy_tx_ant_id_cmd, + "octphy tx-ant-id <0-1>", + OCT_STR "Configure the TX Antenna for this TRX\n" "TX Antenna Id\n") +{ + struct phy_link *plink = vty->index; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Can only reconfigure a PHY link that is down%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + plink->u.octphy.tx_ant_id = atoi(argv[0]); + + return CMD_SUCCESS; +} +#endif + +DEFUN(cfg_phy_rx_gain_db, cfg_phy_rx_gain_db_cmd, + "octphy rx-gain <0-73>", + OCT_STR "Configure the Rx Gain in dB\n" + "Rx gain in dB\n") +{ + struct phy_link *plink = vty->index; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Can only reconfigure a PHY link that is down%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + plink->u.octphy.rx_gain_db = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_tx_atten_db, cfg_phy_tx_atten_db_cmd, + "octphy tx-attenuation (oml|<0-359>)", + OCT_STR "Set attenuation on transmitted RF\n" + "Use tx-attenuation according to OML instructions from BSC\n" + "Fixed tx-attenuation in quarter-dB\n") +{ + struct phy_link *plink = vty->index; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Can only reconfigure a PHY link that is down%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + if (strcmp(argv[0], "oml") == 0) { + plink->u.octphy.tx_atten_flag = false; + } else { + plink->u.octphy.tx_atten_db = atoi(argv[0]); + plink->u.octphy.tx_atten_flag = true; + } + + return CMD_SUCCESS; +} + +void show_rf_port_stats_cb(struct msgb *resp, void *data) +{ + struct vty *vty = (struct vty*) data; + tOCTVC1_HW_MSG_RF_PORT_STATS_RSP *psr; + + if (sizeof(tOCTVC1_HW_MSG_RF_PORT_STATS_RSP) != msgb_l2len(resp)) { + vty_out(vty, + "invalid msgb size (%d bytes, expected %zu bytes)%s", + msgb_l2len(resp), + sizeof(tOCTVC1_HW_MSG_RF_PORT_STATS_RSP), VTY_NEWLINE); + return; + } + + psr = (tOCTVC1_HW_MSG_RF_PORT_STATS_RSP *) msgb_l2(resp); + + vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, "RF-PORT-STATS:%s", VTY_NEWLINE); + vty_out(vty, "Idx=%d%s", psr->ulPortIndex, VTY_NEWLINE); + vty_out(vty, "RadioStandard=%s%s", + get_value_string(radio_std_vals, psr->ulRadioStandard), + VTY_NEWLINE); + vty_out(vty, "Rx Bytes=%u%s", psr->RxStats.ulRxByteCnt, VTY_NEWLINE); + vty_out(vty, "Rx Overflow=%u%s", psr->RxStats.ulRxOverflowCnt, + VTY_NEWLINE); + vty_out(vty, "Rx AvgBps=%u%s", psr->RxStats.ulRxAverageBytePerSecond, + VTY_NEWLINE); + vty_out(vty, "Rx Period=%u%s", psr->RxStats.ulRxAveragePeriodUs, + VTY_NEWLINE); +#if OCTPHY_USE_FREQUENCY == 1 + vty_out(vty, "Rx Freq=%u%s", psr->RxStats.Frequency.ulValue, VTY_NEWLINE); +#else + vty_out(vty, "Rx Freq=%u%s", psr->RxStats.ulFrequencyKhz, VTY_NEWLINE); +#endif + vty_out(vty, "Tx Bytes=%u%s", psr->TxStats.ulTxByteCnt, VTY_NEWLINE); + vty_out(vty, "Tx Underflow=%u%s", psr->TxStats.ulTxUnderflowCnt, + VTY_NEWLINE); + vty_out(vty, "Tx AvgBps=%u%s", psr->TxStats.ulTxAverageBytePerSecond, + VTY_NEWLINE); + vty_out(vty, "Tx Period=%u%s", psr->TxStats.ulTxAveragePeriodUs, + VTY_NEWLINE); +#if OCTPHY_USE_FREQUENCY == 1 + vty_out(vty, "Tx Freq=%u%s", psr->TxStats.Frequency.ulValue, VTY_NEWLINE); +#else + vty_out(vty, "Tx Freq=%u%s", psr->TxStats.ulFrequencyKhz, VTY_NEWLINE); +#endif +} + +DEFUN(show_rf_port_stats, show_rf_port_stats_cmd, + "show phy <0-255> rf-port-stats <0-1>", + "Show statistics for the RF Port\n" + "RF Port Number\n") +{ + int phy_nr = atoi(argv[0]); + struct phy_link *plink = phy_link_by_num(phy_nr); + static struct octphy_hw_get_cb_data cb_data; + + cb_data.cb = show_rf_port_stats_cb; + cb_data.data = vty; + + octphy_hw_get_rf_port_stats(plink->u.octphy.hdl, atoi(argv[1]), + &cb_data); + + return CMD_SUCCESS; +} + +void show_clk_sync_stats_cb(struct msgb *resp, void *data) +{ + struct vty *vty = (struct vty*) data; + tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP *csr; + + if (sizeof(tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP) != + msgb_l2len(resp)) { + vty_out(vty, + "invalid msgb size (%d bytes, expected %zu bytes)%s", + msgb_l2len(resp), + sizeof(tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP), + VTY_NEWLINE); + return; + } + + csr = (tOCTVC1_HW_MSG_CLOCK_SYNC_MGR_STATS_RSP *) msgb_l2(resp); + + vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, "CLOCK-SYNC-MGR-STATS:%s", VTY_NEWLINE); + vty_out(vty, "State=%s%s", + get_value_string(clocksync_state_vals, csr->ulState), + VTY_NEWLINE); +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_CLOCK_ERROR == 1 + vty_out(vty, "ClockError=%d%s", csr->lClockError, VTY_NEWLINE); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DROPPED_CYCLES == 1 + vty_out(vty, "DroppedCycles=%d%s", csr->lDroppedCycles, VTY_NEWLINE); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_PLL_FREQ_HZ == 1 + vty_out(vty, "PllFreqHz=%u%s", csr->ulPllFreqHz, VTY_NEWLINE); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_PLL_FRACTIONAL_FREQ_HZ == 1 + vty_out(vty, "PllFract=%u%s", csr->ulPllFractionalFreqHz, VTY_NEWLINE); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SLIP_CNT == 1 + vty_out(vty, "SlipCnt=%u%s", csr->ulSlipCnt, VTY_NEWLINE); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SYNC_LOSS_CNT == 1 + vty_out(vty, "SyncLosses=%u%s", csr->ulSyncLossCnt, VTY_NEWLINE); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SYNC_LOSSE_CNT == 1 + vty_out(vty, "SyncLosses=%u%s", csr->ulSyncLosseCnt, VTY_NEWLINE); +#endif +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_SOURCE_STATE == 1 + vty_out(vty, "SourceState=%u%s", csr->ulSourceState, VTY_NEWLINE); +#endif + vty_out(vty, "DacValue=%u%s", csr->ulDacValue, VTY_NEWLINE); +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DAC_STATE == 1 + vty_out(vty, "CLOCK-SYNC-MGR-STATS.resp State=%s%s", + get_value_string(clocksync_dac_vals, csr->ulDacState), + VTY_NEWLINE); +#endif + vty_out(vty, "LOCK-SYNC-MGR-USR-PROCESS.resp State=%s%s", + get_value_string(usr_process_id, csr->ulOwnerProcessUid), + VTY_NEWLINE); + vty_out(vty, "DacValue=%u%s", csr->ulDacValue, VTY_NEWLINE); +#if OCTPHY_USE_CLOCK_SYNC_MGR_STATS_DRIFT_ELAPSE_TIME_US == 1 + vty_out(vty, "DriftElapseTime=%u Us%s", csr->ulDriftElapseTimeUs, + VTY_NEWLINE); +#endif +} + +DEFUN(show_clk_sync_stats, show_clk_sync_stats_cmd, + "show phy <0-255> clk-sync-stats", + "Obtain statistics for the Clock Sync Manager\n") +{ + int phy_nr = atoi(argv[0]); + struct phy_link *plink = phy_link_by_num(phy_nr); + static struct octphy_hw_get_cb_data cb_data; + + cb_data.cb = show_clk_sync_stats_cb; + cb_data.data = vty; + + octphy_hw_get_clock_sync_stats(plink->u.octphy.hdl, &cb_data); + return CMD_SUCCESS; +} + +void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink) +{ + if (plink->u.octphy.netdev_name) + vty_out(vty, " octphy net-device %s%s", + plink->u.octphy.netdev_name, VTY_NEWLINE); + + vty_out(vty, " octphy hw-addr %02x:%02x:%02x:%02x:%02x:%02x%s", + plink->u.octphy.phy_addr.sll_addr[0], + plink->u.octphy.phy_addr.sll_addr[1], + plink->u.octphy.phy_addr.sll_addr[2], + plink->u.octphy.phy_addr.sll_addr[3], + plink->u.octphy.phy_addr.sll_addr[4], + plink->u.octphy.phy_addr.sll_addr[5], + VTY_NEWLINE); + vty_out(vty, " octphy rx-gain %u%s", plink->u.octphy.rx_gain_db, + VTY_NEWLINE); + + if (plink->u.octphy.tx_atten_flag) { + vty_out(vty, " octphy tx-attenuation %u%s", + plink->u.octphy.tx_atten_db, VTY_NEWLINE); + } else + vty_out(vty, " octphy tx-attenuation oml%s", VTY_NEWLINE); + + vty_out(vty, " octphy rf-port-index %u%s", plink->u.octphy.rf_port_index, + VTY_NEWLINE); + +#if OCTPHY_USE_ANTENNA_ID == 1 + vty_out(vty, " octphy tx-ant-id %u%s", plink->u.octphy.tx_ant_id, + VTY_NEWLINE); + + vty_out(vty, " octphy rx-ant-id %u%s", plink->u.octphy.rx_ant_id, + VTY_NEWLINE); +#endif +} + +void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst) +{ +} + +void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts) +{ +} + +void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx) +{ +} + +DEFUN(show_sys_info, show_sys_info_cmd, + "show phy <0-255> system-information", + SHOW_TRX_STR "Display information about system\n") +{ + int phy_nr = atoi(argv[0]); + struct phy_link *plink = phy_link_by_num(phy_nr); + struct octphy_hdl *fl1h; + + if (!plink) { + vty_out(vty, "Cannot find PHY number %u%s", + phy_nr, VTY_NEWLINE); + return CMD_WARNING; + } + fl1h = plink->u.octphy.hdl; + + vty_out(vty, "System Platform: '%s', Version: '%s'%s", + fl1h->info.system.platform, fl1h->info.system.version, + VTY_NEWLINE); + vty_out(vty, "Application Name: '%s', Description: '%s', Version: '%s'%s", + fl1h->info.app.name, fl1h->info.app.description, + fl1h->info.app.version, VTY_NEWLINE); + + return CMD_SUCCESS; +} + + +int bts_model_vty_init(struct gsm_bts *bts) +{ + vty_bts = bts; + + install_element(PHY_NODE, &cfg_phy_hwaddr_cmd); + install_element(PHY_NODE, &cfg_phy_netdev_cmd); + install_element(PHY_NODE, &cfg_phy_rf_port_idx_cmd); +#if OCTPHY_USE_ANTENNA_ID == 1 + install_element(PHY_NODE, &cfg_phy_rx_ant_id_cmd); + install_element(PHY_NODE, &cfg_phy_tx_ant_id_cmd); +#endif + install_element(PHY_NODE, &cfg_phy_rx_gain_db_cmd); + install_element(PHY_NODE, &cfg_phy_tx_atten_db_cmd); + + install_element_ve(&show_rf_port_stats_cmd); + install_element_ve(&show_clk_sync_stats_cmd); + install_element_ve(&show_sys_info_cmd); + + return 0; +} + +int bts_model_ctrl_cmds_install(struct gsm_bts *bts) +{ + return 0; +} diff --git a/src/osmo-bts-octphy/octpkt.c b/src/osmo-bts-octphy/octpkt.c new file mode 100644 index 0000000..d96d93d --- /dev/null +++ b/src/osmo-bts-octphy/octpkt.c @@ -0,0 +1,158 @@ +/* Utility routines for dealing with OCTPKT/OCTVC1 in msgb */ + +/* Copyright (c) 2015 Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include + +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "l1_if.h" +#include "octpkt.h" + +/* push a common header (1 dword) to the start of a msgb */ +void octpkt_push_common_hdr(struct msgb *msg, uint8_t format, + uint8_t trace, uint32_t ptype) +{ + uint32_t ch; + uint32_t *chptr; + uint32_t tot_len = msgb_length(msg) + sizeof(ch); + + ch = ((format & cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_FORMAT_BIT_MASK) + << cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_FORMAT_BIT_OFFSET) | + ((trace & cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_TRACE_BIT_MASK) + << cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_TRACE_BIT_OFFSET) | + ((ptype & cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_CONTROL_PROTOCOL_TYPE_BIT_MASK) + << cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_CONTROL_PROTOCOL_TYPE_BIT_OFFSET) | + (tot_len & cOCTPKT_HDR_FORMAT_PROTO_TYPE_LEN_MASK_LENGTH_BIT_MASK); + + chptr = (uint32_t *) msgb_push(msg, sizeof(ch)); + *chptr = htonl(ch); +} + +/* push a control header (3 dwords) to the start of a msgb. This format + * is used for command and response packets */ +void octvocnet_push_ctl_hdr(struct msgb *msg, uint32_t dest_fifo_id, + uint32_t src_fifo_id, uint32_t socket_id) +{ + tOCTVOCNET_PKT_CTL_HEADER *ch; + + ch = (tOCTVOCNET_PKT_CTL_HEADER *) msgb_push(msg, sizeof(*ch)); + + ch->ulDestFifoId = htonl(dest_fifo_id); + ch->ulSourceFifoId = htonl(src_fifo_id); + ch->ulSocketId = htonl(socket_id); +} + +/* common msg_header shared by all control messages. host byte order! */ +void octvc1_fill_msg_hdr(tOCTVC1_MSG_HEADER *mh, uint32_t len, + uint32_t sess_id, uint32_t trans_id, + uint32_t user_info, uint32_t msg_type, + uint32_t flags, uint32_t api_cmd) +{ + uint32_t type_r_cmdid; + type_r_cmdid = ((msg_type & cOCTVC1_MSG_TYPE_BIT_MASK) + << cOCTVC1_MSG_TYPE_BIT_OFFSET) | + ((api_cmd & cOCTVC1_MSG_ID_BIT_MASK) + << cOCTVC1_MSG_ID_BIT_OFFSET); + /* Resync? Flags? */ + + mh->ulLength = len; + mh->ulTransactionId = trans_id; + mh->ul_Type_R_CmdId = type_r_cmdid; + mh->ulSessionId = sess_id; + mh->ulReturnCode = 0; + mh->ulUserInfo = user_info; +} + +#include +#include +#include +#include +#include + +/*! \brief Initialize a packet socket + * \param[in] tye Socket type like SOCK_RAW or SOCK_DGRAM + * \param[in] proto The link-layer protocol in network byte order + * \param[in] bind_dev The name of the interface to bind to (if any) + * \param[in] flags flags like \ref OSMO_SOCK_F_BIND + * + * This function creates a new packet socket of \a type and \a proto + * and optionally bnds to it, if stated in the \a flags parameter. + */ +int osmo_sock_packet_init(uint16_t type, uint16_t proto, const char *bind_dev, + unsigned int flags) +{ + int sfd, rc, on = 1; + + if (flags & OSMO_SOCK_F_CONNECT) + return -EINVAL; + + sfd = socket(AF_PACKET, type, proto); + if (sfd < 0) + return -1; + + if (flags & OSMO_SOCK_F_NONBLOCK) { + if (ioctl(sfd, FIONBIO, (unsigned char *)&on) < 0) { + perror("cannot set this socket unblocking"); + close(sfd); + return -EINVAL; + } + } + + if (bind_dev) { + struct sockaddr_ll sa; + struct ifreq ifr; + + /* resolve the string device name to an ifindex */ + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, bind_dev, sizeof(ifr.ifr_name)); + rc = ioctl(sfd, SIOCGIFINDEX, &ifr); + if (rc < 0) + goto err; + + memset(&sa, 0, sizeof(sa)); + sa.sll_family = AF_PACKET; + sa.sll_protocol = htons(proto); + sa.sll_ifindex = ifr.ifr_ifindex; + /* according to the packet(7) man page, bind() will only + * use sll_protocol nad sll_ifindex */ + rc = bind(sfd, (struct sockaddr *)&sa, sizeof(sa)); + if (rc < 0) + goto err; + } + + return sfd; +err: + close(sfd); + return -1; +} diff --git a/src/osmo-bts-octphy/octpkt.h b/src/osmo-bts-octphy/octpkt.h new file mode 100644 index 0000000..fcffec0 --- /dev/null +++ b/src/osmo-bts-octphy/octpkt.h @@ -0,0 +1,22 @@ +#pragma once +#include + +/* push a common header (1 dword) to the start of a msgb */ +void octpkt_push_common_hdr(struct msgb *msg, uint8_t format, + uint8_t trace, uint32_t ptype); + +/* common msg_header shared by all control messages */ +void octvc1_fill_msg_hdr(tOCTVC1_MSG_HEADER *mh, uint32_t len, + uint32_t sess_id, uint32_t trans_id, + uint32_t user_info, uint32_t msg_type, + uint32_t flags, uint32_t api_cmd); + +/* push a control header (3 dwords) to the start of a msgb. This format + * is used for command and response packets */ +void octvocnet_push_ctl_hdr(struct msgb *msg, uint32_t dest_fifo_id, + uint32_t src_fifo_id, uint32_t socket_id); + +int osmo_sock_packet_init(uint16_t type, uint16_t proto, const char *bind_dev, + unsigned int flags); + +int tx_trx_open(struct gsm_bts_trx *trx); diff --git a/src/osmo-bts-omldummy/Makefile.am b/src/osmo-bts-omldummy/Makefile.am new file mode 100644 index 0000000..4ff28f0 --- /dev/null +++ b/src/osmo-bts-omldummy/Makefile.am @@ -0,0 +1,8 @@ +AM_CFLAGS = -Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS) $(ORTP_CFLAGS) +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -Iinclude +COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) $(ORTP_LIBS) -ldl + +bin_PROGRAMS = osmo-bts-omldummy + +osmo_bts_omldummy_SOURCES = main.c bts_model.c +osmo_bts_omldummy_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) diff --git a/src/osmo-bts-omldummy/bts_model.c b/src/osmo-bts-omldummy/bts_model.c new file mode 100644 index 0000000..9ade2c6 --- /dev/null +++ b/src/osmo-bts-omldummy/bts_model.c @@ -0,0 +1,217 @@ +/* (C) 2015 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* TODO: check if dummy method is sufficient, else implement */ +int bts_model_lchan_deactivate(struct gsm_lchan *lchan) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return -1; +} + +/* TODO: check if dummy method is sufficient, else implement */ +int osmo_amr_rtp_dec(const uint8_t *rtppayload, int payload_len, uint8_t *cmr, + int8_t *cmi, enum osmo_amr_type *ft, enum osmo_amr_quality *bfi, int8_t *sti) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return -1; +} + +int bts_model_trx_close(struct gsm_bts_trx *trx) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return 0; +} + +int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return 0; +} + +int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, + struct tlv_parsed *old_attr, struct tlv_parsed *new_attr, + void *obj) +{ + return 0; +} + +static uint8_t vbts_set_bts(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + uint8_t tn; + + llist_for_each_entry(trx, &bts->trx_list, list) { + oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK); + oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK); + + for (tn = 0; tn < TRX_NR_TS; tn++) + oml_mo_state_chg(&trx->ts[tn].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY); + + /* report availability of trx to the bts. this will trigger the rsl connection */ + oml_mo_tx_sw_act_rep(&trx->mo); + oml_mo_tx_sw_act_rep(&trx->bb_transc.mo); + } + return 0; +} + +static uint8_t vbts_set_trx(struct gsm_bts_trx *trx) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return 0; +} + +static uint8_t vbts_set_ts(struct gsm_bts_trx_ts *ts) +{ + return 0; +} + +int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, + struct tlv_parsed *new_attr, int kind, void *obj) +{ + struct abis_om_fom_hdr *foh = msgb_l3(msg); + int cause = 0; + + switch (foh->msg_type) { + case NM_MT_SET_BTS_ATTR: + cause = vbts_set_bts(obj); + break; + case NM_MT_SET_RADIO_ATTR: + cause = vbts_set_trx(obj); + break; + case NM_MT_SET_CHAN_ATTR: + cause = vbts_set_ts(obj); + break; + } + return oml_fom_ack_nack(msg, cause); +} + +/* MO: TS 12.21 Managed Object */ +int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj) +{ + int rc; + + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + case NM_OC_CHANNEL: + case NM_OC_SITE_MANAGER: + case NM_OC_BASEB_TRANSC: + case NM_OC_BTS: + case NM_OC_GPRS_NSE: + case NM_OC_GPRS_CELL: + case NM_OC_GPRS_NSVC: + oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); + rc = oml_mo_opstart_ack(mo); + break; + default: + rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP); + } + return rc; +} + +int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo, + void *obj, uint8_t adm_state) +{ + mo->nm_state.administrative = adm_state; + return oml_mo_statechg_ack(mo); +} + +int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return 0; +} + + +int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return 0; +} + +int bts_model_ctrl_cmds_install(struct gsm_bts *bts) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return 0; +} + +uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx) +{ + return 0; +} + +int bts_model_init(struct gsm_bts *bts) +{ + bts->variant = BTS_OSMO_OMLDUMMY; + return 0; +} + +void bts_model_print_help() +{ +} + +void bts_model_abis_close(struct gsm_bts *bts) +{ + bts_shutdown(bts, "Abis close"); +} + +void bts_model_phy_link_set_defaults(struct phy_link *plink) +{ +} + +void bts_model_phy_instance_set_defaults(struct phy_instance *pinst) +{ +} + +int bts_model_oml_estab(struct gsm_bts *bts) +{ + return 0; +} + +int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) +{ + return -ENOTSUP; +} + +int bts_model_ts_connect(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config as_pchan) +{ + return -ENOTSUP; +} + +int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) +{ + return 0; +} diff --git a/src/osmo-bts-omldummy/main.c b/src/osmo-bts-omldummy/main.c new file mode 100644 index 0000000..3f1d58c --- /dev/null +++ b/src/osmo-bts-omldummy/main.c @@ -0,0 +1,53 @@ + +#include +#include +#include +#include +#include +#include + + +int main(int argc, char **argv) +{ + struct gsm_bts *bts; + struct gsm_bts_trx *trx; + struct e1inp_line *line; + int i; + + char *dst_host = argv[1]; + int site_id = atoi(argv[2]); + + tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context"); + msgb_talloc_ctx_init(tall_bts_ctx, 10*1024); + + osmo_init_logging2(tall_bts_ctx, &bts_log_info); + + bts = gsm_bts_alloc(tall_bts_ctx, 0); + if (!bts) + exit(1); + bts->ip_access.site_id = site_id; + bts->ip_access.bts_id = 0; + + /* Additional TRXs */ + for (i = 1; i < 8; i++) { + trx = gsm_bts_trx_alloc(bts); + if (!trx) + exit(1); + } + + if (bts_init(bts) < 0) + exit(1); + //btsb = bts_role_bts(bts); + abis_init(bts); + + + line = abis_open(bts, dst_host, "OMLdummy"); + if (!line) + exit(2); + + while (1) { + osmo_select_main(0); + } + + return EXIT_SUCCESS; +} diff --git a/src/osmo-bts-omldummy/respawn.sh b/src/osmo-bts-omldummy/respawn.sh new file mode 100755 index 0000000..b025d43 --- /dev/null +++ b/src/osmo-bts-omldummy/respawn.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +while [ -f /etc/passwd ]; do + ./osmo-bts-omldummy $* + sleep 1 +done diff --git a/src/osmo-bts-sysmo/Makefile.am b/src/osmo-bts-sysmo/Makefile.am new file mode 100644 index 0000000..8e03b77 --- /dev/null +++ b/src/osmo-bts-sysmo/Makefile.am @@ -0,0 +1,43 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include $(SYSMOBTS_INCDIR) +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS) $(ORTP_CFLAGS) +COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) $(ORTP_LIBS) + +EXTRA_DIST = misc/sysmobts_mgr.h misc/sysmobts_misc.h misc/sysmobts_par.h \ + misc/sysmobts_eeprom.h misc/sysmobts_nl.h femtobts.h hw_misc.h \ + misc/sysmobts-layer1.h \ + l1_fwd.h l1_if.h l1_transp.h eeprom.h utils.h oml_router.h + +bin_PROGRAMS = osmo-bts-sysmo osmo-bts-sysmo-remote l1fwd-proxy sysmobts-mgr sysmobts-util + +COMMON_SOURCES = main.c femtobts.c l1_if.c oml.c sysmobts_vty.c tch.c hw_misc.c calib_file.c \ + eeprom.c calib_fixup.c utils.c misc/sysmobts_par.c oml_router.c sysmobts_ctrl.c + +osmo_bts_sysmo_SOURCES = $(COMMON_SOURCES) l1_transp_hw.c +osmo_bts_sysmo_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) + +osmo_bts_sysmo_remote_SOURCES = $(COMMON_SOURCES) l1_transp_fwd.c +osmo_bts_sysmo_remote_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) + +l1fwd_proxy_SOURCES = l1_fwd_main.c l1_transp_hw.c +l1fwd_proxy_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) + +if ENABLE_SYSMOBTS_CALIB +bin_PROGRAMS = sysmobts-calib + +sysmobts_calib_SOURCES = misc/sysmobts-calib.c misc/sysmobts-layer1.c +sysmobts_calib_LDADD = -lrt $(COMMON_LDADD) +endif + +sysmobts_mgr_SOURCES = \ + misc/sysmobts_mgr.c misc/sysmobts_misc.c \ + misc/sysmobts_par.c misc/sysmobts_nl.c \ + misc/sysmobts_mgr_2050.c \ + misc/sysmobts_mgr_vty.c \ + misc/sysmobts_mgr_nl.c \ + misc/sysmobts_mgr_temp.c \ + misc/sysmobts_mgr_calib.c \ + eeprom.c +sysmobts_mgr_LDADD = $(LIBGPS_LIBS) $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) + +sysmobts_util_SOURCES = misc/sysmobts_util.c misc/sysmobts_par.c eeprom.c +sysmobts_util_LDADD = $(LIBOSMOCORE_LIBS) diff --git a/src/osmo-bts-sysmo/calib_file.c b/src/osmo-bts-sysmo/calib_file.c new file mode 100644 index 0000000..8cb09d9 --- /dev/null +++ b/src/osmo-bts-sysmo/calib_file.c @@ -0,0 +1,475 @@ +/* sysmocom femtobts L1 calibration file routines*/ + +/* (C) 2012 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +#include "l1_if.h" +#include "femtobts.h" +#include "eeprom.h" +#include "utils.h" + +struct calib_file_desc { + const char *fname; + GsmL1_FreqBand_t band; + int uplink; + int rx; +}; + +static const struct calib_file_desc calib_files[] = { + { + .fname = "calib_rxu_850.cfg", + .band = GsmL1_FreqBand_850, + .uplink = 1, + .rx = 1, + }, { + .fname = "calib_rxu_900.cfg", + .band = GsmL1_FreqBand_900, + .uplink = 1, + .rx = 1, + }, { + .fname = "calib_rxu_1800.cfg", + .band = GsmL1_FreqBand_1800, + .uplink = 1, + .rx = 1, + }, { + .fname = "calib_rxu_1900.cfg", + .band = GsmL1_FreqBand_1900, + .uplink = 1, + .rx = 1, + }, { + .fname = "calib_rxd_850.cfg", + .band = GsmL1_FreqBand_850, + .uplink = 0, + .rx = 1, + }, { + .fname = "calib_rxd_900.cfg", + .band = GsmL1_FreqBand_900, + .uplink = 0, + .rx = 1, + }, { + .fname = "calib_rxd_1800.cfg", + .band = GsmL1_FreqBand_1800, + .uplink = 0, + .rx = 1, + }, { + .fname = "calib_rxd_1900.cfg", + .band = GsmL1_FreqBand_1900, + .uplink = 0, + .rx = 1, + }, { + .fname = "calib_tx_850.cfg", + .band = GsmL1_FreqBand_850, + .uplink = 0, + .rx = 0, + }, { + .fname = "calib_tx_900.cfg", + .band = GsmL1_FreqBand_900, + .uplink = 0, + .rx = 0, + }, { + .fname = "calib_tx_1800.cfg", + .band = GsmL1_FreqBand_1800, + .uplink = 0, + .rx = 0, + }, { + .fname = "calib_tx_1900.cfg", + .band = GsmL1_FreqBand_1900, + .uplink = 0, + .rx = 0, + + }, +}; + +static const unsigned int arrsize_by_band[] = { + [GsmL1_FreqBand_850] = 124, + [GsmL1_FreqBand_900] = 194, + [GsmL1_FreqBand_1800] = 374, + [GsmL1_FreqBand_1900] = 299 +}; + +/* determine next calibration file index based on supported bands */ +static int next_calib_file_idx(uint32_t band_mask, int last_idx) +{ + int i; + + for (i = last_idx+1; i < ARRAY_SIZE(calib_files); i++) { + int band = band_femto2osmo(calib_files[i].band); + if (band < 0) + continue; + if (band_mask & band) + return i; + } + return -1; +} + +static float read_float(FILE *in) +{ + int rc; + float f = 0.0f; + + rc = fscanf(in, "%f\n", &f); + if (rc != 1) + LOGP(DL1C, LOGL_ERROR, + "Reading a float from calib data failed.\n"); + return f; +} + +static int read_int(FILE *in) +{ + int rc; + int i = 0; + + rc = fscanf(in, "%d\n", &i); + if (rc != 1) + LOGP(DL1C, LOGL_ERROR, + "Reading an int from calib data failed.\n"); + return i; +} + +/* some particular units have calibration data that is incompatible with + * firmware >= 3.3, so we need to alter it as follows: */ +static const float delta_by_band[Num_GsmL1_FreqBand] = { + [GsmL1_FreqBand_850] = -2.5f, + [GsmL1_FreqBand_900] = -2.0f, + [GsmL1_FreqBand_1800] = -8.0f, + [GsmL1_FreqBand_1900] = -12.0f, +}; + +extern const uint8_t fixup_macs[95][6]; + + +static void determine_fixup(struct femtol1_hdl *fl1h) +{ + uint8_t macaddr[6]; + int rc, i; + + rc = eeprom_ReadEthAddr(macaddr); + if (rc != EEPROM_SUCCESS) { + LOGP(DL1C, LOGL_ERROR, + "Unable to read Ethenet MAC from EEPROM\n"); + return; + } + + /* assume no fixup is needed */ + fl1h->fixup_needed = FIXUP_NOT_NEEDED; + + if (fl1h->hw_info.dsp_version[0] < 3 || + (fl1h->hw_info.dsp_version[0] == 3 && + fl1h->hw_info.dsp_version[1] < 3)) { + LOGP(DL1C, LOGL_NOTICE, "No calibration table fix-up needed, " + "firmware < 3.3\n"); + return; + } + + for (i = 0; i < sizeof(fixup_macs)/6; i++) { + if (!memcmp(fixup_macs[i], macaddr, 6)) { + fl1h->fixup_needed = FIXUP_NEEDED; + break; + } + } + + LOGP(DL1C, LOGL_NOTICE, "MAC Address is %02x:%02x:%02x:%02x:%02x:%02x -> %s\n", + macaddr[0], macaddr[1], macaddr[2], macaddr[3], + macaddr[4], macaddr[5], + fl1h->fixup_needed == FIXUP_NEEDED ? "FIXUP" : "NO FIXUP"); +} + +static int fixup_needed(struct femtol1_hdl *fl1h) +{ + if (fl1h->fixup_needed == FIXUP_UNITILIAZED) + determine_fixup(fl1h); + + return fl1h->fixup_needed == FIXUP_NEEDED; +} + +static void calib_fixup_rx(struct femtol1_hdl *fl1h, SuperFemto_Prim_t *prim) +{ +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,4,0) + SuperFemto_SetRxCalibTblReq_t *rx = &prim->u.setRxCalibTblReq; + + if (fixup_needed(fl1h)) + rx->fExtRxGain += delta_by_band[rx->freqBand]; +#endif +} + +static int calib_file_read(const char *path, const struct calib_file_desc *desc, + SuperFemto_Prim_t *prim) +{ + FILE *in; + char fname[PATH_MAX]; + int i; + + fname[0] = '\0'; + snprintf(fname, sizeof(fname)-1, "%s/%s", path, desc->fname); + fname[sizeof(fname)-1] = '\0'; + + in = fopen(fname, "r"); + if (!in) { + LOGP(DL1C, LOGL_ERROR, + "Failed to open '%s' for calibration data.\n", fname); + return -1; + } + +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,4,0) + if (desc->rx) { + SuperFemto_SetRxCalibTblReq_t *rx = &prim->u.setRxCalibTblReq; + memset(rx, 0, sizeof(*rx)); + + prim->id = SuperFemto_PrimId_SetRxCalibTblReq; + + rx->freqBand = desc->band; + rx->bUplink = desc->uplink; + + rx->fExtRxGain = read_float(in); + rx->fRxMixGainCorr = read_float(in); + + for (i = 0; i < ARRAY_SIZE(rx->fRxLnaGainCorr); i++) + rx->fRxLnaGainCorr[i] = read_float(in); + + for (i = 0; i < arrsize_by_band[desc->band]; i++) + rx->fRxRollOffCorr[i] = read_float(in); + + if (desc->uplink) { + rx->u8IqImbalMode = read_int(in); + printf("%s: u8IqImbalMode=%d\n", desc->fname, rx->u8IqImbalMode); + + for (i = 0; i < ARRAY_SIZE(rx->u16IqImbalCorr); i++) + rx->u16IqImbalCorr[i] = read_int(in); + } + } else { + SuperFemto_SetTxCalibTblReq_t *tx = &prim->u.setTxCalibTblReq; + memset(tx, 0, sizeof(*tx)); + + prim->id = SuperFemto_PrimId_SetTxCalibTblReq; + + tx->freqBand = desc->band; + + for (i = 0; i < ARRAY_SIZE(tx->fTxGainGmsk); i++) + tx->fTxGainGmsk[i] = read_float(in); + + tx->fTx8PskCorr = read_float(in); + + for (i = 0; i < ARRAY_SIZE(tx->fTxExtAttCorr); i++) + tx->fTxExtAttCorr[i] = read_float(in); + + for (i = 0; i < arrsize_by_band[desc->band]; i++) + tx->fTxRollOffCorr[i] = read_float(in); + } +#else +#warning Format of calibration tables before API version 2.4.0 not supported +#endif + fclose(in); + + return 0; +} + +static int calib_eeprom_read(const struct calib_file_desc *desc, SuperFemto_Prim_t *prim) +{ + eeprom_Error_t eerr; + int i; + +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,4,0) + if (desc->rx) { + SuperFemto_SetRxCalibTblReq_t *rx = &prim->u.setRxCalibTblReq; + eeprom_RxCal_t rx_cal; + + memset(rx, 0, sizeof(*rx)); + + prim->id = SuperFemto_PrimId_SetRxCalibTblReq; + + rx->freqBand = desc->band; + rx->bUplink = desc->uplink; + + eerr = eeprom_ReadRxCal(desc->band, desc->uplink, &rx_cal); + if (eerr != EEPROM_SUCCESS) { + LOGP(DL1C, LOGL_ERROR, "Error reading RxCalibration " + "from EEPROM, band=%d, ul=%d, err=%d\n", + desc->band, desc->uplink, eerr); + return -EIO; + } + + rx->fExtRxGain = rx_cal.fExtRxGain; + rx->fRxMixGainCorr = rx_cal.fRxMixGainCorr; + + for (i = 0; i < ARRAY_SIZE(rx->fRxLnaGainCorr); i++) + rx->fRxLnaGainCorr[i] = rx_cal.fRxLnaGainCorr[i]; + + for (i = 0; i < arrsize_by_band[desc->band]; i++) + rx->fRxRollOffCorr[i] = rx_cal.fRxRollOffCorr[i]; + + if (desc->uplink) { + rx->u8IqImbalMode = rx_cal.u8IqImbalMode; + + for (i = 0; i < ARRAY_SIZE(rx->u16IqImbalCorr); i++) + rx->u16IqImbalCorr[i] = rx_cal.u16IqImbalCorr[i]; + } +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(5,1,0) + rx->u8DspMajVer = rx_cal.u8DspMajVer; + rx->u8DspMinVer = rx_cal.u8DspMinVer; + rx->u8FpgaMajVer = rx_cal.u8FpgaMajVer; + rx->u8FpgaMinVer = rx_cal.u8FpgaMinVer; +#endif + } else { + SuperFemto_SetTxCalibTblReq_t *tx = &prim->u.setTxCalibTblReq; + eeprom_TxCal_t tx_cal; + + memset(tx, 0, sizeof(*tx)); + + prim->id = SuperFemto_PrimId_SetTxCalibTblReq; + tx->freqBand = desc->band; + + eerr = eeprom_ReadTxCal(desc->band, &tx_cal); + if (eerr != EEPROM_SUCCESS) { + LOGP(DL1C, LOGL_ERROR, "Error reading TxCalibration " + "from EEPROM, band=%d, err=%d\n", + desc->band, eerr); + return -EIO; + } + + for (i = 0; i < ARRAY_SIZE(tx->fTxGainGmsk); i++) + tx->fTxGainGmsk[i] = tx_cal.fTxGainGmsk[i]; + + tx->fTx8PskCorr = tx_cal.fTx8PskCorr; + + for (i = 0; i < ARRAY_SIZE(tx->fTxExtAttCorr); i++) + tx->fTxExtAttCorr[i] = tx_cal.fTxExtAttCorr[i]; + + for (i = 0; i < arrsize_by_band[desc->band]; i++) + tx->fTxRollOffCorr[i] = tx_cal.fTxRollOffCorr[i]; +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(5,1,0) + tx->u8DspMajVer = tx_cal.u8DspMajVer; + tx->u8DspMinVer = tx_cal.u8DspMinVer; + tx->u8FpgaMajVer = tx_cal.u8FpgaMajVer; + tx->u8FpgaMinVer = tx_cal.u8FpgaMinVer; +#endif + } +#endif + + return 0; +} + +/* iteratively download the calibration data into the L1 */ + +static int calib_send_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data); + +/* send the calibration table for a single specified file */ +static int calib_file_send(struct femtol1_hdl *fl1h, + const struct calib_file_desc *desc) +{ + struct calib_send_state *st = &fl1h->st; + struct msgb *msg; + char *calib_path = fl1h->phy_inst->u.sysmobts.calib_path; + int rc; + + msg = sysp_msgb_alloc(); + + if (calib_path) + rc = calib_file_read(calib_path, desc, msgb_sysprim(msg)); + else + rc = calib_eeprom_read(desc, msgb_sysprim(msg)); + if (rc < 0) { + msgb_free(msg); + + /* still, we'd like to continue trying to load + * calibration for all other bands */ + st->last_file_idx = next_calib_file_idx(fl1h->hw_info.band_support, + st->last_file_idx); + if (st->last_file_idx >= 0) + return calib_file_send(fl1h, + &calib_files[st->last_file_idx]); + else + return rc; + } + calib_fixup_rx(fl1h, msgb_sysprim(msg)); + + return l1if_req_compl(fl1h, msg, calib_send_compl_cb, NULL); +} + +/* completion callback after every SetCalibTbl is confirmed */ +static int calib_send_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); + struct calib_send_state *st = &fl1h->st; + char *calib_path = fl1h->phy_inst->u.sysmobts.calib_path; + + LOGP(DL1C, LOGL_NOTICE, "L1 calibration table %s loaded (src: %s)\n", + calib_files[st->last_file_idx].fname, + calib_path ? "file" : "eeprom"); + + msgb_free(l1_msg); + + st->last_file_idx = next_calib_file_idx(fl1h->hw_info.band_support, + st->last_file_idx); + if (st->last_file_idx >= 0) + return calib_file_send(fl1h, + &calib_files[st->last_file_idx]); + + LOGP(DL1C, LOGL_INFO, "L1 calibration table loading complete!\n"); + eeprom_free_resources(); + + return 0; +} + + +int calib_load(struct femtol1_hdl *fl1h) +{ +#if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,4,0) + LOGP(DL1C, LOGL_ERROR, "L1 calibration is not supported on pre 2.4.0 firmware.\n"); + return -1; +#else + int idx = next_calib_file_idx(fl1h->hw_info.band_support, -1); + if (idx < 0) { + LOGP(DL1C, LOGL_ERROR, "No band_support?!?\n"); + return -1; + } + return calib_file_send(fl1h, &calib_files[idx]); +#endif +} + + +#if 0 +int main(int argc, char **argv) +{ + SuperFemto_Prim_t p; + int i; + + for (i = 0; i < ARRAY_SIZE(calib_files); i++) { + memset(&p, 0, sizeof(p)); + calib_read_file(argv[1], &calib_files[i], &p); + } + exit(0); +} +#endif diff --git a/src/osmo-bts-sysmo/calib_fixup.c b/src/osmo-bts-sysmo/calib_fixup.c new file mode 100644 index 0000000..29dd34d --- /dev/null +++ b/src/osmo-bts-sysmo/calib_fixup.c @@ -0,0 +1,101 @@ +/* AUTOGENERATED, DO NOT EDIT */ + +#include + +const uint8_t fixup_macs[95][6] = { + { 0x00, 0x0D, 0xCC, 0x08, 0x02, 0x3B }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x31 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x32 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x33 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x34 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x35 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x36 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x37 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x38 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x39 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x3A }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x3C }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x3D }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x3E }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x40 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x41 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x42 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x43 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x44 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x45 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x46 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x47 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x48 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x49 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x4A }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x4B }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x4C }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x4D }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x4E }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x4F }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x50 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x51 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x52 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x53 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x55 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x56 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x57 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x58 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x59 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x5A }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x5B }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x5C }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x5D }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x5E }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x5F }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x60 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x97 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x98 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x99 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x9A }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x9B }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x9C }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x9D }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x9E }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0x9F }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA0 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA1 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA3 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA4 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA5 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA6 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA7 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA8 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xA9 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xAA }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xAB }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xAC }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xAD }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xAE }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xAF }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB0 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB1 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB2 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB3 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB4 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB5 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB6 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB7 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB8 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xB9 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xBA }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xBB }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xBC }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xBE }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xBF }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC0 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC1 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC3 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC6 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC7 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC8 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xC9 }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xCA }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xCB }, + { 0x00, 0xD0, 0xCC, 0x08, 0x02, 0xCD }, +}; diff --git a/src/osmo-bts-sysmo/eeprom.c b/src/osmo-bts-sysmo/eeprom.c new file mode 100644 index 0000000..472b78e --- /dev/null +++ b/src/osmo-bts-sysmo/eeprom.c @@ -0,0 +1,1804 @@ +// $Id: $ +/**************************************************************************** + * + * **** I + * ****** *** + * ******* **** + * ******** **** **** **** ********* ******* **** *********** + * ********* **** **** **** ********* ************** ************* + * **** ***** **** **** **** **** ***** ****** ***** **** + * **** ***** **** **** **** **** ***** **** **** **** + * **** ********* **** **** **** **** **** **** **** + * **** ******** **** ****I **** ***** ***** **** **** + * **** ****** ***** ****** ***** ****** ******* ****** ******* + * **** **** ************ ****** ************* ************* + * **** *** **** **** **** ***** **** ***** **** + * **** + * I N N O V A T I O N T O D A Y F O R T O M M O R O W **** + * *** + * + ************************************************************************//** + * + * @file eeprom.c + * @brief SuperFemto EEPROM interface. + * + * Author : Yves Godin + * Date : 2012 + * $Revision: $ + * + * Copyright (c) Nutaq. 2012 + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + *************************************************************************** + * + * "$Revision: $" + * "$Name: $" + * "$Date: $" + * + ***************************************************************************/ +#include +#include +#include +#include +#include +#include + +#include "eeprom.h" + +//#define DISP_ERROR 1 + +#ifdef DISP_ERROR +#define PERROR(x, args ...) fprintf(stderr, x, ## args) +#else +#define PERROR(x, args ...) do { } while (0) +#endif + +/**************************************************************************** + * Private constants * + ****************************************************************************/ + +/** + * EEPROM device file + */ +#define EEPROM_DEV "/sys/bus/i2c/devices/i2c-1/1-0050/eeprom" + +/** + * EEPROM configuration start address + */ +#define EEPROM_CFG_START_ADDR 0x0100 + +/** + * EEPROM configuration max size + */ +#define EEPROM_CFG_MAX_SIZE (0x2000 - EEPROM_CFG_START_ADDR) + +/** + * EEPROM config magic ID + */ +#define EEPROM_CFG_MAGIC_ID 0x53464548 + +/** + * EEPROM header version + */ +#define EEPROM_HDR_V1 1 +#define EEPROM_HDR_V2 2 + +/** + * EEPROM section ID + */ +typedef enum +{ + EEPROM_SID_SYSINFO = 0x1000, ///< System information + EEPROM_SID_RFCLOCK_CAL = 0x2000, ///< RF Clock Calibration + EEPROM_SID_GSM850_TXCAL = 0x3000, ///< GSM-850 TX Calibration Table + EEPROM_SID_GSM850_RXUCAL = 0x3010, ///< GSM-850 RX Uplink Calibration Table + EEPROM_SID_GSM850_RXDCAL = 0x3020, ///< GSM-850 RX Downlink Calibration Table + EEPROM_SID_GSM900_TXCAL = 0x3100, ///< GSM-900 TX Calibration Table + EEPROM_SID_GSM900_RXUCAL = 0x3110, ///< GSM-900 RX Uplink Calibration Table + EEPROM_SID_GSM900_RXDCAL = 0x3120, ///< GSM-900 RX Downlink Calibration Table + EEPROM_SID_DCS1800_TXCAL = 0x3200, ///< DCS-1800 TX Calibration Table + EEPROM_SID_DCS1800_RXUCAL = 0x3210, ///< DCS-1800 RX Uplink Calibration Table + EEPROM_SID_DCS1800_RXDCAL = 0x3220, ///< DCS-1800 RX Downlink Calibration Table + EEPROM_SID_PCS1900_TXCAL = 0x3300, ///< PCS-1900 TX Calibration Table + EEPROM_SID_PCS1900_RXUCAL = 0x3310, ///< PCS-1900 RX Uplink Calibration Table + EEPROM_SID_PCS1900_RXDCAL = 0x3320, ///< PCS-1900 RX Downlink Calibration Table + EEPROM_SID_ASSY = 0x3400 ///< Assembly information +} eeprom_SID_t; + +/**************************************************************************** + * Private types * + ****************************************************************************/ + +/** + * TX calibration table (common part) V1 + */ +typedef struct +{ + uint16_t u16SectionID; ///< Section ID + uint16_t u16Crc; ///< Parity + uint32_t u32Time; ///< Epoch time + + int16_t sfixTxGainGmsk[80]; ///< [Q10.5] Gain setting for GMSK output level from +50dBm to -29 dBm + int16_t sfixTx8PskCorr; ///< [Q6.9] Gain adjustment for 8 PSK (default to +3.25 dB) + int16_t sfixTxExtAttCorr[31]; ///< [Q6.9] Gain adjustment for external attenuator (0:@1dB, 1:@2dB, ..., 31:@32dB) + int16_t sfixTxRollOffCorr[0]; ///< [Q6.9] Gain correction for each ARFCN +} __attribute__((packed)) eeprom_CfgTxCal_t; + +/** + * RX calibration table (common part) V1 + */ +typedef struct +{ + uint16_t u16SectionID; ///< Section ID + uint16_t u16Crc; ///< Parity + uint32_t u32Time; ///< Epoch time + + uint16_t u16IqImbalMode; ///< IQ imbalance mode (0:off, 1:on, 2:auto) + uint16_t u16IqImbalCorr[4]; ///< IQ imbalance compensation + + int16_t sfixExtRxGain; ///< [Q6.9] External RX gain + int16_t sfixRxMixGainCorr; ///< [Q6.9] Mixer gain error compensation + int16_t sfixRxLnaGainCorr[3]; ///< [Q6.9] LNA gain error compensation (1:@-12 dB, 2:@-24 dB, 3:@-36 dB) + int16_t sfixRxRollOffCorr[0]; ///< [Q6.9] Frequency roll-off compensation +} __attribute__((packed)) eeprom_CfgRxCal_t; + +/** + * TX calibration table (common part) V2 + */ +typedef struct +{ + uint16_t u16SectionID; ///< Section ID + uint16_t u16Crc; ///< Parity + uint32_t u32Time; ///< Epoch time + uint8_t u8DspMajVer; ///< DSP firmware major version + uint8_t u8DspMinVer; ///< DSP firmware minor version + uint8_t u8FpgaMajVer; ///< FPGA firmware major version + uint8_t u8FpgaMinVer; ///< FPGA firmware minor version + int16_t sfixTxGainGmsk[80]; ///< [Q10.5] Gain setting for GMSK output level from +50dBm to -29 dBm + int16_t sfixTx8PskCorr; ///< [Q6.9] Gain adjustment for 8 PSK (default to +3.25 dB) + int16_t sfixTxExtAttCorr[31]; ///< [Q6.9] Gain adjustment for external attenuator (0:@1dB, 1:@2dB, ..., 31:@32dB) + int16_t sfixTxRollOffCorr[0]; ///< [Q6.9] Gain correction for each ARFCN +} __attribute__((packed)) eeprom_CfgTxCalV2_t; + +/** + * RX calibration table (common part) V2 + */ +typedef struct +{ + uint16_t u16SectionID; ///< Section ID + uint16_t u16Crc; ///< Parity + uint32_t u32Time; ///< Epoch time + uint8_t u8DspMajVer; ///< DSP firmware major version + uint8_t u8DspMinVer ; ///< DSP firmware minor version + uint8_t u8FpgaMajVer; ///< FPGA firmware major version + uint8_t u8FpgaMinVer; ///< FPGA firmware minor version + uint16_t u16IqImbalMode; ///< IQ imbalance mode (0:off, 1:on, 2:auto) + uint16_t u16IqImbalCorr[4]; ///< IQ imbalance compensation + int16_t sfixExtRxGain; ///< [Q6.9] External RX gain + int16_t sfixRxMixGainCorr; ///< [Q6.9] Mixer gain error compensation + int16_t sfixRxLnaGainCorr[3]; ///< [Q6.9] LNA gain error compensation (1:@-12 dB, 2:@-24 dB, 3:@-36 dB) + int16_t sfixRxRollOffCorr[0]; ///< [Q6.9] Frequency roll-off compensation +} __attribute__((packed)) eeprom_CfgRxCalV2_t; + + +/** + * EEPROM configuration area format + */ +typedef struct +{ + struct + { + uint32_t u32MagicId; ///< Magic ID (0x53464548) + uint32_t u16Version : 16; ///< Header format version (v1) + uint32_t : 16; ///< unused + } hdr; + + union + { + /** EEPROM Format V1 */ + struct + { + /** System information */ + struct + { + uint16_t u16SectionID; ///< Section ID + uint16_t u16Crc; ///< Parity + uint32_t u32Time; ///< Epoch time + + char szSn[16]; ///< Serial number + uint32_t u8Rev : 8; ///< Board revision + uint32_t u2Tcxo : 2; ///< TCXO present (0:absent, 1:present, x:unknows) + uint32_t u2Ocxo : 2; ///< OCXO present (0:absent, 1:present, x:unknows) + uint32_t u2GSM850 : 2; ///< GSM-850 supported (0:unsupported, 1:supported, x:unknows) + uint32_t u2GSM900 : 2; ///< GSM-900 supported (0:unsupported, 1:supported, x:unknows) + uint32_t u2DCS1800 : 2; ///< GSM-1800 supported (0:unsupported, 1:supported, x:unknows) + uint32_t u2PCS1900 : 2; ///< GSM-1900 supported (0:unsupported, 1:supported, x:unknows) + uint32_t : 12; ///< unused + } __attribute__((packed)) sysInfo; + + /** RF Clock configuration */ + struct + { + uint16_t u16SectionID; ///< Section ID + uint16_t u16Crc; ///< Parity + uint32_t u32Time; ///< Epoch time + + int i24ClkCor :24; ///< Clock correction value in PPB. + uint32_t u8ClkSrc : 8; ///< Clock source (0:None, 1:OCXO, 2:TCXO, 3:External, 4:GPS PPS, 5:reserved, 6:RX, 7:Edge) + } __attribute__((packed)) rfClk; + + /** GSM-850 TX Calibration Table */ + eeprom_CfgTxCal_t gsm850TxCal; + uint16_t __gsm850TxCalMem[124]; + + /** GSM-850 RX Uplink Calibration Table */ + eeprom_CfgRxCal_t gsm850RxuCal; + uint16_t __gsm850RxuCalMem[124]; + + /** GSM-850 RX Downlink Calibration Table */ + eeprom_CfgRxCal_t gsm850RxdCal; + uint16_t __gsm850RxdCalMem[124]; + + /** GSM-900 TX Calibration Table */ + eeprom_CfgTxCal_t gsm900TxCal; + uint16_t __gsm900TxCalMem[194]; + + /** GSM-900 RX Uplink Calibration Table */ + eeprom_CfgRxCal_t gsm900RxuCal; + uint16_t __gsm900RxuCalMem[194]; + + /** GSM-900 RX Downlink Calibration Table */ + eeprom_CfgRxCal_t gsm900RxdCal; + uint16_t __gsm900RxdCalMem[194]; + + /** DCS-1800 TX Calibration Table */ + eeprom_CfgTxCal_t dcs1800TxCal; + uint16_t __dcs1800TxCalMem[374]; + + /** DCS-1800 RX Uplink Calibration Table */ + eeprom_CfgRxCal_t dcs1800RxuCal; + uint16_t __dcs1800RxuCalMem[374]; + + /** DCS-1800 RX Downlink Calibration Table */ + eeprom_CfgRxCal_t dcs1800RxdCal; + uint16_t __dcs1800RxdCalMem[374]; + + /** PCS-1900 TX Calibration Table */ + eeprom_CfgTxCal_t pcs1900TxCal; + uint16_t __pcs1900TxCalMem[299]; + + /** PCS-1900 RX Uplink Calibration Table */ + eeprom_CfgRxCal_t pcs1900RxuCal; + uint16_t __pcs1900RxuCalMem[299]; + + /** PCS-1900 RX Downlink Calibration Table */ + eeprom_CfgRxCal_t pcs1900RxdCal; + uint16_t __pcs1900RxdCalMem[299]; + + } __attribute__((packed)) v1; + + /** EEPROM Format V2 */ + struct + { + /** System information */ + struct + { + uint16_t u16SectionID; ///< Section ID + uint16_t u16Crc; ///< Parity + uint32_t u32Time; ///< Epoch time + char szSn[16]; ///< Serial number + uint32_t u8Rev : 8; ///< Board revision + uint32_t u2Tcxo : 2; ///< TCXO present (0:absent, 1:present, x:unknows) + uint32_t u2Ocxo : 2; ///< OCXO present (0:absent, 1:present, x:unknows) + uint32_t u2GSM850 : 2; ///< GSM-850 supported (0:unsupported, 1:supported, x:unknows) + uint32_t u2GSM900 : 2; ///< GSM-900 supported (0:unsupported, 1:supported, x:unknows) + uint32_t u2DCS1800 : 2; ///< GSM-1800 supported (0:unsupported, 1:supported, x:unknows) + uint32_t u2PCS1900 : 2; ///< GSM-1900 supported (0:unsupported, 1:supported, x:unknows) + uint32_t : 12; ///< unused + } __attribute__((packed)) sysInfo; + + /** RF Clock configuration */ + struct + { + uint16_t u16SectionID; ///< Section ID + uint16_t u16Crc; ///< Parity + uint32_t u32Time; ///< Epoch time + + int i24ClkCor :24; ///< Clock correction value in PPB. + uint32_t u8ClkSrc : 8; ///< Clock source (0:None, 1:OCXO, 2:TCXO, 3:External, 4:GPS PPS, 5:reserved, 6:RX, 7:Edge) + } __attribute__((packed)) rfClk; + + /** GSM-850 TX Calibration Table */ + eeprom_CfgTxCalV2_t gsm850TxCalV2; + uint16_t __gsm850TxCalMemV2[124]; + + /** GSM-850 RX Uplink Calibration Table */ + eeprom_CfgRxCalV2_t gsm850RxuCalV2; + uint16_t __gsm850RxuCalMemV2[124]; + + /** GSM-850 RX Downlink Calibration Table */ + eeprom_CfgRxCalV2_t gsm850RxdCalV2; + uint16_t __gsm850RxdCalMemV2[124]; + + /** GSM-900 TX Calibration Table */ + eeprom_CfgTxCalV2_t gsm900TxCalV2; + uint16_t __gsm900TxCalMemV2[194]; + + /** GSM-900 RX Uplink Calibration Table */ + eeprom_CfgRxCalV2_t gsm900RxuCalV2; + uint16_t __gsm900RxuCalMemV2[194]; + + /** GSM-900 RX Downlink Calibration Table */ + eeprom_CfgRxCalV2_t gsm900RxdCalV2; + uint16_t __gsm900RxdCalMemV2[194]; + + /** DCS-1800 TX Calibration Table */ + eeprom_CfgTxCalV2_t dcs1800TxCalV2; + uint16_t __dcs1800TxCalMemV2[374]; + + /** DCS-1800 RX Uplink Calibration Table */ + eeprom_CfgRxCalV2_t dcs1800RxuCalV2; + uint16_t __dcs1800RxuCalMemV2[374]; + + /** DCS-1800 RX Downlink Calibration Table */ + eeprom_CfgRxCalV2_t dcs1800RxdCalV2; + uint16_t __dcs1800RxdCalMemV2[374]; + + /** PCS-1900 TX Calibration Table */ + eeprom_CfgTxCalV2_t pcs1900TxCalV2; + uint16_t __pcs1900TxCalMemV2[299]; + + /** PCS-1900 RX Uplink Calibration Table */ + eeprom_CfgRxCalV2_t pcs1900RxuCalV2; + uint16_t __pcs1900RxuCalMemV2[299]; + + /** PCS-1900 RX Downlink Calibration Table */ + eeprom_CfgRxCalV2_t pcs1900RxdCalV2; + uint16_t __pcs1900RxdCalMemV2[299]; + + /** Assembly information */ + struct + { + uint16_t u16SectionID; ///< Section ID + uint16_t u16Crc; ///< Parity + uint32_t u32Time; ///< Epoch time + char szSn[16]; ///< System serial number + char szPartNum[20]; ///< System part number + uint8_t u8TsID ; ///< Test station ID + uint8_t u8TstVer ; ///< Test version + uint8_t u8PaType; ///< PA type (0: None, 1-254 supported, 255 ; Unknown) + uint8_t u8PaBand; ///< PA GSM band (0: Unknown, 1: 850 MHz, 2: 900 MHz, 4: 1800 MHz, 8: 1900 MHz) + uint8_t u8PaMajVer; ///< PA major version + uint8_t u8PaMinVer; ///< PA minor version + } __attribute__((packed)) assyInfo; + } __attribute__((packed)) v2; + } __attribute__((packed)) cfg; +} __attribute__((packed)) eeprom_Cfg_t; + + + +/**************************************************************************** + * Private routine prototypes * + ****************************************************************************/ + +static int eeprom_read( int addr, int size, char *pBuff ); +static int eeprom_write( int addr, int size, const char *pBuff ); +static uint16_t eeprom_crc( uint8_t *pu8Data, int len ); +static eeprom_Cfg_t *eeprom_cached_config(void); + + +/**************************************************************************** + * Public functions * + ****************************************************************************/ + +/**************************************************************************** + * Function : eeprom_ResetCfg + ************************************************************************//** + * + * This function reset the content of the EEPROM config area. + * + * @return + * 0 if or an error core. + * + ****************************************************************************/ +eeprom_Error_t eeprom_ResetCfg( void ) +{ + int err; + eeprom_Cfg_t ee; + + // Clear the structure + memset( &ee, 0xFF, sizeof(eeprom_Cfg_t) ); + + // Init the header + ee.hdr.u32MagicId = EEPROM_CFG_MAGIC_ID; + ee.hdr.u16Version = EEPROM_HDR_V2; + + // Write it to the EEPROM + err = eeprom_write( EEPROM_CFG_START_ADDR, sizeof(ee.hdr) + sizeof(ee.cfg.v2), (const char *) &ee ); + if ( err != sizeof(ee.hdr) + sizeof(ee.cfg.v2) ) + { + return EEPROM_ERR_DEVICE; + } + return EEPROM_SUCCESS; +} + + +eeprom_Error_t eeprom_ReadEthAddr( uint8_t *ethaddr ) +{ + int err; + + err = eeprom_read(0, 6, (char *) ethaddr); + if ( err != 6 ) + { + return EEPROM_ERR_DEVICE; + } + return EEPROM_SUCCESS; +} + +/**************************************************************************** + * Function : eeprom_ReadSysInfo + ************************************************************************//** + * + * This function reads the system information from the EEPROM. + * + * @param [inout] pTime + * Pointer to a system info structure. + * + * @param [inout] pSysInfo + * Pointer to a system info structure. + * + * @return + * 0 if or an error core. + * + ****************************************************************************/ +eeprom_Error_t eeprom_ReadSysInfo( eeprom_SysInfo_t *pSysInfo ) +{ + int err; + eeprom_Cfg_t ee; + + // Get a copy of the EEPROM header + err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(ee.hdr), (char *) &ee.hdr ); + if ( err != sizeof(ee.hdr) ) + { + PERROR( "Error while reading the EEPROM content (%d)\n", err ); + return EEPROM_ERR_DEVICE; + } + + // Validate the header magic ID + if ( ee.hdr.u32MagicId != EEPROM_CFG_MAGIC_ID ) + { + PERROR( "Invalid EEPROM format\n" ); + return EEPROM_ERR_INVALID; + } + + switch ( ee.hdr.u16Version ) + { + case EEPROM_HDR_V1: + case EEPROM_HDR_V2: + { + // Get a copy of the EEPROM section + err = eeprom_read( EEPROM_CFG_START_ADDR + offsetof(eeprom_Cfg_t, cfg.v1.sysInfo), sizeof(ee.cfg.v1.sysInfo), (char *)&ee.cfg.v1.sysInfo ); + if ( err != sizeof(ee.cfg.v1.sysInfo) ) + { + PERROR( "Error while reading the EEPROM content (%d)\n", err ); + return EEPROM_ERR_DEVICE; + } + + // Validate the ID + if ( ee.cfg.v1.sysInfo.u16SectionID != EEPROM_SID_SYSINFO ) + { + PERROR( "Uninitialized data section\n" ); + return EEPROM_ERR_UNAVAILABLE; + } + + // Validate the CRC + if ( eeprom_crc( (uint8_t *)&ee.cfg.v1.sysInfo.u32Time, sizeof(ee.cfg.v1.sysInfo) - 2 * sizeof(uint16_t) ) != ee.cfg.v1.sysInfo.u16Crc ) + { + PERROR( "Parity error\n" ); + return EEPROM_ERR_PARITY; + } + + // Expand the content of the section + memcpy( (void *)pSysInfo->szSn, ee.cfg.v1.sysInfo.szSn, sizeof(pSysInfo->szSn) ); + pSysInfo->u8Rev = ee.cfg.v1.sysInfo.u8Rev; + pSysInfo->u8Tcxo = ee.cfg.v1.sysInfo.u2Tcxo; + pSysInfo->u8Ocxo = ee.cfg.v1.sysInfo.u2Ocxo; + pSysInfo->u8GSM850 = ee.cfg.v1.sysInfo.u2GSM850; + pSysInfo->u8GSM900 = ee.cfg.v1.sysInfo.u2GSM900; + pSysInfo->u8DCS1800 = ee.cfg.v1.sysInfo.u2DCS1800; + pSysInfo->u8PCS1900 = ee.cfg.v1.sysInfo.u2PCS1900; + break; + } + + default: + { + PERROR( "Unsupported header version\n" ); + return EEPROM_ERR_UNSUPPORTED; + } + } + return EEPROM_SUCCESS; +} + + +/**************************************************************************** + * Function : eeprom_WriteSysInfo + ************************************************************************//** + * + * This function writes the system information to the EEPROM. + * + * @param [in] pSysInfo + * Pointer to the system info structure to be written. + * + * @return + * 0 if or an error core. + * + ****************************************************************************/ +eeprom_Error_t eeprom_WriteSysInfo( const eeprom_SysInfo_t *pSysInfo ) +{ + int err; + eeprom_Cfg_t ee; + + // Get a copy of the EEPROM header + err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(ee.hdr), (char *) &ee.hdr ); + if ( err != sizeof(ee.hdr) ) + { + PERROR( "Error while reading the EEPROM content (%d)\n", err ); + return EEPROM_ERR_DEVICE; + } + + // Validate the header magic ID + if ( ee.hdr.u32MagicId != EEPROM_CFG_MAGIC_ID ) + { + // Init the header + ee.hdr.u32MagicId = EEPROM_CFG_MAGIC_ID; + ee.hdr.u16Version = EEPROM_HDR_V2; + + // Write it to the EEPROM + err = eeprom_write( EEPROM_CFG_START_ADDR, sizeof(ee.hdr) + sizeof(ee.cfg.v1), (const char *) &ee ); + if ( err != sizeof(ee.hdr) + sizeof(ee.cfg.v1) ) + { + PERROR( "Error while writing to the EEPROM (%d)\n", err ); + return EEPROM_ERR_DEVICE; + } + } + + switch ( ee.hdr.u16Version ) + { + case EEPROM_HDR_V2: + { + ee.cfg.v1.sysInfo.u16SectionID = EEPROM_SID_SYSINFO; + ee.cfg.v1.sysInfo.u16Crc = 0; + ee.cfg.v1.sysInfo.u32Time = time(NULL); + + // Compress the info + memcpy( ee.cfg.v1.sysInfo.szSn, pSysInfo->szSn, sizeof(ee.cfg.v1.sysInfo.szSn) ); + ee.cfg.v1.sysInfo.u8Rev = pSysInfo->u8Rev; + ee.cfg.v1.sysInfo.u2Tcxo = pSysInfo->u8Tcxo; + ee.cfg.v1.sysInfo.u2Ocxo = pSysInfo->u8Ocxo; + ee.cfg.v1.sysInfo.u2GSM850 = pSysInfo->u8GSM850; + ee.cfg.v1.sysInfo.u2GSM900 = pSysInfo->u8GSM900; + ee.cfg.v1.sysInfo.u2DCS1800 = pSysInfo->u8DCS1800; + ee.cfg.v1.sysInfo.u2PCS1900 = pSysInfo->u8PCS1900; + + // Add the CRC + ee.cfg.v1.sysInfo.u16Crc = eeprom_crc( (uint8_t *)&ee.cfg.v1.sysInfo.u32Time, sizeof(ee.cfg.v1.sysInfo) - 2 * sizeof(uint16_t) ); + + // Write it to the EEPROM + err = eeprom_write( EEPROM_CFG_START_ADDR + offsetof(eeprom_Cfg_t, cfg.v1.sysInfo), sizeof(ee.cfg.v1.sysInfo), (const char *) &ee.cfg.v1.sysInfo ); + if ( err != sizeof(ee.cfg.v1.sysInfo) ) + { + PERROR( "Error while writing to the EEPROM (%d)\n", err ); + return EEPROM_ERR_DEVICE; + } + break; + } + + default: + { + PERROR( "Unsupported header version\n" ); + return EEPROM_ERR_UNSUPPORTED; + } + } + return EEPROM_SUCCESS; +} + + +/**************************************************************************** + * Function : eeprom_ReadRfClockCal + ************************************************************************//** + * + * This function reads the RF clock calibration data from the EEPROM. + * + * @param [inout] pRfClockCal + * Pointer to a RF clock calibration structure. + * + * @return + * 0 if or an error core. + * + ****************************************************************************/ +eeprom_Error_t eeprom_ReadRfClockCal( eeprom_RfClockCal_t *pRfClockCal ) +{ + int err; + eeprom_Cfg_t ee; + + // Get a copy of the EEPROM header + err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(ee), (char *) &ee ); + if ( err != sizeof(ee) ) + { + PERROR( "Error while reading the EEPROM content (%d)\n", err ); + return EEPROM_ERR_DEVICE; + } + + // Validate the header magic ID + if ( ee.hdr.u32MagicId != EEPROM_CFG_MAGIC_ID ) + { + PERROR( "Invalid EEPROM format\n" ); + return EEPROM_ERR_INVALID; + } + + switch ( ee.hdr.u16Version ) + { + case EEPROM_HDR_V1: + case EEPROM_HDR_V2: + { + // Get a copy of the EEPROM section + err = eeprom_read( EEPROM_CFG_START_ADDR + offsetof(eeprom_Cfg_t, cfg.v1.rfClk), sizeof(ee.cfg.v1.rfClk), (char *)&ee.cfg.v1.rfClk ); + if ( err != sizeof(ee.cfg.v1.rfClk) ) + { + PERROR( "Error while reading the EEPROM content (%d)\n", err ); + return EEPROM_ERR_DEVICE; + } + + // Validate the ID + if ( ee.cfg.v1.rfClk.u16SectionID != EEPROM_SID_RFCLOCK_CAL ) + { + PERROR( "Uninitialized data section\n" ); + return EEPROM_ERR_UNAVAILABLE; + } + + // Validate the CRC + if ( eeprom_crc( (uint8_t *)&ee.cfg.v1.rfClk.u32Time, sizeof(ee.cfg.v1.rfClk) - 2 * sizeof(uint16_t) ) != ee.cfg.v1.rfClk.u16Crc ) + { + PERROR( "Parity error\n" ); + return EEPROM_ERR_PARITY; + } + + // Expand the content of the section + pRfClockCal->iClkCor = ee.cfg.v1.rfClk.i24ClkCor; + pRfClockCal->u8ClkSrc = ee.cfg.v1.rfClk.u8ClkSrc; + break; + } + + default: + { + PERROR( "Unsupported header version\n" ); + return EEPROM_ERR_UNSUPPORTED; + } + } + return EEPROM_SUCCESS; +} + + +/**************************************************************************** + * Function : eeprom_WriteRfClockCal + ************************************************************************//** + * + * This function writes the RF clock calibration data to the EEPROM. + * + * @param [in] pSysInfo + * Pointer to the system info structure to be written. + * + * @return + * 0 if or an error core. + * + ****************************************************************************/ +eeprom_Error_t eeprom_WriteRfClockCal( const eeprom_RfClockCal_t *pRfClockCal ) +{ + int err; + eeprom_Cfg_t ee; + + // Get a copy of the EEPROM header + err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(ee.hdr), (char *) &ee.hdr ); + if ( err != sizeof(ee.hdr) ) + { + PERROR( "Error while reading the EEPROM content (%d)\n", err ); + return EEPROM_ERR_DEVICE; + } + + // Validate the header magic ID + if ( ee.hdr.u32MagicId != EEPROM_CFG_MAGIC_ID ) + { + // Init the header + ee.hdr.u32MagicId = EEPROM_CFG_MAGIC_ID; + ee.hdr.u16Version = EEPROM_HDR_V2; + + // Write it to the EEPROM + err = eeprom_write( EEPROM_CFG_START_ADDR, sizeof(ee.hdr) + sizeof(ee.cfg.v1), (const char *) &ee ); + if ( err != sizeof(ee.hdr) + sizeof(ee.cfg.v1) ) + { + PERROR( "Error while writing to the EEPROM (%d)\n", err ); + return EEPROM_ERR_DEVICE; + } + } + + switch ( ee.hdr.u16Version ) + { + case EEPROM_HDR_V2: + { + ee.cfg.v1.rfClk.u16SectionID = EEPROM_SID_RFCLOCK_CAL; + ee.cfg.v1.rfClk.u16Crc = 0; + ee.cfg.v1.rfClk.u32Time = time(NULL); + + // Compress the info + ee.cfg.v1.rfClk.i24ClkCor = pRfClockCal->iClkCor; + ee.cfg.v1.rfClk.u8ClkSrc = pRfClockCal->u8ClkSrc; + + // Add the CRC + ee.cfg.v1.rfClk.u16Crc = eeprom_crc( (uint8_t *)&ee.cfg.v1.rfClk.u32Time, sizeof(ee.cfg.v1.rfClk) - 2 * sizeof(uint16_t) ); + + // Write it to the EEPROM + err = eeprom_write( EEPROM_CFG_START_ADDR + offsetof(eeprom_Cfg_t, cfg.v1.rfClk), sizeof(ee.cfg.v1.rfClk), (const char *) &ee.cfg.v1.rfClk ); + if ( err != sizeof(ee.cfg.v1.rfClk) ) + { + PERROR( "Error while writing to the EEPROM (%d)\n", err ); + return EEPROM_ERR_DEVICE; + } + break; + } + + default: + { + PERROR( "Unsupported header version\n" ); + return EEPROM_ERR_UNSUPPORTED; + } + } + return EEPROM_SUCCESS; +} + + +/**************************************************************************** + * Function : eeprom_ReadTxCal + ************************************************************************//** + * + * This function reads the TX calibration tables for the specified band from + * the EEPROM. + * + * @param [in] iBand + * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900). + * + * @param [inout] pTxCal + * Pointer to a TX calibration table structure. + * + * @return + * 0 if or an error core. + * + ****************************************************************************/ +eeprom_Error_t eeprom_ReadTxCal( int iBand, eeprom_TxCal_t *pTxCal ) +{ + int i; + int size; + int nArfcn; + eeprom_Cfg_t *ee = eeprom_cached_config(); + eeprom_SID_t sId; + eeprom_CfgTxCal_t *pCfgTxCal = NULL; + eeprom_CfgTxCalV2_t *pCfgTxCalV2 = NULL; + + // Get a copy of the EEPROM header + if (!ee) + { + PERROR( "Reading cached content failed.\n" ); + return EEPROM_ERR_DEVICE; + } + + switch ( ee->hdr.u16Version ) + { + case EEPROM_HDR_V1: + { + switch ( iBand ) + { + case 0: + nArfcn = 124; + sId = EEPROM_SID_GSM850_TXCAL; + pCfgTxCal = &ee->cfg.v1.gsm850TxCal; + size = sizeof(ee->cfg.v1.gsm850TxCal) + sizeof(ee->cfg.v1.__gsm850TxCalMem); + break; + case 1: + nArfcn = 194; + sId = EEPROM_SID_GSM900_TXCAL; + pCfgTxCal = &ee->cfg.v1.gsm900TxCal; + size = sizeof(ee->cfg.v1.gsm900TxCal) + sizeof(ee->cfg.v1.__gsm900TxCalMem); + break; + case 2: + nArfcn = 374; + sId = EEPROM_SID_DCS1800_TXCAL; + pCfgTxCal = &ee->cfg.v1.dcs1800TxCal; + size = sizeof(ee->cfg.v1.dcs1800TxCal) + sizeof(ee->cfg.v1.__dcs1800TxCalMem); + break; + case 3: + nArfcn = 299; + sId = EEPROM_SID_PCS1900_TXCAL; + pCfgTxCal = &ee->cfg.v1.pcs1900TxCal; + size = sizeof(ee->cfg.v1.pcs1900TxCal) + sizeof(ee->cfg.v1.__pcs1900TxCalMem); + break; + default: + PERROR( "Invalid GSM band specified (%d)\n", iBand ); + return EEPROM_ERR_INVALID; + } + + // Validate the ID + if ( pCfgTxCal->u16SectionID != sId ) + { + PERROR( "Uninitialized data section\n" ); + return EEPROM_ERR_UNAVAILABLE; + } + + // Validate the CRC + if ( eeprom_crc( (uint8_t *)&pCfgTxCal->u32Time, size - 2 * sizeof(uint16_t) ) != pCfgTxCal->u16Crc ) + { + PERROR( "Parity error\n" ); + return EEPROM_ERR_PARITY; + } + + // Expand the content of the section + for ( i = 0; i < 80; i++ ) + { + pTxCal->fTxGainGmsk[i] = (float)pCfgTxCal->sfixTxGainGmsk[i] * 0.03125f; + } + pTxCal->fTx8PskCorr = (float)pCfgTxCal->sfixTx8PskCorr * 0.001953125f; + for ( i = 0; i < 31; i++ ) + { + pTxCal->fTxExtAttCorr[i] = (float)pCfgTxCal->sfixTxExtAttCorr[i] * 0.001953125f; + } + for ( i = 0; i < nArfcn; i++ ) + { + pTxCal->fTxRollOffCorr[i] = (float)pCfgTxCal->sfixTxRollOffCorr[i] * 0.001953125f; + } + + //DSP firmware version + pTxCal->u8DspMajVer = 0; + pTxCal->u8DspMinVer = 0; + + //FPGA firmware version + pTxCal->u8FpgaMajVer = 0; + pTxCal->u8FpgaMinVer = 0; + + break; + } + + case EEPROM_HDR_V2: + { + + switch ( iBand ) + { + case 0: + nArfcn = 124; + sId = EEPROM_SID_GSM850_TXCAL; + pCfgTxCalV2 = &ee->cfg.v2.gsm850TxCalV2; + size = sizeof(ee->cfg.v2.gsm850TxCalV2) + sizeof(ee->cfg.v2.__gsm850TxCalMemV2); + break; + case 1: + nArfcn = 194; + sId = EEPROM_SID_GSM900_TXCAL; + pCfgTxCalV2 = &ee->cfg.v2.gsm900TxCalV2; + size = sizeof(ee->cfg.v2.gsm900TxCalV2) + sizeof(ee->cfg.v2.__gsm900TxCalMemV2); + break; + case 2: + nArfcn = 374; + sId = EEPROM_SID_DCS1800_TXCAL; + pCfgTxCalV2 = &ee->cfg.v2.dcs1800TxCalV2; + size = sizeof(ee->cfg.v2.dcs1800TxCalV2) + sizeof(ee->cfg.v2.__dcs1800TxCalMemV2); + break; + case 3: + nArfcn = 299; + sId = EEPROM_SID_PCS1900_TXCAL; + pCfgTxCalV2 = &ee->cfg.v2.pcs1900TxCalV2; + size = sizeof(ee->cfg.v2.pcs1900TxCalV2) + sizeof(ee->cfg.v2.__pcs1900TxCalMemV2); + break; + default: + PERROR( "Invalid GSM band specified (%d)\n", iBand ); + return EEPROM_ERR_INVALID; + } + + + // Validate the ID + if ( pCfgTxCalV2->u16SectionID != sId ) + { + PERROR( "Uninitialised data section\n" ); + return EEPROM_ERR_UNAVAILABLE; + } + + // Validate the CRC + if ( eeprom_crc( (uint8_t *)&pCfgTxCalV2->u32Time, size - 2 * sizeof(uint16_t) ) != pCfgTxCalV2->u16Crc ) + { + PERROR( "Parity error\n" ); + return EEPROM_ERR_PARITY; + } + + // Expand the content of the section + for ( i = 0; i < 80; i++ ) + { + pTxCal->fTxGainGmsk[i] = (float)pCfgTxCalV2->sfixTxGainGmsk[i] * 0.03125f; + } + pTxCal->fTx8PskCorr = (float)pCfgTxCalV2->sfixTx8PskCorr * 0.001953125f; + for ( i = 0; i < 31; i++ ) + { + pTxCal->fTxExtAttCorr[i] = (float)pCfgTxCalV2->sfixTxExtAttCorr[i] * 0.001953125f; + } + for ( i = 0; i < nArfcn; i++ ) + { + pTxCal->fTxRollOffCorr[i] = (float)pCfgTxCalV2->sfixTxRollOffCorr[i] * 0.001953125f; + } + + //DSP firmware version + pTxCal->u8DspMajVer = pCfgTxCalV2->u8DspMajVer; + pTxCal->u8DspMinVer = pCfgTxCalV2->u8DspMinVer; + + //FPGA firmware version + pTxCal->u8FpgaMajVer = pCfgTxCalV2->u8FpgaMajVer; + pTxCal->u8FpgaMinVer = pCfgTxCalV2->u8FpgaMinVer; + + break; + } + + default: + { + PERROR( "Unsupported header version\n" ); + return EEPROM_ERR_UNSUPPORTED; + } + } + return EEPROM_SUCCESS; +} + + +/**************************************************************************** + * Function : eeprom_WriteTxCal + ************************************************************************//** + * + * This function writes the TX calibration tables for the specified band to + * the EEPROM. + * + * @param [in] iBand + * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900). + * + * @param [in] pTxCal + * Pointer to a TX calibration table structure. + * + * @return + * 0 if or an error core. + * + ****************************************************************************/ +eeprom_Error_t eeprom_WriteTxCal( int iBand, const eeprom_TxCal_t *pTxCal ) +{ + int i; + int err; + int size; + int nArfcn; + eeprom_Cfg_t ee; + eeprom_SID_t sId; + eeprom_CfgTxCalV2_t *pCfgTxCal = NULL; + + // Get a copy of the EEPROM header + err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(ee.hdr), (char *) &ee.hdr ); + if ( err != sizeof(ee.hdr) ) + { + PERROR( "Error while reading the EEPROM content (%d)\n", err ); + return EEPROM_ERR_DEVICE; + } + + // Validate the header magic ID + if ( ee.hdr.u32MagicId != EEPROM_CFG_MAGIC_ID ) + { + // Init the header + ee.hdr.u32MagicId = EEPROM_CFG_MAGIC_ID; + ee.hdr.u16Version = EEPROM_HDR_V2; + + // Write it to the EEPROM + err = eeprom_write( EEPROM_CFG_START_ADDR, sizeof(ee.hdr) + sizeof(ee.cfg.v2), (const char *) &ee ); + if ( err != sizeof(ee.hdr) + sizeof(ee.cfg.v2) ) + { + PERROR( "Error while writing to the EEPROM (%d)\n", err ); + return EEPROM_ERR_DEVICE; + } + } + + switch ( ee.hdr.u16Version ) + { + case EEPROM_HDR_V2: + { + int32_t fixVal; + + switch ( iBand ) + { + case 0: + nArfcn = 124; + sId = EEPROM_SID_GSM850_TXCAL; + pCfgTxCal = &ee.cfg.v2.gsm850TxCalV2; + size = sizeof(ee.cfg.v2.gsm850TxCalV2) + sizeof(ee.cfg.v2.__gsm850TxCalMemV2); + break; + case 1: + nArfcn = 194; + sId = EEPROM_SID_GSM900_TXCAL; + pCfgTxCal = &ee.cfg.v2.gsm900TxCalV2; + size = sizeof(ee.cfg.v2.gsm900TxCalV2) + sizeof(ee.cfg.v2.__gsm900TxCalMemV2); + break; + case 2: + nArfcn = 374; + sId = EEPROM_SID_DCS1800_TXCAL; + pCfgTxCal = &ee.cfg.v2.dcs1800TxCalV2; + size = sizeof(ee.cfg.v2.dcs1800TxCalV2) + sizeof(ee.cfg.v2.__dcs1800TxCalMemV2); + break; + case 3: + nArfcn = 299; + sId = EEPROM_SID_PCS1900_TXCAL; + pCfgTxCal = &ee.cfg.v2.pcs1900TxCalV2; + size = sizeof(ee.cfg.v2.pcs1900TxCalV2) + sizeof(ee.cfg.v2.__pcs1900TxCalMemV2); + break; + default: + PERROR( "Invalid GSM band specified (%d)\n", iBand ); + return EEPROM_ERR_INVALID; + } + + pCfgTxCal->u16SectionID = sId; + pCfgTxCal->u16Crc = 0; + pCfgTxCal->u32Time = time(NULL); + + //DSP firmware version + pCfgTxCal->u8DspMajVer = pTxCal->u8DspMajVer; + pCfgTxCal->u8DspMinVer = pTxCal->u8DspMinVer; + + //FPGA firmware version + pCfgTxCal->u8FpgaMajVer = pTxCal->u8FpgaMajVer; + pCfgTxCal->u8FpgaMinVer = pTxCal->u8FpgaMinVer; + + // Compress the calibration tables + for ( i = 0; i < 80; i++ ) + { + fixVal = (int32_t)(pTxCal->fTxGainGmsk[i] * 32.f + (pTxCal->fTxGainGmsk[i]>0 ? 0.5f:-0.5f)); + if ( fixVal > 32767 ) pCfgTxCal->sfixTxGainGmsk[i] = 32767; + else if ( fixVal < -32768 ) pCfgTxCal->sfixTxGainGmsk[i] = -32768; + else pCfgTxCal->sfixTxGainGmsk[i] = (int16_t)fixVal; + } + fixVal = (int32_t)(pTxCal->fTx8PskCorr * 512.f + (pTxCal->fTx8PskCorr>0 ? 0.5f:-0.5f)); + if ( fixVal > 32767 ) pCfgTxCal->sfixTx8PskCorr = 32767; + else if ( fixVal < -32768 ) pCfgTxCal->sfixTx8PskCorr = -32768; + else pCfgTxCal->sfixTx8PskCorr = (int16_t)fixVal; + for ( i = 0; i < 31; i++ ) + { + fixVal = (int32_t)(pTxCal->fTxExtAttCorr[i] * 512.f + (pTxCal->fTxExtAttCorr[i]>0 ? 0.5f:-0.5f)); + if ( fixVal > 32767 ) pCfgTxCal->sfixTxExtAttCorr[i] = 32767; + else if ( fixVal < -32768 ) pCfgTxCal->sfixTxExtAttCorr[i] = -32768; + else pCfgTxCal->sfixTxExtAttCorr[i] = (int16_t)fixVal; + } + for ( i = 0; i < nArfcn; i++ ) + { + fixVal = (int32_t)(pTxCal->fTxRollOffCorr[i] * 512.f + (pTxCal->fTxRollOffCorr[i]>0 ? 0.5f:-0.5f)); + if ( fixVal > 32767 ) pCfgTxCal->sfixTxRollOffCorr[i] = 32767; + else if ( fixVal < -32768 ) pCfgTxCal->sfixTxRollOffCorr[i] = -32768; + else pCfgTxCal->sfixTxRollOffCorr[i] = (int16_t)fixVal; + } + + // Add the CRC + pCfgTxCal->u16Crc = eeprom_crc( (uint8_t *)&pCfgTxCal->u32Time, size - 2 * sizeof(uint16_t) ); + + // Write it to the EEPROM + err = eeprom_write( EEPROM_CFG_START_ADDR + ((uint8_t*)pCfgTxCal - (uint8_t*)&ee), size, (const char *)pCfgTxCal ); + if ( err != size ) + { + PERROR( "Error while writing to the EEPROM (%d)\n", err ); + return EEPROM_ERR_DEVICE; + } + break; + } + + default: + { + PERROR( "Unsupported header version\n" ); + return EEPROM_ERR_UNSUPPORTED; + } + } + return EEPROM_SUCCESS; +} + + +/**************************************************************************** + * Function : eeprom_ReadRxCal + ************************************************************************//** + * + * This function reads the RX calibration tables for the specified band from + * the EEPROM. + * + * @param [in] iBand + * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900). + * + * @param [in] iUplink + * Uplink flag (0:downlink, X:downlink). + * + * @param [inout] pRxCal + * Pointer to a RX calibration table structure. + * + * @return + * 0 if or an error core. + * + ****************************************************************************/ +eeprom_Error_t eeprom_ReadRxCal( int iBand, int iUplink, eeprom_RxCal_t *pRxCal ) +{ + int i; + int size; + int nArfcn; + eeprom_Cfg_t *ee = eeprom_cached_config(); + eeprom_SID_t sId; + eeprom_CfgRxCal_t *pCfgRxCal = NULL; + eeprom_CfgRxCalV2_t *pCfgRxCalV2 = NULL; + + + if (!ee) + { + PERROR( "Reading cached content failed.\n" ); + return EEPROM_ERR_DEVICE; + } + + switch ( ee->hdr.u16Version ) + { + case EEPROM_HDR_V1: + { + switch ( iBand ) + { + case 0: + nArfcn = 124; + if ( iUplink ) + { + sId = EEPROM_SID_GSM850_RXUCAL; + pCfgRxCal = &ee->cfg.v1.gsm850RxuCal; + size = sizeof(ee->cfg.v1.gsm850RxuCal) + sizeof(ee->cfg.v1.__gsm850RxuCalMem); + } + else + { + sId = EEPROM_SID_GSM850_RXDCAL; + pCfgRxCal = &ee->cfg.v1.gsm850RxdCal; + size = sizeof(ee->cfg.v1.gsm850RxdCal) + sizeof(ee->cfg.v1.__gsm850RxdCalMem); + } + break; + case 1: + nArfcn = 194; + if ( iUplink ) + { + sId = EEPROM_SID_GSM900_RXUCAL; + pCfgRxCal = &ee->cfg.v1.gsm900RxuCal; + size = sizeof(ee->cfg.v1.gsm900RxuCal) + sizeof(ee->cfg.v1.__gsm900RxuCalMem); + } + else + { + sId = EEPROM_SID_GSM900_RXDCAL; + pCfgRxCal = &ee->cfg.v1.gsm900RxdCal; + size = sizeof(ee->cfg.v1.gsm900RxdCal) + sizeof(ee->cfg.v1.__gsm900RxdCalMem); + } + break; + case 2: + nArfcn = 374; + if ( iUplink ) + { + sId = EEPROM_SID_DCS1800_RXUCAL; + pCfgRxCal = &ee->cfg.v1.dcs1800RxuCal; + size = sizeof(ee->cfg.v1.dcs1800RxuCal) + sizeof(ee->cfg.v1.__dcs1800RxuCalMem); + } + else + { + sId = EEPROM_SID_DCS1800_RXDCAL; + pCfgRxCal = &ee->cfg.v1.dcs1800RxdCal; + size = sizeof(ee->cfg.v1.dcs1800RxdCal) + sizeof(ee->cfg.v1.__dcs1800RxdCalMem); + } + break; + case 3: + nArfcn = 299; + if ( iUplink ) + { + sId = EEPROM_SID_PCS1900_RXUCAL; + pCfgRxCal = &ee->cfg.v1.pcs1900RxuCal; + size = sizeof(ee->cfg.v1.pcs1900RxuCal) + sizeof(ee->cfg.v1.__pcs1900RxuCalMem); + } + else + { + sId = EEPROM_SID_PCS1900_RXDCAL; + pCfgRxCal = &ee->cfg.v1.pcs1900RxdCal; + size = sizeof(ee->cfg.v1.pcs1900RxdCal) + sizeof(ee->cfg.v1.__pcs1900RxdCalMem); + } + break; + default: + PERROR( "Invalid GSM band specified (%d)\n", iBand ); + return EEPROM_ERR_INVALID; + } + + // Validate the ID + if ( pCfgRxCal->u16SectionID != sId ) + { + PERROR( "Uninitialized data section\n" ); + return EEPROM_ERR_UNAVAILABLE; + } + + // Validate the CRC + if ( eeprom_crc( (uint8_t *)&pCfgRxCal->u32Time, size - 2 * sizeof(uint16_t) ) != pCfgRxCal->u16Crc ) + { + PERROR( "Parity error\n" ); + return EEPROM_ERR_PARITY; + } + + // Expand the IQ imbalance mode (0:off, 1:on, 2:auto) + pRxCal->u8IqImbalMode = pCfgRxCal->u16IqImbalMode; + + // Expand the IQ imbalance compensation + for ( i = 0; i < 4; i++ ) + { + pRxCal->u16IqImbalCorr[i] = pCfgRxCal->u16IqImbalCorr[i]; + } + + // Expand the External RX gain + pRxCal->fExtRxGain = (float)pCfgRxCal->sfixExtRxGain * 0.001953125f; + + // Expand the Mixer gain error compensation + pRxCal->fRxMixGainCorr = (float)pCfgRxCal->sfixRxMixGainCorr * 0.001953125f; + + // Expand the LNA gain error compensation (1:@-12 dB, 2:@-24 dB, 3:@-36 dB) + for ( i = 0; i < 3; i++ ) + { + pRxCal->fRxLnaGainCorr[i] = (float)pCfgRxCal->sfixRxLnaGainCorr[i] * 0.001953125f; + } + + // Expand the Frequency roll-off compensation + for ( i = 0; i < nArfcn; i++ ) + { + pRxCal->fRxRollOffCorr[i] = (float)pCfgRxCal->sfixRxRollOffCorr[i] * 0.001953125f; + } + + //DSP firmware version + pRxCal->u8DspMajVer = 0; + pRxCal->u8DspMinVer = 0; + + //FPGA firmware version + pRxCal->u8FpgaMajVer = 0; + pRxCal->u8FpgaMinVer = 0; + + break; + } + + case EEPROM_HDR_V2: + { + switch ( iBand ) + { + case 0: + nArfcn = 124; + if ( iUplink ) + { + sId = EEPROM_SID_GSM850_RXUCAL; + pCfgRxCalV2 = &ee->cfg.v2.gsm850RxuCalV2; + size = sizeof(ee->cfg.v2.gsm850RxuCalV2) + sizeof(ee->cfg.v2.__gsm850RxuCalMemV2); + } + else + { + sId = EEPROM_SID_GSM850_RXDCAL; + pCfgRxCalV2 = &ee->cfg.v2.gsm850RxdCalV2; + size = sizeof(ee->cfg.v2.gsm850RxdCalV2) + sizeof(ee->cfg.v2.__gsm850RxdCalMemV2); + } + break; + case 1: + nArfcn = 194; + if ( iUplink ) + { + sId = EEPROM_SID_GSM900_RXUCAL; + pCfgRxCalV2 = &ee->cfg.v2.gsm900RxuCalV2; + size = sizeof(ee->cfg.v2.gsm900RxuCalV2) + sizeof(ee->cfg.v2.__gsm900RxuCalMemV2); + } + else + { + sId = EEPROM_SID_GSM900_RXDCAL; + pCfgRxCalV2 = &ee->cfg.v2.gsm900RxdCalV2; + size = sizeof(ee->cfg.v2.gsm900RxdCalV2) + sizeof(ee->cfg.v2.__gsm900RxdCalMemV2); + } + break; + case 2: + nArfcn = 374; + if ( iUplink ) + { + sId = EEPROM_SID_DCS1800_RXUCAL; + pCfgRxCalV2 = &ee->cfg.v2.dcs1800RxuCalV2; + size = sizeof(ee->cfg.v2.dcs1800RxuCalV2) + sizeof(ee->cfg.v2.__dcs1800RxuCalMemV2); + } + else + { + sId = EEPROM_SID_DCS1800_RXDCAL; + pCfgRxCalV2 = &ee->cfg.v2.dcs1800RxdCalV2; + size = sizeof(ee->cfg.v2.dcs1800RxdCalV2) + sizeof(ee->cfg.v2.__dcs1800RxdCalMemV2); + } + break; + case 3: + nArfcn = 299; + if ( iUplink ) + { + sId = EEPROM_SID_PCS1900_RXUCAL; + pCfgRxCalV2 = &ee->cfg.v2.pcs1900RxuCalV2; + size = sizeof(ee->cfg.v2.pcs1900RxuCalV2) + sizeof(ee->cfg.v2.__pcs1900RxuCalMemV2); + } + else + { + sId = EEPROM_SID_PCS1900_RXDCAL; + pCfgRxCalV2 = &ee->cfg.v2.pcs1900RxdCalV2; + size = sizeof(ee->cfg.v2.pcs1900RxdCalV2) + sizeof(ee->cfg.v2.__pcs1900RxdCalMemV2); + } + break; + + default: + PERROR( "Invalid GSM band specified (%d)\n", iBand ); + return EEPROM_ERR_INVALID; + } + + // Validate the ID + if ( pCfgRxCalV2->u16SectionID != sId ) + { + PERROR( "Uninitialized data section\n" ); + return EEPROM_ERR_UNAVAILABLE; + } + + // Validate the CRC + if ( eeprom_crc( (uint8_t *)&pCfgRxCalV2->u32Time, size - 2 * sizeof(uint16_t) ) != pCfgRxCalV2->u16Crc ) + { + PERROR( "Parity error - Band %d\n", iBand ); + return EEPROM_ERR_PARITY; + } + + // Expand the IQ imbalance mode (0:off, 1:on, 2:auto) + pRxCal->u8IqImbalMode = pCfgRxCalV2->u16IqImbalMode; + + // Expand the IQ imbalance compensation + for ( i = 0; i < 4; i++ ) + { + pRxCal->u16IqImbalCorr[i] = pCfgRxCalV2->u16IqImbalCorr[i]; + } + + // Expand the External RX gain + pRxCal->fExtRxGain = (float)pCfgRxCalV2->sfixExtRxGain * 0.001953125; + + // Expand the Mixer gain error compensation + pRxCal->fRxMixGainCorr = (float)pCfgRxCalV2->sfixRxMixGainCorr * 0.001953125; + + // Expand the LNA gain error compensation (1:@-12 dB, 2:@-24 dB, 3:@-36 dB) + for ( i = 0; i < 3; i++ ) + { + pRxCal->fRxLnaGainCorr[i] = (float)pCfgRxCalV2->sfixRxLnaGainCorr[i] * 0.001953125; + } + + // Expand the Frequency roll-off compensation + for ( i = 0; i < nArfcn; i++ ) + { + pRxCal->fRxRollOffCorr[i] = (float)pCfgRxCalV2->sfixRxRollOffCorr[i] * 0.001953125; + } + + //DSP firmware version + pRxCal->u8DspMajVer = pCfgRxCalV2->u8DspMajVer; + pRxCal->u8DspMinVer = pCfgRxCalV2->u8DspMinVer; + + //FPGA firmware version + pRxCal->u8FpgaMajVer = pCfgRxCalV2->u8FpgaMajVer; + pRxCal->u8FpgaMinVer = pCfgRxCalV2->u8FpgaMinVer; + + break; + } + + default: + { + PERROR( "Unsupported header version\n" ); + return EEPROM_ERR_UNSUPPORTED; + } + } + return EEPROM_SUCCESS; +} + + +/**************************************************************************** + * Function : eeprom_WriteRxCal + ************************************************************************//** + * + * This function writes the RX calibration tables for the specified band to + * the EEPROM. + * + * @param [in] iBand + * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900). + * + * @param [in] iUplink + * Uplink flag (0:downlink, X:downlink). + * + * @param [in] pRxCal + * Pointer to a RX calibration table structure. + * + * @return + * 0 if or an error core. + * + ****************************************************************************/ +eeprom_Error_t eeprom_WriteRxCal( int iBand, int iUplink, const eeprom_RxCal_t *pRxCal ) +{ + int i; + int err; + int size; + int nArfcn; + eeprom_Cfg_t ee; + eeprom_SID_t sId; + eeprom_CfgRxCalV2_t *pCfgRxCal = NULL; + + // Get a copy of the EEPROM header + err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(ee.hdr), (char *) &ee.hdr ); + if ( err != sizeof(ee.hdr) ) + { + PERROR( "Error while reading the EEPROM content (%d)\n", err ); + return EEPROM_ERR_DEVICE; + } + + // Validate the header magic ID + if ( ee.hdr.u32MagicId != EEPROM_CFG_MAGIC_ID ) + { + // Init the header + ee.hdr.u32MagicId = EEPROM_CFG_MAGIC_ID; + ee.hdr.u16Version = EEPROM_HDR_V2; + + // Write it to the EEPROM + err = eeprom_write( EEPROM_CFG_START_ADDR, sizeof(ee.hdr) + sizeof(ee.cfg.v2), (const char *) &ee ); + if ( err != sizeof(ee.hdr) + sizeof(ee.cfg.v2) ) + { + PERROR( "Error while writing to the EEPROM (%d)\n", err ); + return EEPROM_ERR_DEVICE; + } + } + + switch ( ee.hdr.u16Version ) + { + case EEPROM_HDR_V2: + { + int32_t fixVal; + + switch ( iBand ) + { + case 0: + nArfcn = 124; + if ( iUplink ) + { + sId = EEPROM_SID_GSM850_RXUCAL; + pCfgRxCal = &ee.cfg.v2.gsm850RxuCalV2; + size = sizeof(ee.cfg.v2.gsm850RxuCalV2) + sizeof(ee.cfg.v2.__gsm850RxuCalMemV2); + } + else + { + sId = EEPROM_SID_GSM850_RXDCAL; + pCfgRxCal = &ee.cfg.v2.gsm850RxdCalV2; + size = sizeof(ee.cfg.v2.gsm850RxdCalV2) + sizeof(ee.cfg.v2.__gsm850RxdCalMemV2); + } + break; + case 1: + nArfcn = 194; + if ( iUplink ) + { + sId = EEPROM_SID_GSM900_RXUCAL; + pCfgRxCal = &ee.cfg.v2.gsm900RxuCalV2; + size = sizeof(ee.cfg.v2.gsm900RxuCalV2) + sizeof(ee.cfg.v2.__gsm900RxuCalMemV2); + } + else + { + sId = EEPROM_SID_GSM900_RXDCAL; + pCfgRxCal = &ee.cfg.v2.gsm900RxdCalV2; + size = sizeof(ee.cfg.v2.gsm900RxdCalV2) + sizeof(ee.cfg.v2.__gsm900RxdCalMemV2); + } + break; + case 2: + nArfcn = 374; + if ( iUplink ) + { + sId = EEPROM_SID_DCS1800_RXUCAL; + pCfgRxCal = &ee.cfg.v2.dcs1800RxuCalV2; + size = sizeof(ee.cfg.v2.dcs1800RxuCalV2) + sizeof(ee.cfg.v2.__dcs1800RxuCalMemV2); + } + else + { + sId = EEPROM_SID_DCS1800_RXDCAL; + pCfgRxCal = &ee.cfg.v2.dcs1800RxdCalV2; + size = sizeof(ee.cfg.v2.dcs1800RxdCalV2) + sizeof(ee.cfg.v2.__dcs1800RxdCalMemV2); + } + break; + case 3: + nArfcn = 299; + if ( iUplink ) + { + sId = EEPROM_SID_PCS1900_RXUCAL; + pCfgRxCal = &ee.cfg.v2.pcs1900RxuCalV2; + size = sizeof(ee.cfg.v2.pcs1900RxuCalV2) + sizeof(ee.cfg.v2.__pcs1900RxuCalMemV2); + } + else + { + sId = EEPROM_SID_PCS1900_RXDCAL; + pCfgRxCal = &ee.cfg.v2.pcs1900RxdCalV2; + size = sizeof(ee.cfg.v2.pcs1900RxdCalV2) + sizeof(ee.cfg.v2.__pcs1900RxdCalMemV2); + } + break; + default: + PERROR( "Invalid GSM band specified (%d)\n", iBand ); + return EEPROM_ERR_INVALID; + } + + pCfgRxCal->u16SectionID = sId; + pCfgRxCal->u16Crc = 0; + pCfgRxCal->u32Time = time(NULL); + + //DSP firmware version + pCfgRxCal->u8DspMajVer = pRxCal->u8DspMajVer; + pCfgRxCal->u8DspMinVer = pRxCal->u8DspMinVer; + + //FPGA firmware version + pCfgRxCal->u8FpgaMajVer = pRxCal->u8FpgaMajVer; + pCfgRxCal->u8FpgaMinVer = pRxCal->u8FpgaMinVer; + + // Compress the IQ imbalance mode (0:off, 1:on, 2:auto) + pCfgRxCal->u16IqImbalMode = pRxCal->u8IqImbalMode; + + // Compress the IQ imbalance compensation + for ( i = 0; i < 4; i++ ) + { + pCfgRxCal->u16IqImbalCorr[i] = pRxCal->u16IqImbalCorr[i]; + } + + // Compress the External RX gain + fixVal = (int32_t)(pRxCal->fExtRxGain * 512.f + (pRxCal->fExtRxGain>0 ? 0.5f:-0.5f)); + if ( fixVal > 32767 ) pCfgRxCal->sfixExtRxGain = 32767; + else if ( fixVal < -32768 ) pCfgRxCal->sfixExtRxGain = -32768; + else pCfgRxCal->sfixExtRxGain = (int16_t)fixVal; + + // Compress the Mixer gain error compensation + fixVal = (int32_t)(pRxCal->fRxMixGainCorr * 512.f + (pRxCal->fRxMixGainCorr>0 ? 0.5f:-0.5f)); + if ( fixVal > 32767 ) pCfgRxCal->sfixRxMixGainCorr = 32767; + else if ( fixVal < -32768 ) pCfgRxCal->sfixRxMixGainCorr = -32768; + else pCfgRxCal->sfixRxMixGainCorr = (int16_t)fixVal; + + // Compress the LNA gain error compensation (1:@-12 dB, 2:@-24 dB, 3:@-36 dB) + for ( i = 0; i < 3; i++ ) + { + fixVal = (int32_t)(pRxCal->fRxLnaGainCorr[i] * 512.f + (pRxCal->fRxLnaGainCorr[i]>0 ? 0.5f:-0.5f)); + if ( fixVal > 32767 ) pCfgRxCal->sfixRxLnaGainCorr[i] = 32767; + else if ( fixVal < -32768 ) pCfgRxCal->sfixRxLnaGainCorr[i] = -32768; + else pCfgRxCal->sfixRxLnaGainCorr[i] = (int16_t)fixVal; + } + + // Compress the Frequency roll-off compensation + for ( i = 0; i < nArfcn; i++ ) + { + fixVal = (int32_t)(pRxCal->fRxRollOffCorr[i] * 512.f + (pRxCal->fRxRollOffCorr[i]>0 ? 0.5f:-0.5f)); + if ( fixVal > 32767 ) pCfgRxCal->sfixRxRollOffCorr[i] = 32767; + else if ( fixVal < -32768 ) pCfgRxCal->sfixRxRollOffCorr[i] = -32768; + else pCfgRxCal->sfixRxRollOffCorr[i] = (int16_t)fixVal; + } + + // Add the CRC + pCfgRxCal->u16Crc = eeprom_crc( (uint8_t *)&pCfgRxCal->u32Time, size - 2 * sizeof(uint16_t) ); + + // Write it to the EEPROM + err = eeprom_write( EEPROM_CFG_START_ADDR + ((uint8_t*)pCfgRxCal - (uint8_t*)&ee), size, (const char *)pCfgRxCal ); + if ( err != size ) + { + PERROR( "Error while writing to the EEPROM (%d)\n", err ); + return EEPROM_ERR_DEVICE; + } + break; + } + + default: + { + PERROR( "Unsupported header version\n" ); + return EEPROM_ERR_UNSUPPORTED; + } + } + return EEPROM_SUCCESS; +} + + +/**************************************************************************** + * Private functions * + ****************************************************************************/ + +/** + * Dump the content of the EEPROM to the standard output + */ +int eeprom_dump( int addr, int size, int hex ) +{ + FILE *f; + char ch; + int i; + + f = fopen( EEPROM_DEV, "r+" ); + if ( f == NULL ) + { + perror( "eeprom fopen" ); + return -1; + } + if (fseek( f, addr, SEEK_SET ) != 0) + { + perror( "eeprom fseek" ); + fclose( f ); + return -1; + } + + for ( i = 0; i < size; ++i, ++addr ) + { + if ( fread( &ch, 1, 1, f ) != 1 ) + { + perror( "eeprom fread" ); + fclose( f ); + return -1; + } + if ( hex ) + { + if ( (i % 16) == 0 ) + { + printf( "\n %.4x| ", addr ); + } + else if ( (i % 8) == 0 ) + { + printf( " " ); + } + printf( "%.2x ", ch ); + } + else + putchar( ch ); + } + if ( hex ) + { + printf( "\n\n" ); + } + fflush( stdout ); + + fclose( f ); + return 0; +} + +static FILE *g_file; +static eeprom_Cfg_t *g_cached_cfg; + +void eeprom_free_resources(void) +{ + if (g_file) + fclose(g_file); + g_file = NULL; + + /* release the header */ + free(g_cached_cfg); + g_cached_cfg = NULL; +} + +/** + * Read up to 'size' bytes of data from the EEPROM starting at offset 'addr'. + */ +static int eeprom_read( int addr, int size, char *pBuff ) +{ + FILE *f = g_file; + int n; + + if (!f) { + f = fopen( EEPROM_DEV, "r+" ); + if ( f == NULL ) + { + perror( "eeprom fopen" ); + return -1; + } + g_file = f; + } + if (fseek( f, addr, SEEK_SET ) != 0) + { + perror( "eeprom fseek" ); + return -1; + } + + n = fread( pBuff, 1, size, f ); + return n; +} + +static void eeprom_cache_cfg(void) +{ + int err; + + free(g_cached_cfg); + g_cached_cfg = malloc(sizeof(*g_cached_cfg)); + + if (!g_cached_cfg) + return; + + err = eeprom_read( EEPROM_CFG_START_ADDR, sizeof(*g_cached_cfg), (char *) g_cached_cfg ); + if ( err != sizeof(*g_cached_cfg) ) + { + PERROR( "Error while reading the EEPROM content (%d)\n", err ); + goto error; + } + + if ( g_cached_cfg->hdr.u32MagicId != EEPROM_CFG_MAGIC_ID ) + { + PERROR( "Invalid EEPROM format\n" ); + goto error; + } + + return; + +error: + free(g_cached_cfg); + g_cached_cfg = NULL; +} + +static eeprom_Cfg_t *eeprom_cached_config(void) +{ + if (!g_cached_cfg) + eeprom_cache_cfg(); + return g_cached_cfg; +} + +/** + * Write up to 'size' bytes of data to the EEPROM starting at offset 'addr'. + */ +static int eeprom_write( int addr, int size, const char *pBuff ) +{ + FILE *f = g_file; + int n; + + if (!f) { + f = fopen( EEPROM_DEV, "r+" ); + if ( f == NULL ) + { + perror( "eeprom fopen" ); + return -1; + } + g_file = f; + } + if (fseek( f, addr, SEEK_SET ) != 0) + { + perror( "eeprom fseek" ); + n = -1; + goto error; + } + + n = fwrite( pBuff, 1, size, f ); + +error: + fclose( f ); + g_file = NULL; + return n; +} + + +/** + * EEPROM CRC. + */ +static uint16_t eeprom_crc( uint8_t *pu8Data, int len ) +{ + int i; + uint16_t crc = 0xFFFF; + + while (len--) { + crc ^= (uint16_t)*pu8Data++; + + for (i=0; i<8; i++) { + if (crc & 1) crc = (crc >> 1) ^ 0x8408; + else crc = (crc >> 1); + } + } + + crc = ~crc; + return crc; +} diff --git a/src/osmo-bts-sysmo/eeprom.h b/src/osmo-bts-sysmo/eeprom.h new file mode 100644 index 0000000..f75e54f --- /dev/null +++ b/src/osmo-bts-sysmo/eeprom.h @@ -0,0 +1,304 @@ +/*************************************************************************** + * + * **** I + * ****** *** + * ******* **** + * ******** **** **** **** ********* ******* **** *********** + * ********* **** **** **** ********* ************** ************* + * **** ***** **** **** **** **** ***** ****** ***** **** + * **** ***** **** **** **** **** ***** **** **** **** + * **** ********* **** **** **** **** **** **** **** + * **** ******** **** ****I **** ***** ***** **** **** + * **** ****** ***** ****** ***** ****** ******* ****** ******* + * **** **** ************ ****** ************* ************* + * **** *** **** **** **** ***** **** ***** **** + * **** + * I N N O V A T I O N T O D A Y F O R T O M M O R O W **** + * *** + * + *************************************************************************** + * + * Project : SuperFemto + * File : eeprom.h + * Description : EEPROM interface. + * + * Copyright (c) Nutaq. 2012 + * + *************************************************************************** + * + * "$Revision: 1.1 $" + * "$Name: $" + * "$Date: 2012/06/20 02:18:30 $" + * "$Author: Yves.Godin $" + * + ***************************************************************************/ +#ifndef EEPROM_H__ +#define EEPROM_H__ + +#include + +/**************************************************************************** + * Public constants * + ****************************************************************************/ + +/** + * EEPROM error code + */ +typedef enum +{ + EEPROM_SUCCESS = 0, ///< Success + EEPROM_ERR_DEVICE = -1, ///< Device access error + EEPROM_ERR_PARITY = -2, ///< Parity error + EEPROM_ERR_UNAVAILABLE = -3, ///< Information unavailable + EEPROM_ERR_INVALID = -4, ///< Invalid format + EEPROM_ERR_UNSUPPORTED = -5, ///< Unsupported format +} eeprom_Error_t; + + +/**************************************************************************** + * Struct : eeprom_SysInfo_t + ************************************************************************//** + * + * SuperFemto system information. + * + ***************************************************************************/ +typedef struct eeprom_SysInfo +{ + char szSn[16]; ///< Serial number + uint8_t u8Rev; ///< Board revision + uint8_t u8Tcxo; ///< TCXO present (0:absent, 1:present, X:unknown) + uint8_t u8Ocxo; ///< OCXO present (0:absent, 1:present, X:unknown) + uint8_t u8GSM850; ///< GSM-850 supported (0:unsupported, 1:supported, X:unknown) + uint8_t u8GSM900; ///< GSM-900 supported (0:unsupported, 1:supported, X:unknown) + uint8_t u8DCS1800; ///< GSM-1800 supported (0:unsupported, 1:supported, X:unknown) + uint8_t u8PCS1900; ///< GSM-1900 supported (0:unsupported, 1:supported, X:unknown) +} eeprom_SysInfo_t; + +/**************************************************************************** + * Struct : eeprom_RfClockCal_t + ************************************************************************//** + * + * SuperFemto RF clock calibration. + * + ***************************************************************************/ +typedef struct eeprom_RfClockCal +{ + int iClkCor; ///< Clock correction value in PPB. + uint8_t u8ClkSrc; ///< Clock source (0:None, 1:OCXO, 2:TCXO, 3:External, 4:GPS PPS, 5:reserved, 6:RX, 7:Edge) +} eeprom_RfClockCal_t; + +/**************************************************************************** + * Struct : eeprom_TxCal_t + ************************************************************************//** + * + * SuperFemto transmit calibration table. + * + ***************************************************************************/ +typedef struct eeprom_TxCal +{ + uint8_t u8DspMajVer; ///< DSP firmware major version + uint8_t u8DspMinVer; ///< DSP firmware minor version + uint8_t u8FpgaMajVer; ///< FPGA firmware major version + uint8_t u8FpgaMinVer; ///< FPGA firmware minor version + float fTxGainGmsk[80]; ///< Gain setting for GMSK output level from +50dBm to -29 dBm + float fTx8PskCorr; ///< Gain adjustment for 8 PSK (default to +3.25 dB) + float fTxExtAttCorr[31]; ///< Gain adjustment for external attenuator (0:@1dB, 1:@2dB, ..., 31:@32dB) + float fTxRollOffCorr[374]; /**< Gain correction for each ARFCN + for GSM-850 : 0=128, 1:129, ..., 123:251, [124-373]:unused + for GSM-900 : 0=955, 1:956, ..., 70:1, ..., 317:956, [318-373]:unused + for DCS-1800: 0=512, 1:513, ..., 373:885 + for PCS-1900: 0=512, 1:513, ..., 298:810, [299-373]:unused */ +} eeprom_TxCal_t; + +/**************************************************************************** + * Struct : eeprom_RxCal_t + ************************************************************************//** + * + * SuperFemto receive calibration table. + * + ***************************************************************************/ +typedef struct eeprom_RxCal +{ + uint8_t u8DspMajVer; ///< DSP firmware major version + uint8_t u8DspMinVer; ///< DSP firmware minor version + uint8_t u8FpgaMajVer; ///< FPGA firmware major version + uint8_t u8FpgaMinVer; ///< FPGA firmware minor version + float fExtRxGain; ///< External RX gain + float fRxMixGainCorr; ///< Mixer gain error compensation + float fRxLnaGainCorr[3]; ///< LNA gain error compensation (1:@-12 dB, 2:@-24 dB, 3:@-36 dB) + float fRxRollOffCorr[374]; /***< Frequency roll-off compensation + for GSM-850 : 0=128, 1:129, ..., 123:251, [124-373]:unused + for GSM-900 : 0=955, 1:956, ..., 70:1, ..., 317:956, [318-373]:unused + for DCS-1800: 0=512, 1:513, ..., 373:885 + for PCS-1900: 0=512, 1:513, ..., 298:810, [299-373]:unused */ + uint8_t u8IqImbalMode; ///< IQ imbalance mode (0:off, 1:on, 2:auto) + uint16_t u16IqImbalCorr[4]; ///< IQ imbalance compensation +} eeprom_RxCal_t; + + +/**************************************************************************** + * Public functions * + ****************************************************************************/ + +eeprom_Error_t eeprom_ReadEthAddr( uint8_t *ethaddr ); + +/**************************************************************************** + * Function : eeprom_ResetCfg + ************************************************************************//** + * + * This function reset the content of the EEPROM config area. + * + * @return + * 0 if or an error core. + * + ****************************************************************************/ +eeprom_Error_t eeprom_ResetCfg( void ); + +/**************************************************************************** + * Function : eeprom_ReadSysInfo + ************************************************************************//** + * + * This function reads the system information from the EEPROM. + * + * @param [inout] pTime + * Pointer to a system info structure. + * + * @param [inout] pSysInfo + * Pointer to a system info structure. + * + * @return + * 0 if or an error core. + * + ****************************************************************************/ +eeprom_Error_t eeprom_ReadSysInfo( eeprom_SysInfo_t *pSysInfo ); + +/**************************************************************************** + * Function : eeprom_WriteSysInfo + ************************************************************************//** + * + * This function writes the system information to the EEPROM. + * + * @param [in] pSysInfo + * Pointer to the system info structure to be written. + * + * @return + * 0 if or an error core. + * + ****************************************************************************/ +eeprom_Error_t eeprom_WriteSysInfo( const eeprom_SysInfo_t *pSysInfo ); + +/**************************************************************************** + * Function : eeprom_ReadRfClockCal + ************************************************************************//** + * + * This function reads the RF clock calibration data from the EEPROM. + * + * @param [inout] pRfClockCal + * Pointer to a RF clock calibration structure. + * + * @return + * 0 if or an error core. + * + ****************************************************************************/ +eeprom_Error_t eeprom_ReadRfClockCal( eeprom_RfClockCal_t *pRfClockCal ); + +/**************************************************************************** + * Function : eeprom_WriteRfClockCal + ************************************************************************//** + * + * This function writes the RF clock calibration data to the EEPROM. + * + * @param [in] pSysInfo + * Pointer to the system info structure to be written. + * + * @return + * 0 if or an error core. + * + ****************************************************************************/ +eeprom_Error_t eeprom_WriteRfClockCal( const eeprom_RfClockCal_t *pRfClockCal ); + +/**************************************************************************** + * Function : eeprom_ReadTxCal + ************************************************************************//** + * + * This function reads the TX calibration tables for the specified band from + * the EEPROM. + * + * @param [in] iBand + * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900). + * + * @param [inout] pTxCal + * Pointer to a TX calibration table structure. + * + * @return + * 0 if or an error core. + * + ****************************************************************************/ +eeprom_Error_t eeprom_ReadTxCal( int iBand, eeprom_TxCal_t *pTxCal ); + +/**************************************************************************** + * Function : eeprom_WriteTxCal + ************************************************************************//** + * + * This function writes the TX calibration tables for the specified band to + * the EEPROM. + * + * @param [in] iBand + * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900). + * + * @param [in] pTxCal + * Pointer to a TX calibration table structure. + * + * @return + * 0 if or an error core. + * + ****************************************************************************/ +eeprom_Error_t eeprom_WriteTxCal( int iBand, const eeprom_TxCal_t *pTxCal ); + +/**************************************************************************** + * Function : eeprom_ReadRxCal + ************************************************************************//** + * + * This function reads the RX calibration tables for the specified band from + * the EEPROM. + * + * @param [in] iBand + * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900). + * + * @param [in] iUplink + * Uplink flag (0:downlink, X:downlink). + * + * @param [inout] pRxCal + * Pointer to a RX calibration table structure. + * + * @return + * 0 if or an error core. + * + ****************************************************************************/ +eeprom_Error_t eeprom_ReadRxCal( int iBand, int iUplink, eeprom_RxCal_t *pRxCal ); + +/**************************************************************************** + * Function : eeprom_WriteRxCal + ************************************************************************//** + * + * This function writes the RX calibration tables for the specified band to + * the EEPROM. + * + * @param [in] iBand + * GSM band (0:GSM-850, 1:GSM-900, 2:DCS-1800, 3:PCS-1900). + * + * @param [in] iUplink + * Uplink flag (0:downlink, X:downlink). + * + * @param [in] pRxCal + * Pointer to a RX calibration table structure. + * + * @return + * 0 if or an error core. + * + ****************************************************************************/ +eeprom_Error_t eeprom_WriteRxCal( int iBand, int iUplink, const eeprom_RxCal_t *pRxCal ); + +void eeprom_free_resources(void); + +#endif // EEPROM_H__ diff --git a/src/osmo-bts-sysmo/femtobts.c b/src/osmo-bts-sysmo/femtobts.c new file mode 100644 index 0000000..480fe06 --- /dev/null +++ b/src/osmo-bts-sysmo/femtobts.c @@ -0,0 +1,370 @@ +/* sysmocom femtobts L1 API related definitions */ + +/* (C) 2011 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include "femtobts.h" + +const enum l1prim_type femtobts_l1prim_type[GsmL1_PrimId_NUM] = { + [GsmL1_PrimId_MphInitReq] = L1P_T_REQ, + [GsmL1_PrimId_MphCloseReq] = L1P_T_REQ, + [GsmL1_PrimId_MphConnectReq] = L1P_T_REQ, + [GsmL1_PrimId_MphDisconnectReq] = L1P_T_REQ, + [GsmL1_PrimId_MphActivateReq] = L1P_T_REQ, + [GsmL1_PrimId_MphDeactivateReq] = L1P_T_REQ, + [GsmL1_PrimId_MphConfigReq] = L1P_T_REQ, + [GsmL1_PrimId_MphMeasureReq] = L1P_T_REQ, + [GsmL1_PrimId_MphInitCnf] = L1P_T_CONF, + [GsmL1_PrimId_MphCloseCnf] = L1P_T_CONF, + [GsmL1_PrimId_MphConnectCnf] = L1P_T_CONF, + [GsmL1_PrimId_MphDisconnectCnf] = L1P_T_CONF, + [GsmL1_PrimId_MphActivateCnf] = L1P_T_CONF, + [GsmL1_PrimId_MphDeactivateCnf] = L1P_T_CONF, + [GsmL1_PrimId_MphConfigCnf] = L1P_T_CONF, + [GsmL1_PrimId_MphMeasureCnf] = L1P_T_CONF, + [GsmL1_PrimId_MphTimeInd] = L1P_T_IND, + [GsmL1_PrimId_MphSyncInd] = L1P_T_IND, + [GsmL1_PrimId_PhEmptyFrameReq] = L1P_T_REQ, + [GsmL1_PrimId_PhDataReq] = L1P_T_REQ, + [GsmL1_PrimId_PhConnectInd] = L1P_T_IND, + [GsmL1_PrimId_PhReadyToSendInd] = L1P_T_IND, + [GsmL1_PrimId_PhDataInd] = L1P_T_IND, + [GsmL1_PrimId_PhRaInd] = L1P_T_IND, +}; + +const struct value_string femtobts_l1prim_names[GsmL1_PrimId_NUM+1] = { + { GsmL1_PrimId_MphInitReq, "MPH-INIT.req" }, + { GsmL1_PrimId_MphCloseReq, "MPH-CLOSE.req" }, + { GsmL1_PrimId_MphConnectReq, "MPH-CONNECT.req" }, + { GsmL1_PrimId_MphDisconnectReq,"MPH-DISCONNECT.req" }, + { GsmL1_PrimId_MphActivateReq, "MPH-ACTIVATE.req" }, + { GsmL1_PrimId_MphDeactivateReq,"MPH-DEACTIVATE.req" }, + { GsmL1_PrimId_MphConfigReq, "MPH-CONFIG.req" }, + { GsmL1_PrimId_MphMeasureReq, "MPH-MEASURE.req" }, + { GsmL1_PrimId_MphInitCnf, "MPH-INIT.conf" }, + { GsmL1_PrimId_MphCloseCnf, "MPH-CLOSE.conf" }, + { GsmL1_PrimId_MphConnectCnf, "MPH-CONNECT.conf" }, + { GsmL1_PrimId_MphDisconnectCnf,"MPH-DISCONNECT.conf" }, + { GsmL1_PrimId_MphActivateCnf, "MPH-ACTIVATE.conf" }, + { GsmL1_PrimId_MphDeactivateCnf,"MPH-DEACTIVATE.conf" }, + { GsmL1_PrimId_MphConfigCnf, "MPH-CONFIG.conf" }, + { GsmL1_PrimId_MphMeasureCnf, "MPH-MEASURE.conf" }, + { GsmL1_PrimId_MphTimeInd, "MPH-TIME.ind" }, + { GsmL1_PrimId_MphSyncInd, "MPH-SYNC.ind" }, + { GsmL1_PrimId_PhEmptyFrameReq, "PH-EMPTY_FRAME.req" }, + { GsmL1_PrimId_PhDataReq, "PH-DATA.req" }, + { GsmL1_PrimId_PhConnectInd, "PH-CONNECT.ind" }, + { GsmL1_PrimId_PhReadyToSendInd,"PH-READY_TO_SEND.ind" }, + { GsmL1_PrimId_PhDataInd, "PH-DATA.ind" }, + { GsmL1_PrimId_PhRaInd, "PH-RA.ind" }, + { 0, NULL } +}; + +const GsmL1_PrimId_t femtobts_l1prim_req2conf[GsmL1_PrimId_NUM] = { + [GsmL1_PrimId_MphInitReq] = GsmL1_PrimId_MphInitCnf, + [GsmL1_PrimId_MphCloseReq] = GsmL1_PrimId_MphCloseCnf, + [GsmL1_PrimId_MphConnectReq] = GsmL1_PrimId_MphConnectCnf, + [GsmL1_PrimId_MphDisconnectReq] = GsmL1_PrimId_MphDisconnectCnf, + [GsmL1_PrimId_MphActivateReq] = GsmL1_PrimId_MphActivateCnf, + [GsmL1_PrimId_MphDeactivateReq] = GsmL1_PrimId_MphDeactivateCnf, + [GsmL1_PrimId_MphConfigReq] = GsmL1_PrimId_MphConfigCnf, + [GsmL1_PrimId_MphMeasureReq] = GsmL1_PrimId_MphMeasureCnf, +}; + +const enum l1prim_type femtobts_sysprim_type[SuperFemto_PrimId_NUM] = { + [SuperFemto_PrimId_SystemInfoReq] = L1P_T_REQ, + [SuperFemto_PrimId_SystemInfoCnf] = L1P_T_CONF, + [SuperFemto_PrimId_SystemFailureInd] = L1P_T_IND, + [SuperFemto_PrimId_ActivateRfReq] = L1P_T_REQ, + [SuperFemto_PrimId_ActivateRfCnf] = L1P_T_CONF, + [SuperFemto_PrimId_DeactivateRfReq] = L1P_T_REQ, + [SuperFemto_PrimId_DeactivateRfCnf] = L1P_T_CONF, + [SuperFemto_PrimId_SetTraceFlagsReq] = L1P_T_REQ, + [SuperFemto_PrimId_RfClockInfoReq] = L1P_T_REQ, + [SuperFemto_PrimId_RfClockInfoCnf] = L1P_T_CONF, + [SuperFemto_PrimId_RfClockSetupReq] = L1P_T_REQ, + [SuperFemto_PrimId_RfClockSetupCnf] = L1P_T_CONF, + [SuperFemto_PrimId_Layer1ResetReq] = L1P_T_REQ, + [SuperFemto_PrimId_Layer1ResetCnf] = L1P_T_CONF, +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,1,0) + [SuperFemto_PrimId_GetTxCalibTblReq] = L1P_T_REQ, + [SuperFemto_PrimId_GetTxCalibTblCnf] = L1P_T_CONF, + [SuperFemto_PrimId_SetTxCalibTblReq] = L1P_T_REQ, + [SuperFemto_PrimId_SetTxCalibTblCnf] = L1P_T_CONF, + [SuperFemto_PrimId_GetRxCalibTblReq] = L1P_T_REQ, + [SuperFemto_PrimId_GetRxCalibTblCnf] = L1P_T_CONF, + [SuperFemto_PrimId_SetRxCalibTblReq] = L1P_T_REQ, + [SuperFemto_PrimId_SetRxCalibTblCnf] = L1P_T_CONF, +#endif +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0) + [SuperFemto_PrimId_MuteRfReq] = L1P_T_REQ, + [SuperFemto_PrimId_MuteRfCnf] = L1P_T_CONF, +#endif +}; + +const struct value_string femtobts_sysprim_names[SuperFemto_PrimId_NUM+1] = { + { SuperFemto_PrimId_SystemInfoReq, "SYSTEM-INFO.req" }, + { SuperFemto_PrimId_SystemInfoCnf, "SYSTEM-INFO.conf" }, + { SuperFemto_PrimId_SystemFailureInd, "SYSTEM-FAILURE.ind" }, + { SuperFemto_PrimId_ActivateRfReq, "ACTIVATE-RF.req" }, + { SuperFemto_PrimId_ActivateRfCnf, "ACTIVATE-RF.conf" }, + { SuperFemto_PrimId_DeactivateRfReq, "DEACTIVATE-RF.req" }, + { SuperFemto_PrimId_DeactivateRfCnf, "DEACTIVATE-RF.conf" }, + { SuperFemto_PrimId_SetTraceFlagsReq, "SET-TRACE-FLAGS.req" }, + { SuperFemto_PrimId_RfClockInfoReq, "RF-CLOCK-INFO.req" }, + { SuperFemto_PrimId_RfClockInfoCnf, "RF-CLOCK-INFO.conf" }, + { SuperFemto_PrimId_RfClockSetupReq, "RF-CLOCK-SETUP.req" }, + { SuperFemto_PrimId_RfClockSetupCnf, "RF-CLOCK-SETUP.conf" }, + { SuperFemto_PrimId_Layer1ResetReq, "LAYER1-RESET.req" }, + { SuperFemto_PrimId_Layer1ResetCnf, "LAYER1-RESET.conf" }, +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,1,0) + { SuperFemto_PrimId_GetTxCalibTblReq, "GET-TX-CALIB.req" }, + { SuperFemto_PrimId_GetTxCalibTblCnf, "GET-TX-CALIB.cnf" }, + { SuperFemto_PrimId_SetTxCalibTblReq, "SET-TX-CALIB.req" }, + { SuperFemto_PrimId_SetTxCalibTblCnf, "SET-TX-CALIB.cnf" }, + { SuperFemto_PrimId_GetRxCalibTblReq, "GET-RX-CALIB.req" }, + { SuperFemto_PrimId_GetRxCalibTblCnf, "GET-RX-CALIB.cnf" }, + { SuperFemto_PrimId_SetRxCalibTblReq, "SET-RX-CALIB.req" }, + { SuperFemto_PrimId_SetRxCalibTblCnf, "SET-RX-CALIB.cnf" }, +#endif +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0) + { SuperFemto_PrimId_MuteRfReq, "MUTE-RF.req" }, + { SuperFemto_PrimId_MuteRfCnf, "MUTE-RF.cnf" }, +#endif + { 0, NULL } +}; + +const SuperFemto_PrimId_t femtobts_sysprim_req2conf[SuperFemto_PrimId_NUM] = { + [SuperFemto_PrimId_SystemInfoReq] = SuperFemto_PrimId_SystemInfoCnf, + [SuperFemto_PrimId_ActivateRfReq] = SuperFemto_PrimId_ActivateRfCnf, + [SuperFemto_PrimId_DeactivateRfReq] = SuperFemto_PrimId_DeactivateRfCnf, + [SuperFemto_PrimId_RfClockInfoReq] = SuperFemto_PrimId_RfClockInfoCnf, + [SuperFemto_PrimId_RfClockSetupReq] = SuperFemto_PrimId_RfClockSetupCnf, + [SuperFemto_PrimId_Layer1ResetReq] = SuperFemto_PrimId_Layer1ResetCnf, +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,1,0) + [SuperFemto_PrimId_GetTxCalibTblReq] = SuperFemto_PrimId_GetTxCalibTblCnf, + [SuperFemto_PrimId_SetTxCalibTblReq] = SuperFemto_PrimId_SetTxCalibTblCnf, + [SuperFemto_PrimId_GetRxCalibTblReq] = SuperFemto_PrimId_GetRxCalibTblCnf, + [SuperFemto_PrimId_SetRxCalibTblReq] = SuperFemto_PrimId_SetRxCalibTblCnf, +#endif +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0) + [SuperFemto_PrimId_MuteRfReq] = SuperFemto_PrimId_MuteRfCnf, +#endif +}; + +const struct value_string femtobts_l1sapi_names[GsmL1_Sapi_NUM+1] = { + { GsmL1_Sapi_Idle, "IDLE" }, + { GsmL1_Sapi_Fcch, "FCCH" }, + { GsmL1_Sapi_Sch, "SCH" }, + { GsmL1_Sapi_Sacch, "SACCH" }, + { GsmL1_Sapi_Sdcch, "SDCCH" }, + { GsmL1_Sapi_Bcch, "BCCH" }, + { GsmL1_Sapi_Pch, "PCH" }, + { GsmL1_Sapi_Agch, "AGCH" }, + { GsmL1_Sapi_Cbch, "CBCH" }, + { GsmL1_Sapi_Rach, "RACH" }, + { GsmL1_Sapi_TchF, "TCH/F" }, + { GsmL1_Sapi_FacchF, "FACCH/F" }, + { GsmL1_Sapi_TchH, "TCH/H" }, + { GsmL1_Sapi_FacchH, "FACCH/H" }, + { GsmL1_Sapi_Nch, "NCH" }, + { GsmL1_Sapi_Pdtch, "PDTCH" }, + { GsmL1_Sapi_Pacch, "PACCH" }, + { GsmL1_Sapi_Pbcch, "PBCCH" }, + { GsmL1_Sapi_Pagch, "PAGCH" }, + { GsmL1_Sapi_Ppch, "PPCH" }, + { GsmL1_Sapi_Pnch, "PNCH" }, + { GsmL1_Sapi_Ptcch, "PTCCH" }, + { GsmL1_Sapi_Prach, "PRACH" }, + { 0, NULL } +}; + +const struct value_string femtobts_l1status_names[GSML1_STATUS_NUM+1] = { + { GsmL1_Status_Success, "Success" }, + { GsmL1_Status_Generic, "Generic error" }, + { GsmL1_Status_NoMemory, "Not enough memory" }, + { GsmL1_Status_Timeout, "Timeout" }, + { GsmL1_Status_InvalidParam, "Invalid parameter" }, + { GsmL1_Status_Busy, "Resource busy" }, + { GsmL1_Status_NoRessource, "No more resources" }, + { GsmL1_Status_Uninitialized, "Trying to use uninitialized resource" }, + { GsmL1_Status_NullInterface, "Trying to call a NULL interface" }, + { GsmL1_Status_NullFctnPtr, "Trying to call a NULL function ptr" }, + { GsmL1_Status_BadCrc, "Bad CRC" }, + { GsmL1_Status_BadUsf, "Bad USF" }, + { GsmL1_Status_InvalidCPS, "Invalid CPS field" }, + { GsmL1_Status_UnexpectedBurst, "Unexpected burst" }, + { GsmL1_Status_UnavailCodec, "AMR codec is unavailable" }, + { GsmL1_Status_CriticalError, "Critical error" }, + { GsmL1_Status_OverheatError, "Overheat error" }, + { GsmL1_Status_DeviceError, "Device error" }, + { GsmL1_Status_FacchError, "FACCH / TCH order error" }, + { GsmL1_Status_AlreadyDeactivated, "Lchan already deactivated" }, + { GsmL1_Status_TxBurstFifoOvrn, "FIFO overrun" }, + { GsmL1_Status_TxBurstFifoUndr, "FIFO underrun" }, + { GsmL1_Status_NotSynchronized, "Not synchronized" }, + { GsmL1_Status_Unsupported, "Unsupported feature" }, +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(5,1,0) + { GsmL1_Status_ClockError, "Clock error" }, +#endif + { 0, NULL } +}; + +const struct value_string femtobts_tracef_names[29] = { + { DBG_DEBUG, "DEBUG" }, + { DBG_L1WARNING, "L1_WARNING" }, + { DBG_ERROR, "ERROR" }, + { DBG_L1RXMSG, "L1_RX_MSG" }, + { DBG_L1RXMSGBYTE, "L1_RX_MSG_BYTE" }, + { DBG_L1TXMSG, "L1_TX_MSG" }, + { DBG_L1TXMSGBYTE, "L1_TX_MSG_BYTE" }, + { DBG_MPHCNF, "MPH_CNF" }, + { DBG_MPHIND, "MPH_IND" }, + { DBG_MPHREQ, "MPH_REQ" }, + { DBG_PHIND, "PH_IND" }, + { DBG_PHREQ, "PH_REQ" }, + { DBG_PHYRF, "PHY_RF" }, + { DBG_PHYRFMSGBYTE, "PHY_MSG_BYTE" }, + { DBG_MODE, "MODE" }, + { DBG_TDMAINFO, "TDMA_INFO" }, + { DBG_BADCRC, "BAD_CRC" }, + { DBG_PHINDBYTE, "PH_IND_BYTE" }, + { DBG_PHREQBYTE, "PH_REQ_BYTE" }, + { DBG_DEVICEMSG, "DEVICE_MSG" }, + { DBG_RACHINFO, "RACH_INFO" }, + { DBG_LOGCHINFO, "LOG_CH_INFO" }, + { DBG_MEMORY, "MEMORY" }, + { DBG_PROFILING, "PROFILING" }, + { DBG_TESTCOMMENT, "TEST_COMMENT" }, + { DBG_TEST, "TEST" }, + { DBG_STATUS, "STATUS" }, + { 0, NULL } +}; + +const struct value_string femtobts_tracef_docs[29] = { + { DBG_DEBUG, "Debug Region" }, + { DBG_L1WARNING, "L1 Warning Region" }, + { DBG_ERROR, "Error Region" }, + { DBG_L1RXMSG, "L1_RX_MSG Region" }, + { DBG_L1RXMSGBYTE, "L1_RX_MSG_BYTE Region" }, + { DBG_L1TXMSG, "L1_TX_MSG Region" }, + { DBG_L1TXMSGBYTE, "L1_TX_MSG_BYTE Region" }, + { DBG_MPHCNF, "MphConfirmation Region" }, + { DBG_MPHIND, "MphIndication Region" }, + { DBG_MPHREQ, "MphRequest Region" }, + { DBG_PHIND, "PhIndication Region" }, + { DBG_PHREQ, "PhRequest Region" }, + { DBG_PHYRF, "PhyRF Region" }, + { DBG_PHYRFMSGBYTE, "PhyRF Message Region" }, + { DBG_MODE, "Mode Region" }, + { DBG_TDMAINFO, "TDMA Info Region" }, + { DBG_BADCRC, "Bad CRC Region" }, + { DBG_PHINDBYTE, "PH_IND_BYTE" }, + { DBG_PHREQBYTE, "PH_REQ_BYTE" }, + { DBG_DEVICEMSG, "Device Message Region" }, + { DBG_RACHINFO, "RACH Info" }, + { DBG_LOGCHINFO, "LOG_CH_INFO" }, + { DBG_MEMORY, "Memory Region" }, + { DBG_PROFILING, "Profiling Region" }, + { DBG_TESTCOMMENT, "Test Comments" }, + { DBG_TEST, "Test Region" }, + { DBG_STATUS, "Status Region" }, + { 0, NULL } +}; + +const struct value_string femtobts_tch_pl_names[] = { + { GsmL1_TchPlType_NA, "N/A" }, + { GsmL1_TchPlType_Fr, "FR" }, + { GsmL1_TchPlType_Hr, "HR" }, + { GsmL1_TchPlType_Efr, "EFR" }, + { GsmL1_TchPlType_Amr, "AMR(IF2)" }, + { GsmL1_TchPlType_Amr_SidBad, "AMR(SID BAD)" }, + { GsmL1_TchPlType_Amr_Onset, "AMR(ONSET)" }, + { GsmL1_TchPlType_Amr_Ratscch, "AMR(RATSCCH)" }, + { GsmL1_TchPlType_Amr_SidUpdateInH, "AMR(SID_UPDATE INH)" }, + { GsmL1_TchPlType_Amr_SidFirstP1, "AMR(SID_FIRST P1)" }, + { GsmL1_TchPlType_Amr_SidFirstP2, "AMR(SID_FIRST P2)" }, + { GsmL1_TchPlType_Amr_SidFirstInH, "AMR(SID_FIRST INH)" }, + { GsmL1_TchPlType_Amr_RatscchMarker, "AMR(RATSCCH MARK)" }, + { GsmL1_TchPlType_Amr_RatscchData, "AMR(RATSCCH DATA)" }, + { 0, NULL } +}; + +const struct value_string femtobts_clksrc_names[] = { +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,1,0) + { SuperFemto_ClkSrcId_None, "None" }, + { SuperFemto_ClkSrcId_Ocxo, "ocxo" }, + { SuperFemto_ClkSrcId_Tcxo, "tcxo" }, + { SuperFemto_ClkSrcId_External, "ext" }, + { SuperFemto_ClkSrcId_GpsPps, "gps" }, + { SuperFemto_ClkSrcId_Trx, "trx" }, + { SuperFemto_ClkSrcId_Rx, "rx" }, + { SuperFemto_ClkSrcId_Edge, "edge" }, + { SuperFemto_ClkSrcId_NetList, "nwl" }, +#else + { SF_CLKSRC_NONE, "None" }, + { SF_CLKSRC_OCXO, "ocxo" }, + { SF_CLKSRC_TCXO, "tcxo" }, + { SF_CLKSRC_EXT, "ext" }, + { SF_CLKSRC_GPS, "gps" }, + { SF_CLKSRC_TRX, "trx" }, + { SF_CLKSRC_RX, "rx" }, +#endif + { 0, NULL } +}; + +const struct value_string femtobts_dir_names[] = { + { GsmL1_Dir_TxDownlink, "TxDL" }, + { GsmL1_Dir_TxUplink, "TxUL" }, + { GsmL1_Dir_RxUplink, "RxUL" }, + { GsmL1_Dir_RxDownlink, "RxDL" }, + { GsmL1_Dir_TxDownlink|GsmL1_Dir_RxUplink, "BOTH" }, + { 0, NULL } +}; + +const struct value_string femtobts_chcomb_names[] = { + { GsmL1_LogChComb_0, "dummy" }, + { GsmL1_LogChComb_I, "tch_f" }, + { GsmL1_LogChComb_II, "tch_h" }, + { GsmL1_LogChComb_IV, "ccch" }, + { GsmL1_LogChComb_V, "ccch_sdcch4" }, + { GsmL1_LogChComb_VII, "sdcch8" }, + { GsmL1_LogChComb_XIII, "pdtch" }, + { 0, NULL } +}; + +const uint8_t pdch_msu_size[_NUM_PDCH_CS] = { + [PDCH_CS_1] = 23, + [PDCH_CS_2] = 34, + [PDCH_CS_3] = 40, + [PDCH_CS_4] = 54, + [PDCH_MCS_1] = 27, + [PDCH_MCS_2] = 33, + [PDCH_MCS_3] = 42, + [PDCH_MCS_4] = 49, + [PDCH_MCS_5] = 60, + [PDCH_MCS_6] = 78, + [PDCH_MCS_7] = 118, + [PDCH_MCS_8] = 142, + [PDCH_MCS_9] = 154 +}; diff --git a/src/osmo-bts-sysmo/femtobts.h b/src/osmo-bts-sysmo/femtobts.h new file mode 100644 index 0000000..9163ebb --- /dev/null +++ b/src/osmo-bts-sysmo/femtobts.h @@ -0,0 +1,110 @@ +#ifndef FEMTOBTS_H +#define FEMTOBTS_H + +#include +#include + +#include +#include + +#ifdef FEMTOBTS_API_VERSION +#define SuperFemto_PrimId_t FemtoBts_PrimId_t +#define SuperFemto_Prim_t FemtoBts_Prim_t +#define SuperFemto_PrimId_SystemInfoReq FemtoBts_PrimId_SystemInfoReq +#define SuperFemto_PrimId_SystemInfoCnf FemtoBts_PrimId_SystemInfoCnf +#define SuperFemto_SystemInfoCnf_t FemtoBts_SystemInfoCnf_t +#define SuperFemto_PrimId_SystemFailureInd FemtoBts_PrimId_SystemFailureInd +#define SuperFemto_PrimId_ActivateRfReq FemtoBts_PrimId_ActivateRfReq +#define SuperFemto_PrimId_ActivateRfCnf FemtoBts_PrimId_ActivateRfCnf +#define SuperFemto_PrimId_DeactivateRfReq FemtoBts_PrimId_DeactivateRfReq +#define SuperFemto_PrimId_DeactivateRfCnf FemtoBts_PrimId_DeactivateRfCnf +#define SuperFemto_PrimId_SetTraceFlagsReq FemtoBts_PrimId_SetTraceFlagsReq +#define SuperFemto_PrimId_RfClockInfoReq FemtoBts_PrimId_RfClockInfoReq +#define SuperFemto_PrimId_RfClockInfoCnf FemtoBts_PrimId_RfClockInfoCnf +#define SuperFemto_PrimId_RfClockSetupReq FemtoBts_PrimId_RfClockSetupReq +#define SuperFemto_PrimId_RfClockSetupCnf FemtoBts_PrimId_RfClockSetupCnf +#define SuperFemto_PrimId_Layer1ResetReq FemtoBts_PrimId_Layer1ResetReq +#define SuperFemto_PrimId_Layer1ResetCnf FemtoBts_PrimId_Layer1ResetCnf +#define SuperFemto_PrimId_NUM FemtoBts_PrimId_NUM +#define HW_SYSMOBTS_V1 1 +#define SUPERFEMTO_API(x,y,z) FEMTOBTS_API(x,y,z) +#endif + +#ifdef L1_HAS_RTP_MODE +/* + * The bit ordering has been fixed on >= 3.10 but I am verifying + * this on 3.11. + */ +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3, 11, 0) +#define USE_L1_RTP_MODE /* Tell L1 to use RTP mode */ +#endif +#endif + +/* + * Depending on the firmware version either GsmL1_Prim_t or SuperFemto_Prim_t + * is the bigger struct. For earlier firmware versions the GsmL1_Prim_t was the + * bigger struct. + */ +#define SYSMOBTS_PRIM_SIZE \ + (OSMO_MAX(sizeof(SuperFemto_Prim_t), sizeof(GsmL1_Prim_t)) + 128) + +enum l1prim_type { + L1P_T_INVALID, /* this must be 0 to detect uninitialized elements */ + L1P_T_REQ, + L1P_T_CONF, + L1P_T_IND, +}; + +#if !defined(SUPERFEMTO_API_VERSION) || SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,1,0) +enum uperfemto_clk_src { + SF_CLKSRC_NONE = 0, + SF_CLKSRC_OCXO = 1, + SF_CLKSRC_TCXO = 2, + SF_CLKSRC_EXT = 3, + SF_CLKSRC_GPS = 4, + SF_CLKSRC_TRX = 5, + SF_CLKSRC_RX = 6, + SF_CLKSRC_NL = 7, +}; +#endif + +const enum l1prim_type femtobts_l1prim_type[GsmL1_PrimId_NUM]; +const struct value_string femtobts_l1prim_names[GsmL1_PrimId_NUM+1]; +const GsmL1_PrimId_t femtobts_l1prim_req2conf[GsmL1_PrimId_NUM]; + +const enum l1prim_type femtobts_sysprim_type[SuperFemto_PrimId_NUM]; +const struct value_string femtobts_sysprim_names[SuperFemto_PrimId_NUM+1]; +const SuperFemto_PrimId_t femtobts_sysprim_req2conf[SuperFemto_PrimId_NUM]; + +const struct value_string femtobts_l1sapi_names[GsmL1_Sapi_NUM+1]; +const struct value_string femtobts_l1status_names[GSML1_STATUS_NUM+1]; + +const struct value_string femtobts_tracef_names[29]; +const struct value_string femtobts_tracef_docs[29]; + +const struct value_string femtobts_tch_pl_names[15]; +const struct value_string femtobts_chcomb_names[8]; +const struct value_string femtobts_clksrc_names[10]; + +const struct value_string femtobts_dir_names[6]; + +enum pdch_cs { + PDCH_CS_1, + PDCH_CS_2, + PDCH_CS_3, + PDCH_CS_4, + PDCH_MCS_1, + PDCH_MCS_2, + PDCH_MCS_3, + PDCH_MCS_4, + PDCH_MCS_5, + PDCH_MCS_6, + PDCH_MCS_7, + PDCH_MCS_8, + PDCH_MCS_9, + _NUM_PDCH_CS +}; + +const uint8_t pdch_msu_size[_NUM_PDCH_CS]; + +#endif /* FEMTOBTS_H */ diff --git a/src/osmo-bts-sysmo/hw_misc.c b/src/osmo-bts-sysmo/hw_misc.c new file mode 100644 index 0000000..6aa3b83 --- /dev/null +++ b/src/osmo-bts-sysmo/hw_misc.c @@ -0,0 +1,113 @@ +/* Misc HW routines for Sysmocom BTS */ + +/* (C) 2012 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "hw_misc.h" + +static const struct value_string sysmobts_led_names[] = { + { LED_RF_ACTIVE, "activity_led" }, + { LED_ONLINE, "online_led" }, + { 0, NULL } +}; + +int sysmobts_led_set(enum sysmobts_led nr, int on) +{ + char tmp[PATH_MAX+1]; + const char *filename; + int fd; + uint8_t byte; + + if (on) + byte = '1'; + else + byte = '0'; + + filename = get_value_string(sysmobts_led_names, nr); + if (!filename) + return -EINVAL; + + snprintf(tmp, sizeof(tmp)-1, "/sys/class/leds/%s/brightness", filename); + tmp[sizeof(tmp)-1] = '\0'; + + fd = open(tmp, O_WRONLY); + if (fd < 0) + return -ENODEV; + + write(fd, &byte, 1); + + close(fd); + + return 0; +} + +#if 0 +#define HWMON_PREFIX "/sys/class/hwmon/hwmon0/device" + +static FILE *temperature_f[NUM_TEMP]; + +int sysmobts_temp_init() +{ + char tmp[PATH_MAX+1]; + FILE *in; + int rc = 0; + + for (i = 0; i < NUM_TEMP; i++) { + snprintf(tmp, sizeof(tmp)-1, HWMON_PREFIX "/temp%u_input", i+1), + tmp[sizeof(tmp)-1] = '\0'; + + temperature_f[i] = fopen(tmp, "r"); + if (!temperature_f[i]) + rc = -ENODEV; + } + + return 0; +} + +int sysmobts_temp_get(uint8_t num) +{ + if (num >= NUM_TEMP) + return -EINVAL; + + if (!temperature_f[num]) + return -ENODEV; + + + in = fopen(tmp, "r"); + if (!in) + return -ENODEV; + + fclose(tmp); + + return 0; +} +#endif diff --git a/src/osmo-bts-sysmo/hw_misc.h b/src/osmo-bts-sysmo/hw_misc.h new file mode 100644 index 0000000..c4838db --- /dev/null +++ b/src/osmo-bts-sysmo/hw_misc.h @@ -0,0 +1,12 @@ +#ifndef _SYSMOBTS_HW_MISC_H +#define _SYSMOBTS_HW_MISC_H + +enum sysmobts_led { + LED_NONE, + LED_RF_ACTIVE, + LED_ONLINE, +}; + +int sysmobts_led_set(enum sysmobts_led nr, int on); + +#endif diff --git a/src/osmo-bts-sysmo/l1_fwd.h b/src/osmo-bts-sysmo/l1_fwd.h new file mode 100644 index 0000000..5539792 --- /dev/null +++ b/src/osmo-bts-sysmo/l1_fwd.h @@ -0,0 +1,5 @@ +#define L1FWD_L1_PORT 9999 +#define L1FWD_SYS_PORT 9998 +#define L1FWD_TCH_PORT 9997 +#define L1FWD_PDTCH_PORT 9996 + diff --git a/src/osmo-bts-sysmo/l1_fwd_main.c b/src/osmo-bts-sysmo/l1_fwd_main.c new file mode 100644 index 0000000..bc9fc21 --- /dev/null +++ b/src/osmo-bts-sysmo/l1_fwd_main.c @@ -0,0 +1,236 @@ +/* Sysmocom femtobts L1 proxy */ + +/* (C) 2011 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "femtobts.h" +#include "l1_if.h" +#include "l1_transp.h" +#include "l1_fwd.h" + +static const uint16_t fwd_udp_ports[_NUM_MQ_WRITE] = { + [MQ_SYS_READ] = L1FWD_SYS_PORT, + [MQ_L1_READ] = L1FWD_L1_PORT, +#ifndef HW_SYSMOBTS_V1 + [MQ_TCH_READ] = L1FWD_TCH_PORT, + [MQ_PDTCH_READ] = L1FWD_PDTCH_PORT, +#endif +}; + +struct l1fwd_hdl { + struct sockaddr_storage remote_sa[_NUM_MQ_WRITE]; + socklen_t remote_sa_len[_NUM_MQ_WRITE]; + + struct osmo_wqueue udp_wq[_NUM_MQ_WRITE]; + + struct femtol1_hdl *fl1h; +}; + + +/* callback when there's a new L1 primitive coming in from the HW */ +int l1if_handle_l1prim(int wq, struct femtol1_hdl *fl1h, struct msgb *msg) +{ + struct l1fwd_hdl *l1fh = fl1h->priv; + + /* Enqueue message to UDP socket */ + if (osmo_wqueue_enqueue(&l1fh->udp_wq[wq], msg) != 0) { + LOGP(DL1C, LOGL_ERROR, "Write queue %d full. dropping msg\n", wq); + msgb_free(msg); + return -EAGAIN; + } + return 0; +} + +/* callback when there's a new SYS primitive coming in from the HW */ +int l1if_handle_sysprim(struct femtol1_hdl *fl1h, struct msgb *msg) +{ + struct l1fwd_hdl *l1fh = fl1h->priv; + + /* Enqueue message to UDP socket */ + if (osmo_wqueue_enqueue(&l1fh->udp_wq[MQ_SYS_WRITE], msg) != 0) { + LOGP(DL1C, LOGL_ERROR, "MQ_SYS_WRITE ful. dropping msg\n"); + msgb_free(msg); + return -EAGAIN; + } + return 0; +} + + +/* data has arrived on the udp socket */ +static int udp_read_cb(struct osmo_fd *ofd) +{ + struct msgb *msg = msgb_alloc_headroom(SYSMOBTS_PRIM_SIZE, 128, "udp_rx"); + struct l1fwd_hdl *l1fh = ofd->data; + struct femtol1_hdl *fl1h = l1fh->fl1h; + int rc; + + if (!msg) + return -ENOMEM; + + msg->l1h = msg->data; + + l1fh->remote_sa_len[ofd->priv_nr] = sizeof(l1fh->remote_sa[ofd->priv_nr]); + rc = recvfrom(ofd->fd, msg->l1h, msgb_tailroom(msg), 0, + (struct sockaddr *) &l1fh->remote_sa[ofd->priv_nr], &l1fh->remote_sa_len[ofd->priv_nr]); + if (rc < 0) { + perror("read from udp"); + msgb_free(msg); + return rc; + } else if (rc == 0) { + perror("len=0 read from udp"); + msgb_free(msg); + return rc; + } + msgb_put(msg, rc); + + DEBUGP(DL1C, "UDP: Received %u bytes for queue %d\n", rc, + ofd->priv_nr); + + /* put the message into the right queue */ + if (osmo_wqueue_enqueue(&fl1h->write_q[ofd->priv_nr], msg) != 0) { + LOGP(DL1C, LOGL_ERROR, "Write queue %d full. dropping msg\n", + ofd->priv_nr); + msgb_free(msg); + return -EAGAIN; + } + return 0; +} + +/* callback when we can write to the UDP socket */ +static int udp_write_cb(struct osmo_fd *ofd, struct msgb *msg) +{ + int rc; + struct l1fwd_hdl *l1fh = ofd->data; + + DEBUGP(DL1C, "UDP: Writing %u bytes for queue %d\n", msgb_l1len(msg), + ofd->priv_nr); + + rc = sendto(ofd->fd, msg->l1h, msgb_l1len(msg), 0, + (const struct sockaddr *)&l1fh->remote_sa[ofd->priv_nr], l1fh->remote_sa_len[ofd->priv_nr]); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "error writing to L1 msg_queue: %s\n", + strerror(errno)); + return rc; + } else if (rc < msgb_l1len(msg)) { + LOGP(DL1C, LOGL_ERROR, "short write to L1 msg_queue: " + "%u < %u\n", rc, msgb_l1len(msg)); + return -EIO; + } + + return 0; +} + +int main(int argc, char **argv) +{ + struct l1fwd_hdl *l1fh; + struct femtol1_hdl *fl1h; + int rc, i; + void *ctx = talloc_named_const(NULL, 0, "l1_fwd"); + + printf("sizeof(GsmL1_Prim_t) = %zu\n", sizeof(GsmL1_Prim_t)); + printf("sizeof(SuperFemto_Prim_t) = %zu\n", sizeof(SuperFemto_Prim_t)); + + osmo_init_logging2(ctx, &bts_log_info); + + /* + * hack and prevent that two l1fwd-proxy/sysmobts run at the same + * time. This is done by binding to the same VTY port. + */ + rc = osmo_sock_init(AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP, + "127.0.0.1", 4241, OSMO_SOCK_F_BIND); + if (rc < 0) { + fprintf(stderr, "Failed to bind to the BTS VTY port.\n"); + return EXIT_FAILURE; + } + + /* allocate new femtol1_handle */ + fl1h = talloc_zero(ctx, struct femtol1_hdl); + INIT_LLIST_HEAD(&fl1h->wlc_list); + + /* open the actual hardware transport */ + for (i = 0; i < ARRAY_SIZE(fl1h->write_q); i++) { + rc = l1if_transport_open(i, fl1h); + if (rc < 0) + exit(1); + } + + /* create our fwd handle */ + l1fh = talloc_zero(ctx, struct l1fwd_hdl); + + l1fh->fl1h = fl1h; + fl1h->priv = l1fh; + + /* Open UDP */ + for (i = 0; i < ARRAY_SIZE(l1fh->udp_wq); i++) { + struct osmo_wqueue *wq = &l1fh->udp_wq[i]; + + osmo_wqueue_init(wq, 10); + wq->write_cb = udp_write_cb; + wq->read_cb = udp_read_cb; + + wq->bfd.when |= BSC_FD_READ; + wq->bfd.data = l1fh; + wq->bfd.priv_nr = i; + rc = osmo_sock_init_ofd(&wq->bfd, AF_UNSPEC, SOCK_DGRAM, + IPPROTO_UDP, NULL, fwd_udp_ports[i], + OSMO_SOCK_F_BIND); + if (rc < 0) { + perror("sock_init"); + exit(1); + } + } + + while (1) { + rc = osmo_select_main(0); + if (rc < 0) { + perror("select"); + exit(1); + } + } + exit(0); +} diff --git a/src/osmo-bts-sysmo/l1_if.c b/src/osmo-bts-sysmo/l1_if.c new file mode 100644 index 0000000..57e2d5c --- /dev/null +++ b/src/osmo-bts-sysmo/l1_if.c @@ -0,0 +1,1891 @@ +/* Interface handler for Sysmocom L1 */ + +/* (C) 2011-2016 by Harald Welte + * (C) 2014 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "femtobts.h" +#include "l1_if.h" +#include "l1_transp.h" +#include "hw_misc.h" +#include "misc/sysmobts_par.h" +#include "eeprom.h" +#include "utils.h" + +struct wait_l1_conf { + struct llist_head list; /* internal linked list */ + struct osmo_timer_list timer; /* timer for L1 timeout */ + unsigned int conf_prim_id; /* primitive we expect in response */ + HANDLE conf_hLayer3; /* layer 3 handle we expect in response */ + unsigned int is_sys_prim; /* is this a system (1) or L1 (0) primitive */ + l1if_compl_cb *cb; + void *cb_data; +}; + +static void release_wlc(struct wait_l1_conf *wlc) +{ + osmo_timer_del(&wlc->timer); + talloc_free(wlc); +} + +static void l1if_req_timeout(void *data) +{ + struct wait_l1_conf *wlc = data; + + if (wlc->is_sys_prim) + LOGP(DL1C, LOGL_FATAL, "Timeout waiting for SYS primitive %s\n", + get_value_string(femtobts_sysprim_names, wlc->conf_prim_id)); + else + LOGP(DL1C, LOGL_FATAL, "Timeout waiting for L1 primitive %s\n", + get_value_string(femtobts_l1prim_names, wlc->conf_prim_id)); + exit(23); +} + +static HANDLE l1p_get_hLayer3(GsmL1_Prim_t *prim) +{ + switch (prim->id) { + case GsmL1_PrimId_MphInitReq: + return prim->u.mphInitReq.hLayer3; + case GsmL1_PrimId_MphCloseReq: + return prim->u.mphCloseReq.hLayer3; + case GsmL1_PrimId_MphConnectReq: + return prim->u.mphConnectReq.hLayer3; + case GsmL1_PrimId_MphDisconnectReq: + return prim->u.mphDisconnectReq.hLayer3; + case GsmL1_PrimId_MphActivateReq: + return prim->u.mphActivateReq.hLayer3; + case GsmL1_PrimId_MphDeactivateReq: + return prim->u.mphDeactivateReq.hLayer3; + case GsmL1_PrimId_MphConfigReq: + return prim->u.mphConfigReq.hLayer3; + case GsmL1_PrimId_MphMeasureReq: + return prim->u.mphMeasureReq.hLayer3; + case GsmL1_PrimId_MphInitCnf: + return prim->u.mphInitCnf.hLayer3; + case GsmL1_PrimId_MphCloseCnf: + return prim->u.mphCloseCnf.hLayer3; + case GsmL1_PrimId_MphConnectCnf: + return prim->u.mphConnectCnf.hLayer3; + case GsmL1_PrimId_MphDisconnectCnf: + return prim->u.mphDisconnectCnf.hLayer3; + case GsmL1_PrimId_MphActivateCnf: + return prim->u.mphActivateCnf.hLayer3; + case GsmL1_PrimId_MphDeactivateCnf: + return prim->u.mphDeactivateCnf.hLayer3; + case GsmL1_PrimId_MphConfigCnf: + return prim->u.mphConfigCnf.hLayer3; + case GsmL1_PrimId_MphMeasureCnf: + return prim->u.mphMeasureCnf.hLayer3; + case GsmL1_PrimId_MphTimeInd: + case GsmL1_PrimId_MphSyncInd: + case GsmL1_PrimId_PhEmptyFrameReq: + case GsmL1_PrimId_PhDataReq: + case GsmL1_PrimId_PhConnectInd: + case GsmL1_PrimId_PhReadyToSendInd: + case GsmL1_PrimId_PhDataInd: + case GsmL1_PrimId_PhRaInd: + break; + default: + LOGP(DL1C, LOGL_ERROR, "unknown L1 primitive %u\n", prim->id); + break; + } + return 0; +} + + +static int _l1if_req_compl(struct femtol1_hdl *fl1h, struct msgb *msg, + int is_system_prim, l1if_compl_cb *cb, void *data) +{ + struct wait_l1_conf *wlc; + struct osmo_wqueue *wqueue; + unsigned int timeout_secs; + + /* allocate new wsc and store reference to mutex and conf_id */ + wlc = talloc_zero(fl1h, struct wait_l1_conf); + wlc->cb = cb; + wlc->cb_data = data; + + /* Make sure we actually have received a REQUEST type primitive */ + if (is_system_prim == 0) { + GsmL1_Prim_t *l1p = msgb_l1prim(msg); + + LOGP(DL1P, LOGL_INFO, "Tx L1 prim %s\n", + get_value_string(femtobts_l1prim_names, l1p->id)); + + if (femtobts_l1prim_type[l1p->id] != L1P_T_REQ) { + LOGP(DL1C, LOGL_ERROR, "L1 Prim %s is not a Request!\n", + get_value_string(femtobts_l1prim_names, l1p->id)); + talloc_free(wlc); + return -EINVAL; + } + wlc->is_sys_prim = 0; + wlc->conf_prim_id = femtobts_l1prim_req2conf[l1p->id]; + wlc->conf_hLayer3 = l1p_get_hLayer3(l1p); + wqueue = &fl1h->write_q[MQ_L1_WRITE]; + timeout_secs = 30; + } else { + SuperFemto_Prim_t *sysp = msgb_sysprim(msg); + + LOGP(DL1C, LOGL_INFO, "Tx SYS prim %s\n", + get_value_string(femtobts_sysprim_names, sysp->id)); + + if (femtobts_sysprim_type[sysp->id] != L1P_T_REQ) { + LOGP(DL1C, LOGL_ERROR, "SYS Prim %s is not a Request!\n", + get_value_string(femtobts_sysprim_names, sysp->id)); + talloc_free(wlc); + return -EINVAL; + } + wlc->is_sys_prim = 1; + wlc->conf_prim_id = femtobts_sysprim_req2conf[sysp->id]; + wqueue = &fl1h->write_q[MQ_SYS_WRITE]; + timeout_secs = 30; + } + + /* enqueue the message in the queue and add wsc to list */ + if (osmo_wqueue_enqueue(wqueue, msg) != 0) { + /* So we will get a timeout but the log message might help */ + LOGP(DL1C, LOGL_ERROR, "Write queue for %s full. dropping msg.\n", + is_system_prim ? "system primitive" : "gsm"); + msgb_free(msg); + } + llist_add(&wlc->list, &fl1h->wlc_list); + + /* schedule a timer for timeout_secs seconds. If DSP fails to respond, we terminate */ + wlc->timer.data = wlc; + wlc->timer.cb = l1if_req_timeout; + osmo_timer_schedule(&wlc->timer, timeout_secs, 0); + + return 0; +} + +/* send a request primitive to the L1 and schedule completion call-back */ +int l1if_req_compl(struct femtol1_hdl *fl1h, struct msgb *msg, + l1if_compl_cb *cb, void *data) +{ + return _l1if_req_compl(fl1h, msg, 1, cb, data); +} + +int l1if_gsm_req_compl(struct femtol1_hdl *fl1h, struct msgb *msg, + l1if_compl_cb *cb, void *data) +{ + return _l1if_req_compl(fl1h, msg, 0, cb, data); +} + +/* allocate a msgb containing a GsmL1_Prim_t */ +struct msgb *l1p_msgb_alloc(void) +{ + struct msgb *msg = msgb_alloc(sizeof(GsmL1_Prim_t), "l1_prim"); + + if (msg) + msg->l1h = msgb_put(msg, sizeof(GsmL1_Prim_t)); + + return msg; +} + +/* allocate a msgb containing a SuperFemto_Prim_t */ +struct msgb *sysp_msgb_alloc(void) +{ + struct msgb *msg = msgb_alloc(sizeof(SuperFemto_Prim_t), "sys_prim"); + + if (msg) + msg->l1h = msgb_put(msg, sizeof(SuperFemto_Prim_t)); + + return msg; +} + +static GsmL1_PhDataReq_t * +data_req_from_rts_ind(GsmL1_Prim_t *l1p, + const GsmL1_PhReadyToSendInd_t *rts_ind) +{ + GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq; + + l1p->id = GsmL1_PrimId_PhDataReq; + + /* copy fields from PH-RSS.ind */ + data_req->hLayer1 = rts_ind->hLayer1; + data_req->u8Tn = rts_ind->u8Tn; + data_req->u32Fn = rts_ind->u32Fn; + data_req->sapi = rts_ind->sapi; + data_req->subCh = rts_ind->subCh; + data_req->u8BlockNbr = rts_ind->u8BlockNbr; + + return data_req; +} + +static GsmL1_PhEmptyFrameReq_t * +empty_req_from_rts_ind(GsmL1_Prim_t *l1p, + const GsmL1_PhReadyToSendInd_t *rts_ind) +{ + GsmL1_PhEmptyFrameReq_t *empty_req = &l1p->u.phEmptyFrameReq; + + l1p->id = GsmL1_PrimId_PhEmptyFrameReq; + + empty_req->hLayer1 = rts_ind->hLayer1; + empty_req->u8Tn = rts_ind->u8Tn; + empty_req->u32Fn = rts_ind->u32Fn; + empty_req->sapi = rts_ind->sapi; + empty_req->subCh = rts_ind->subCh; + empty_req->u8BlockNbr = rts_ind->u8BlockNbr; + + return empty_req; +} + +/* fill PH-DATA.req from l1sap primitive */ +static GsmL1_PhDataReq_t * +data_req_from_l1sap(GsmL1_Prim_t *l1p, struct femtol1_hdl *fl1, + uint8_t tn, uint32_t fn, uint8_t sapi, uint8_t sub_ch, + uint8_t block_nr, uint8_t len) +{ + GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq; + + l1p->id = GsmL1_PrimId_PhDataReq; + + /* copy fields from PH-RSS.ind */ + data_req->hLayer1 = fl1->hLayer1; + data_req->u8Tn = tn; + data_req->u32Fn = fn; + data_req->sapi = sapi; + data_req->subCh = sub_ch; + data_req->u8BlockNbr = block_nr; + + data_req->msgUnitParam.u8Size = len; + + return data_req; +} + +/* fill PH-EMPTY_FRAME.req from l1sap primitive */ +static GsmL1_PhEmptyFrameReq_t * +empty_req_from_l1sap(GsmL1_Prim_t *l1p, struct femtol1_hdl *fl1, + uint8_t tn, uint32_t fn, uint8_t sapi, + uint8_t subch, uint8_t block_nr) +{ + GsmL1_PhEmptyFrameReq_t *empty_req = &l1p->u.phEmptyFrameReq; + + l1p->id = GsmL1_PrimId_PhEmptyFrameReq; + + empty_req->hLayer1 = fl1->hLayer1; + empty_req->u8Tn = tn; + empty_req->u32Fn = fn; + empty_req->sapi = sapi; + empty_req->subCh = subch; + empty_req->u8BlockNbr = block_nr; + + return empty_req; +} + +static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap, bool use_cache) +{ + struct femtol1_hdl *fl1 = trx_femtol1_hdl(trx); + struct msgb *l1msg = l1p_msgb_alloc(); + struct gsm_lchan *lchan; + uint32_t u32Fn; + uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi = 0; + uint8_t chan_nr, link_id; + int len; + + if (!msg) { + LOGP(DL1C, LOGL_FATAL, "PH-DATA.req without msg. " + "Please fix!\n"); + abort(); + } + + len = msgb_l2len(msg); + + chan_nr = l1sap->u.data.chan_nr; + link_id = l1sap->u.data.link_id; + u32Fn = l1sap->u.data.fn; + u8Tn = L1SAP_CHAN2TS(chan_nr); + subCh = 0x1f; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + if (L1SAP_IS_LINK_SACCH(link_id)) { + sapi = GsmL1_Sapi_Sacch; + if (!L1SAP_IS_CHAN_TCHF(chan_nr) && !L1SAP_IS_CHAN_PDCH(chan_nr)) + subCh = l1sap_chan2ss(chan_nr); + } else if (L1SAP_IS_CHAN_TCHF(chan_nr) || L1SAP_IS_CHAN_PDCH(chan_nr)) { + if (ts_is_pdch(&trx->ts[u8Tn])) { + if (L1SAP_IS_PTCCH(u32Fn)) { + sapi = GsmL1_Sapi_Ptcch; + u8BlockNbr = L1SAP_FN2PTCCHBLOCK(u32Fn); + } else { + sapi = GsmL1_Sapi_Pdtch; + u8BlockNbr = L1SAP_FN2MACBLOCK(u32Fn); + } + } else { + sapi = GsmL1_Sapi_FacchF; + u8BlockNbr = (u32Fn % 13) >> 2; + } + } else if (L1SAP_IS_CHAN_TCHH(chan_nr)) { + subCh = L1SAP_CHAN2SS_TCHH(chan_nr); + sapi = GsmL1_Sapi_FacchH; + u8BlockNbr = (u32Fn % 26) >> 3; + } else if (L1SAP_IS_CHAN_SDCCH4(chan_nr)) { + subCh = L1SAP_CHAN2SS_SDCCH4(chan_nr); + sapi = GsmL1_Sapi_Sdcch; + } else if (L1SAP_IS_CHAN_SDCCH8(chan_nr)) { + subCh = L1SAP_CHAN2SS_SDCCH8(chan_nr); + sapi = GsmL1_Sapi_Sdcch; + } else if (L1SAP_IS_CHAN_BCCH(chan_nr)) { + sapi = GsmL1_Sapi_Bcch; + } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) { + /* The sapi depends on DSP configuration, not + * on the actual SYSTEM INFORMATION 3. */ + u8BlockNbr = L1SAP_FN2CCCHBLOCK(u32Fn); + if (u8BlockNbr >= 1) + sapi = GsmL1_Sapi_Pch; + else + sapi = GsmL1_Sapi_Agch; + } else { + LOGPFN(DL1C, LOGL_NOTICE, u32Fn, "unknown prim %d op %d " + "chan_nr %d link_id %d\n", l1sap->oph.primitive, + l1sap->oph.operation, chan_nr, link_id); + msgb_free(l1msg); + return -EINVAL; + } + + /* convert l1sap message to GsmL1 primitive, keep payload */ + if (len) { + /* data request */ + GsmL1_Prim_t *l1p = msgb_l1prim(l1msg); + data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr, len); + if (use_cache) + memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer, + lchan->tch.dtx.facch, msgb_l2len(msg)); + else if (dtx_dl_amr_enabled(lchan) && + ((lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F) || + (lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F) || + (lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F))) { + if (sapi == GsmL1_Sapi_FacchF) { + sapi = GsmL1_Sapi_TchF; + } + if (sapi == GsmL1_Sapi_FacchH) { + sapi = GsmL1_Sapi_TchH; + subCh = L1SAP_CHAN2SS_TCHH(chan_nr); + u8BlockNbr = (u32Fn % 13) >> 2; + } + if (sapi == GsmL1_Sapi_TchH || sapi == GsmL1_Sapi_TchF) { + /* FACCH interruption of DTX silence */ + /* cache FACCH data */ + memcpy(lchan->tch.dtx.facch, msg->l2h, + msgb_l2len(msg)); + /* prepare ONSET or INH message */ + if(lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F) + l1p->u.phDataReq.msgUnitParam.u8Buffer[0] = + GsmL1_TchPlType_Amr_Onset; + else if(lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F) + l1p->u.phDataReq.msgUnitParam.u8Buffer[0] = + GsmL1_TchPlType_Amr_SidUpdateInH; + else if(lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F) + l1p->u.phDataReq.msgUnitParam.u8Buffer[0] = + GsmL1_TchPlType_Amr_SidFirstInH; + /* ignored CMR/CMI pair */ + l1p->u.phDataReq.msgUnitParam.u8Buffer[1] = 0; + l1p->u.phDataReq.msgUnitParam.u8Buffer[2] = 0; + /* update length */ + data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, + subCh, u8BlockNbr, 3); + /* update FN so it can be checked by TCH silence + resume handler */ + lchan->tch.dtx.fn = LCHAN_FN_DUMMY; + } + } else if (dtx_dl_amr_enabled(lchan) && + lchan->tch.dtx.dl_amr_fsm->state == ST_FACCH) { + /* update FN so it can be checked by TCH silence + resume handler */ + lchan->tch.dtx.fn = LCHAN_FN_DUMMY; + } + else { + OSMO_ASSERT(msgb_l2len(msg) <= sizeof(l1p->u.phDataReq.msgUnitParam.u8Buffer)); + memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer, msg->l2h, + msgb_l2len(msg)); + } + LOGPFN(DL1P, LOGL_DEBUG, u32Fn, "PH-DATA.req(%s)\n", + osmo_hexdump(l1p->u.phDataReq.msgUnitParam.u8Buffer, + l1p->u.phDataReq.msgUnitParam.u8Size)); + } else { + /* empty frame */ + GsmL1_Prim_t *l1p = msgb_l1prim(l1msg); + + empty_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr); + } + + /* send message to DSP's queue */ + if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], l1msg) != 0) { + LOGPFN(DL1P, LOGL_ERROR, u32Fn, "MQ_L1_WRITE queue full. Dropping msg.\n"); + msgb_free(l1msg); + } else + dtx_int_signal(lchan); + + if (dtx_recursion(lchan)) + ph_data_req(trx, msg, l1sap, true); + return 0; +} + +static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap, bool use_cache, bool marker) +{ + struct femtol1_hdl *fl1 = trx_femtol1_hdl(trx); + struct gsm_lchan *lchan; + uint32_t u32Fn; + uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi; + uint8_t chan_nr; + GsmL1_Prim_t *l1p; + struct msgb *nmsg = NULL; + int rc = -1; + + chan_nr = l1sap->u.tch.chan_nr; + u32Fn = l1sap->u.tch.fn; + u8Tn = L1SAP_CHAN2TS(chan_nr); + u8BlockNbr = (u32Fn % 13) >> 2; + if (L1SAP_IS_CHAN_TCHH(chan_nr)) { + subCh = L1SAP_CHAN2SS_TCHH(chan_nr); + sapi = GsmL1_Sapi_TchH; + } else { + subCh = 0x1f; + sapi = GsmL1_Sapi_TchF; + } + + lchan = get_lchan_by_chan_nr(trx, chan_nr); + + /* create new message and fill data */ + if (msg) { + msgb_pull(msg, sizeof(*l1sap)); + /* create new message */ + nmsg = l1p_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + l1p = msgb_l1prim(nmsg); + rc = l1if_tch_encode(lchan, + l1p->u.phDataReq.msgUnitParam.u8Buffer, + &l1p->u.phDataReq.msgUnitParam.u8Size, + msg->data, msg->len, u32Fn, use_cache, + l1sap->u.tch.marker); + if (rc < 0) { + /* no data encoded for L1: smth will be generated below */ + msgb_free(nmsg); + nmsg = NULL; + } + } + + /* no message/data, we might generate an empty traffic msg or re-send + cached SID in case of DTX */ + if (!nmsg) + nmsg = gen_empty_tch_msg(lchan, u32Fn); + + /* no traffic message, we generate an empty msg */ + if (!nmsg) { + nmsg = l1p_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + } + + l1p = msgb_l1prim(nmsg); + + /* if we provide data, or if data is already in nmsg */ + if (l1p->u.phDataReq.msgUnitParam.u8Size) { + /* data request */ + data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, + u8BlockNbr, + l1p->u.phDataReq.msgUnitParam.u8Size); + } else { + /* empty frame */ + if (trx->bts->dtxd && trx != trx->bts->c0) + lchan->tch.dtx.dl_active = true; + empty_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr); + } + /* send message to DSP's queue */ + osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], nmsg); + if (dtx_is_first_p1(lchan)) + dtx_dispatch(lchan, E_FIRST); + else + dtx_int_signal(lchan); + + if (dtx_recursion(lchan)) /* DTX: send voice after ONSET was sent */ + return ph_tch_req(trx, l1sap->oph.msg, l1sap, true, false); + + return 0; +} + +static int mph_info_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap) +{ + struct femtol1_hdl *fl1 = trx_femtol1_hdl(trx); + uint8_t chan_nr; + struct gsm_lchan *lchan; + int rc = 0; + + switch (l1sap->u.info.type) { + case PRIM_INFO_ACT_CIPH: + chan_nr = l1sap->u.info.u.ciph_req.chan_nr; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + if (l1sap->u.info.u.ciph_req.uplink) { + l1if_set_ciphering(fl1, lchan, 0); + lchan->ciph_state = LCHAN_CIPH_RX_REQ; + } + if (l1sap->u.info.u.ciph_req.downlink) { + l1if_set_ciphering(fl1, lchan, 1); + lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ; + } + if (l1sap->u.info.u.ciph_req.downlink + && l1sap->u.info.u.ciph_req.uplink) + lchan->ciph_state = LCHAN_CIPH_RXTX_REQ; + break; + case PRIM_INFO_ACTIVATE: + case PRIM_INFO_DEACTIVATE: + case PRIM_INFO_MODIFY: + chan_nr = l1sap->u.info.u.act_req.chan_nr; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + if (l1sap->u.info.type == PRIM_INFO_ACTIVATE) + l1if_rsl_chan_act(lchan); + else if (l1sap->u.info.type == PRIM_INFO_MODIFY) { + if (lchan->ho.active == HANDOVER_WAIT_FRAME) + l1if_rsl_chan_mod(lchan); + else + l1if_rsl_mode_modify(lchan); + } else if (l1sap->u.info.u.act_req.sacch_only) + l1if_rsl_deact_sacch(lchan); + else + l1if_rsl_chan_rel(lchan); + break; + default: + LOGP(DL1C, LOGL_NOTICE, "unknown MPH-INFO.req %d\n", + l1sap->u.info.type); + rc = -EINVAL; + } + + return rc; +} + +/* primitive from common part */ +int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) +{ + struct msgb *msg = l1sap->oph.msg; + int rc = 0; + + /* called functions MUST NOT take ownership of msgb, as it is + * free()d below */ + switch (OSMO_PRIM_HDR(&l1sap->oph)) { + case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST): + rc = ph_data_req(trx, msg, l1sap, false); + break; + case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST): + rc = ph_tch_req(trx, msg, l1sap, false, l1sap->u.tch.marker); + break; + case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST): + rc = mph_info_req(trx, msg, l1sap); + break; + default: + LOGP(DL1C, LOGL_NOTICE, "unknown prim %d op %d\n", + l1sap->oph.primitive, l1sap->oph.operation); + rc = -EINVAL; + } + + msgb_free(msg); + + return rc; +} + +static int handle_mph_time_ind(struct femtol1_hdl *fl1, + GsmL1_MphTimeInd_t *time_ind, + struct msgb *msg) +{ + struct gsm_bts_trx *trx = femtol1_hdl_trx(fl1); + struct gsm_bts *bts = trx->bts; + struct osmo_phsap_prim l1sap; + uint32_t fn; + + /* increment the primitive count for the alive timer */ + fl1->alive_prim_cnt++; + + /* ignore every time indication, except for c0 */ + if (trx != bts->c0) { + msgb_free(msg); + return 0; + } + + fn = time_ind->u32Fn; + + memset(&l1sap, 0, sizeof(l1sap)); + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, + PRIM_OP_INDICATION, NULL); + l1sap.u.info.type = PRIM_INFO_TIME; + l1sap.u.info.u.time_ind.fn = fn; + + msgb_free(msg); + + return l1sap_up(trx, &l1sap); +} + +static enum gsm_phys_chan_config pick_pchan(struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_PDCH: + if (ts->flags & TS_F_PDCH_ACTIVE) + return GSM_PCHAN_PDCH; + return GSM_PCHAN_TCH_F; + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + return ts->dyn.pchan_is; + default: + return ts->pchan; + } +} + +static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts, + GsmL1_Sapi_t sapi, GsmL1_SubCh_t subCh, + uint8_t u8Tn, uint32_t u32Fn) +{ + uint8_t cbits = 0; + enum gsm_phys_chan_config pchan = pick_pchan(ts); + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH); + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH); + + switch (sapi) { + case GsmL1_Sapi_Bcch: + cbits = 0x10; + break; + case GsmL1_Sapi_Sacch: + switch(pchan) { + case GSM_PCHAN_TCH_F: + cbits = 0x01; + break; + case GSM_PCHAN_TCH_H: + cbits = 0x02 + subCh; + break; + case GSM_PCHAN_CCCH_SDCCH4: + cbits = 0x04 + subCh; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + cbits = 0x08 + subCh; + break; + default: + LOGP(DL1C, LOGL_ERROR, "SACCH for pchan %d?\n", + pchan); + return 0; + } + break; + case GsmL1_Sapi_Sdcch: + switch(pchan) { + case GSM_PCHAN_CCCH_SDCCH4: + cbits = 0x04 + subCh; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + cbits = 0x08 + subCh; + break; + default: + LOGP(DL1C, LOGL_ERROR, "SDCCH for pchan %d?\n", + pchan); + return 0; + } + break; + case GsmL1_Sapi_Agch: + case GsmL1_Sapi_Pch: + cbits = 0x12; + break; + case GsmL1_Sapi_Pdtch: + case GsmL1_Sapi_Pacch: + switch(pchan) { + case GSM_PCHAN_PDCH: + cbits = 0x01; + break; + default: + LOGP(DL1C, LOGL_ERROR, "PDTCH for pchan %d?\n", + pchan); + return 0; + } + break; + case GsmL1_Sapi_TchF: + cbits = 0x01; + break; + case GsmL1_Sapi_TchH: + cbits = 0x02 + subCh; + break; + case GsmL1_Sapi_FacchF: + cbits = 0x01; + break; + case GsmL1_Sapi_FacchH: + cbits = 0x02 + subCh; + break; + case GsmL1_Sapi_Ptcch: + if (!L1SAP_IS_PTCCH(u32Fn)) { + LOGP(DL1C, LOGL_FATAL, "Not expecting PTCCH at frame " + "number other than 12, got it at %u (%u). " + "Please fix!\n", u32Fn % 52, u32Fn); + abort(); + } + switch(pchan) { + case GSM_PCHAN_PDCH: + cbits = 0x01; + break; + default: + LOGP(DL1C, LOGL_ERROR, "PTCCH for pchan %d?\n", + pchan); + return 0; + } + break; + default: + return 0; + } + + /* not reached due to default case above */ + return (cbits << 3) | u8Tn; +} + +static int handle_ph_readytosend_ind(struct femtol1_hdl *fl1, + GsmL1_PhReadyToSendInd_t *rts_ind, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = femtol1_hdl_trx(fl1); + struct gsm_bts *bts = trx->bts; + struct msgb *resp_msg; + GsmL1_PhDataReq_t *data_req; + GsmL1_MsgUnitParam_t *msu_param; + struct gsm_time g_time; + uint32_t t3p; + int rc; + struct osmo_phsap_prim *l1sap; + uint8_t chan_nr, link_id; + uint32_t fn; + + /* check if primitive should be handled by common part */ + chan_nr = chan_nr_by_sapi(&trx->ts[rts_ind->u8Tn], rts_ind->sapi, + rts_ind->subCh, rts_ind->u8Tn, rts_ind->u32Fn); + if (chan_nr) { + fn = rts_ind->u32Fn; + if (rts_ind->sapi == GsmL1_Sapi_Sacch) + link_id = LID_SACCH; + else + link_id = LID_DEDIC; + /* recycle the msgb and use it for the L1 primitive, + * which means that we (or our caller) must not free it */ + rc = msgb_trim(l1p_msg, sizeof(*l1sap)); + if (rc < 0) + MSGB_ABORT(l1p_msg, "No room for primitive\n"); + l1sap = msgb_l1sap_prim(l1p_msg); + if (rts_ind->sapi == GsmL1_Sapi_TchF + || rts_ind->sapi == GsmL1_Sapi_TchH) { + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH_RTS, + PRIM_OP_INDICATION, l1p_msg); + l1sap->u.tch.chan_nr = chan_nr; + l1sap->u.tch.fn = fn; + } else { + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RTS, + PRIM_OP_INDICATION, l1p_msg); + l1sap->u.data.link_id = link_id; + l1sap->u.data.chan_nr = chan_nr; + l1sap->u.data.fn = fn; + } + + return l1sap_up(trx, l1sap); + } + + gsm_fn2gsmtime(&g_time, rts_ind->u32Fn); + + DEBUGPGT(DL1P, &g_time, "Rx PH-RTS.ind SAPI=%s\n", + get_value_string(femtobts_l1sapi_names, rts_ind->sapi)); + + /* in all other cases, we need to allocate a new PH-DATA.ind + * primitive msgb and start to fill it */ + resp_msg = l1p_msgb_alloc(); + data_req = data_req_from_rts_ind(msgb_l1prim(resp_msg), rts_ind); + msu_param = &data_req->msgUnitParam; + + /* set default size */ + msu_param->u8Size = GSM_MACBLOCK_LEN; + + switch (rts_ind->sapi) { + case GsmL1_Sapi_Sch: + /* compute T3prime */ + t3p = (g_time.t3 - 1) / 10; + /* fill SCH burst with data */ + msu_param->u8Size = 4; + msu_param->u8Buffer[0] = (bts->bsic << 2) | (g_time.t1 >> 9); + msu_param->u8Buffer[1] = (g_time.t1 >> 1); + msu_param->u8Buffer[2] = (g_time.t1 << 7) | (g_time.t2 << 2) | (t3p >> 1); + msu_param->u8Buffer[3] = (t3p & 1); + break; + case GsmL1_Sapi_Prach: + goto empty_frame; + break; + case GsmL1_Sapi_Cbch: + /* get them from bts->si_buf[] */ + bts_cbch_get(bts, msu_param->u8Buffer, &g_time); + break; + default: + memcpy(msu_param->u8Buffer, fill_frame, GSM_MACBLOCK_LEN); + break; + } +tx: + + /* transmit */ + if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], resp_msg) != 0) { + LOGPGT(DL1C, LOGL_ERROR, &g_time, "MQ_L1_WRITE queue full. Dropping msg.\n"); + msgb_free(resp_msg); + } + + /* free the msgb, as we have not handed it to l1sap and thus + * need to release its memory */ + msgb_free(l1p_msg); + return 0; + +empty_frame: + /* in case we decide to send an empty frame... */ + empty_req_from_rts_ind(msgb_l1prim(resp_msg), rts_ind); + + goto tx; +} + +static void dump_meas_res(int ll, GsmL1_MeasParam_t *m) +{ + LOGPC(DL1C, ll, ", Meas: RSSI %-3.2f dBm, Qual %-3.2f dB, " + "BER %-3.2f, Timing %d\n", m->fRssi, m->fLinkQuality, + m->fBer, m->i16BurstTiming); +} + +static int process_meas_res(struct gsm_bts_trx *trx, uint8_t chan_nr, + uint32_t fn, GsmL1_MeasParam_t *m) +{ + struct osmo_phsap_prim l1sap; + memset(&l1sap, 0, sizeof(l1sap)); + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, + PRIM_OP_INDICATION, NULL); + l1sap.u.info.type = PRIM_INFO_MEAS; + l1sap.u.info.u.meas_ind.chan_nr = chan_nr; + l1sap.u.info.u.meas_ind.ta_offs_256bits = m->i16BurstTiming * 64; + l1sap.u.info.u.meas_ind.ber10k = (unsigned int) (m->fBer * 10000); + l1sap.u.info.u.meas_ind.inv_rssi = (uint8_t) (m->fRssi * -1); + l1sap.u.info.u.meas_ind.fn = fn; + + /* l1sap wants to take msgb ownership. However, as there is no + * msg, it will msgb_free(l1sap.oph.msg == NULL) */ + return l1sap_up(trx, &l1sap); +} + +static int handle_ph_data_ind(struct femtol1_hdl *fl1, GsmL1_PhDataInd_t *data_ind, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = femtol1_hdl_trx(fl1); + uint8_t chan_nr, link_id; + struct msgb *sap_msg; + struct osmo_phsap_prim *l1sap; + uint32_t fn; + struct gsm_time g_time; + int rc = 0; + + chan_nr = chan_nr_by_sapi(&trx->ts[data_ind->u8Tn], data_ind->sapi, + data_ind->subCh, data_ind->u8Tn, data_ind->u32Fn); + if (!chan_nr) { + LOGPFN(DL1C, LOGL_ERROR, data_ind->u32Fn, "PH-DATA-INDICATION for unknown sapi %s (%d)\n", + get_value_string(femtobts_l1sapi_names, data_ind->sapi), data_ind->sapi); + msgb_free(l1p_msg); + return ENOTSUP; + } + fn = data_ind->u32Fn; + link_id = (data_ind->sapi == GsmL1_Sapi_Sacch) ? LID_SACCH : LID_DEDIC; + + process_meas_res(trx, chan_nr, fn, &data_ind->measParam); + + gsm_fn2gsmtime(&g_time, fn); + + DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind %s (hL2 %08x): %s\n", + get_value_string(femtobts_l1sapi_names, data_ind->sapi), data_ind->hLayer2, + osmo_hexdump(data_ind->msgUnitParam.u8Buffer, data_ind->msgUnitParam.u8Size)); + dump_meas_res(LOGL_DEBUG, &data_ind->measParam); + + /* check for TCH */ + if (data_ind->sapi == GsmL1_Sapi_TchF + || data_ind->sapi == GsmL1_Sapi_TchH) { + /* TCH speech frame handling */ + rc = l1if_tch_rx(trx, chan_nr, l1p_msg); + msgb_free(l1p_msg); + return rc; + } + + /* fill L1SAP header */ + sap_msg = l1sap_msgb_alloc(data_ind->msgUnitParam.u8Size); + l1sap = msgb_l1sap_prim(sap_msg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA, + PRIM_OP_INDICATION, sap_msg); + l1sap->u.data.link_id = link_id; + l1sap->u.data.chan_nr = chan_nr; + l1sap->u.data.fn = fn; + l1sap->u.data.rssi = (int8_t) (data_ind->measParam.fRssi); + if (!pcu_direct) { /* FIXME: if pcu_direct=1, then this is not set, what to do in pcu_tx_data_ind() in this case ?*/ + l1sap->u.data.ber10k = data_ind->measParam.fBer * 10000; + l1sap->u.data.ta_offs_256bits = data_ind->measParam.i16BurstTiming * 64; + l1sap->u.data.lqual_cb = data_ind->measParam.fLinkQuality * 10; + } + /* copy data from L1 primitive to L1SAP primitive */ + sap_msg->l2h = msgb_put(sap_msg, data_ind->msgUnitParam.u8Size); + memcpy(sap_msg->l2h, data_ind->msgUnitParam.u8Buffer, + data_ind->msgUnitParam.u8Size); + + + msgb_free(l1p_msg); + + return l1sap_up(trx, l1sap); +} + +static int handle_ph_ra_ind(struct femtol1_hdl *fl1, GsmL1_PhRaInd_t *ra_ind, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = femtol1_hdl_trx(fl1); + struct gsm_bts *bts = trx->bts; + struct gsm_lchan *lchan; + struct osmo_phsap_prim *l1sap; + int rc; + struct ph_rach_ind_param rach_ind_param; + + /* FIXME: this should be deprecated/obsoleted as it bypasses rach.busy counting */ + if (ra_ind->measParam.fLinkQuality < bts->min_qual_rach) { + msgb_free(l1p_msg); + return 0; + } + + dump_meas_res(LOGL_DEBUG, &ra_ind->measParam); + + if ((ra_ind->msgUnitParam.u8Size != 1) && + (ra_ind->msgUnitParam.u8Size != 2)) { + LOGPFN(DL1C, LOGL_ERROR, ra_ind->u32Fn, "PH-RACH-INDICATION has %d bits\n", + ra_ind->sapi); + msgb_free(l1p_msg); + return 0; + } + + /* We need to evaluate ra_ind before below msgb_trim(), since that invalidates *ra_ind. */ + rach_ind_param = (struct ph_rach_ind_param) { + /* .chan_nr set below */ + /* .ra set below */ + .acc_delay = 0, + .fn = ra_ind->u32Fn, + /* .is_11bit set below */ + /* .burst_type set below */ + .rssi = (int8_t) ra_ind->measParam.fRssi, + .ber10k = (unsigned int) (ra_ind->measParam.fBer * 10000.0), + .acc_delay_256bits = ra_ind->measParam.i16BurstTiming * 64, + }; + + lchan = l1if_hLayer_to_lchan(trx, ra_ind->hLayer2); + if (!lchan || lchan->ts->pchan == GSM_PCHAN_CCCH || + lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4 || + lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH) + rach_ind_param.chan_nr = 0x88; + else + rach_ind_param.chan_nr = gsm_lchan2chan_nr(lchan); + + if (ra_ind->msgUnitParam.u8Size == 2) { + uint16_t temp; + uint16_t ra = ra_ind->msgUnitParam.u8Buffer[0]; + ra = ra << 3; + temp = (ra_ind->msgUnitParam.u8Buffer[1] & 0x7); + ra = ra | temp; + rach_ind_param.is_11bit = 1; + rach_ind_param.ra = ra; + } else { + rach_ind_param.is_11bit = 0; + rach_ind_param.ra = ra_ind->msgUnitParam.u8Buffer[0]; + } + + /* the old legacy full-bits acc_delay cannot express negative values */ + if (ra_ind->measParam.i16BurstTiming > 0) + rach_ind_param.acc_delay = ra_ind->measParam.i16BurstTiming >> 2; + + /* mapping of the burst type, the values are specific to osmo-bts-sysmo */ + switch (ra_ind->burstType) { + case GsmL1_BurstType_Access_0: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_ACCESS_0; + break; + case GsmL1_BurstType_Access_1: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_ACCESS_1; + break; + case GsmL1_BurstType_Access_2: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_ACCESS_2; + break; + default: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_NONE; + break; + } + + /* msgb_trim() invalidates ra_ind, make that abundantly clear: */ + ra_ind = NULL; + rc = msgb_trim(l1p_msg, sizeof(*l1sap)); + if (rc < 0) + MSGB_ABORT(l1p_msg, "No room for primitive data\n"); + l1sap = msgb_l1sap_prim(l1p_msg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, + l1p_msg); + l1sap->u.rach_ind = rach_ind_param; + + return l1sap_up(trx, l1sap); +} + +/* handle any random indication from the L1 */ +static int l1if_handle_ind(struct femtol1_hdl *fl1, struct msgb *msg) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(msg); + int rc = 0; + + /* all the below called functions must take ownership of the msgb */ + switch (l1p->id) { + case GsmL1_PrimId_MphTimeInd: + rc = handle_mph_time_ind(fl1, &l1p->u.mphTimeInd, msg); + break; + case GsmL1_PrimId_MphSyncInd: + msgb_free(msg); + break; + case GsmL1_PrimId_PhConnectInd: + msgb_free(msg); + break; + case GsmL1_PrimId_PhReadyToSendInd: + rc = handle_ph_readytosend_ind(fl1, &l1p->u.phReadyToSendInd, + msg); + break; + case GsmL1_PrimId_PhDataInd: + rc = handle_ph_data_ind(fl1, &l1p->u.phDataInd, msg); + break; + case GsmL1_PrimId_PhRaInd: + rc = handle_ph_ra_ind(fl1, &l1p->u.phRaInd, msg); + break; + default: + msgb_free(msg); + } + + return rc; +} + +static inline int is_prim_compat(GsmL1_Prim_t *l1p, struct wait_l1_conf *wlc) +{ + if (wlc->is_sys_prim != 0) + return 0; + if (l1p->id != wlc->conf_prim_id) + return 0; + if (l1p_get_hLayer3(l1p) != wlc->conf_hLayer3) + return 0; + return 1; +} + +int l1if_handle_l1prim(int wq, struct femtol1_hdl *fl1h, struct msgb *msg) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(msg); + struct wait_l1_conf *wlc; + int rc; + + switch (l1p->id) { + case GsmL1_PrimId_MphTimeInd: + /* silent, don't clog the log file */ + break; + default: + LOGP(DL1P, LOGL_DEBUG, "Rx L1 prim %s on queue %d\n", + get_value_string(femtobts_l1prim_names, l1p->id), wq); + } + + /* check if this is a resposne to a sync-waiting request */ + llist_for_each_entry(wlc, &fl1h->wlc_list, list) { + if (is_prim_compat(l1p, wlc)) { + llist_del(&wlc->list); + if (wlc->cb) { + /* call-back function must take + * ownership of msgb */ + rc = wlc->cb(femtol1_hdl_trx(fl1h), msg, + wlc->cb_data); + } else { + rc = 0; + msgb_free(msg); + } + release_wlc(wlc); + return rc; + } + } + + /* if we reach here, it is not a Conf for a pending Req */ + return l1if_handle_ind(fl1h, msg); +} + +int l1if_handle_sysprim(struct femtol1_hdl *fl1h, struct msgb *msg) +{ + SuperFemto_Prim_t *sysp = msgb_sysprim(msg); + struct wait_l1_conf *wlc; + int rc; + + LOGP(DL1P, LOGL_DEBUG, "Rx SYS prim %s\n", + get_value_string(femtobts_sysprim_names, sysp->id)); + + /* check if this is a resposne to a sync-waiting request */ + llist_for_each_entry(wlc, &fl1h->wlc_list, list) { + /* the limitation here is that we cannot have multiple callers + * sending the same primitive */ + if (wlc->is_sys_prim && sysp->id == wlc->conf_prim_id) { + llist_del(&wlc->list); + if (wlc->cb) { + /* call-back function must take + * ownership of msgb */ + rc = wlc->cb(femtol1_hdl_trx(fl1h), msg, + wlc->cb_data); + } else { + rc = 0; + msgb_free(msg); + } + release_wlc(wlc); + return rc; + } + } + /* if we reach here, it is not a Conf for a pending Req */ + return l1if_handle_ind(fl1h, msg); +} + +static int activate_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + SuperFemto_Prim_t *sysp = msgb_sysprim(resp); + GsmL1_Status_t status; + int on = 0; + unsigned int i; + + if (sysp->id == SuperFemto_PrimId_ActivateRfCnf) + on = 1; + + if (on) + status = sysp->u.activateRfCnf.status; + else + status = sysp->u.deactivateRfCnf.status; + + LOGP(DL1C, LOGL_INFO, "Rx RF-%sACT.conf (status=%s)\n", on ? "" : "DE", + get_value_string(femtobts_l1status_names, status)); + + + if (on) { + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_FATAL, "RF-ACT.conf with status %s\n", + get_value_string(femtobts_l1status_names, status)); + bts_shutdown(trx->bts, "RF-ACT failure"); + } else + bts_update_status(BTS_STATUS_RF_ACTIVE, 1); + + /* signal availability */ + oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK); + oml_mo_tx_sw_act_rep(&trx->mo); + oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK); + oml_mo_tx_sw_act_rep(&trx->bb_transc.mo); + + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) + oml_mo_state_chg(&trx->ts[i].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY); + } else { + bts_update_status(BTS_STATUS_RF_ACTIVE, 0); + oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE); + oml_mo_state_chg(&trx->bb_transc.mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE); + } + + msgb_free(resp); + + return 0; +} + +int get_clk_cal(struct femtol1_hdl *hdl) +{ +#ifdef FEMTOBTS_API_VERSION + return hdl->clk_cal; +#else + switch (hdl->clk_src) { + case SuperFemto_ClkSrcId_Ocxo: + case SuperFemto_ClkSrcId_Tcxo: + /* only for those on-board clocks it makes sense to use + * the calibration value */ + return hdl->clk_cal; + default: + /* external clocks like GPS are taken 1:1 without any + * modification by a local calibration value */ + LOGP(DL1C, LOGL_INFO, "Ignoring Clock Calibration for " + "selected %s clock\n", + get_value_string(femtobts_clksrc_names, hdl->clk_src)); + return 0; + } +#endif +} + +/* + * RevC was the last HW revision without an external + * attenuator. Check for that. + */ +static int has_external_atten(struct femtol1_hdl *hdl) +{ + /* older version doesn't have an attenuator */ + return hdl->hw_info.ver_major > 2; +} + +/* activate or de-activate the entire RF-Frontend */ +int l1if_activate_rf(struct femtol1_hdl *hdl, int on) +{ + struct msgb *msg = sysp_msgb_alloc(); + SuperFemto_Prim_t *sysp = msgb_sysprim(msg); + struct gsm_bts_trx *trx = hdl->phy_inst->trx; + + if (on) { + sysp->id = SuperFemto_PrimId_ActivateRfReq; +#ifdef HW_SYSMOBTS_V1 + sysp->u.activateRfReq.u12ClkVc = get_clk_cal(hdl); +#else +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(0,2,0) + sysp->u.activateRfReq.timing.u8TimSrc = 1; /* Master */ +#endif /* 0.2.0 */ + sysp->u.activateRfReq.msgq.u8UseTchMsgq = 0; + sysp->u.activateRfReq.msgq.u8UsePdtchMsgq = pcu_direct; + /* Use clock from OCXO or whatever source is configured */ +#if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,1,0) + sysp->u.activateRfReq.rfTrx.u8ClkSrc = hdl->clk_src; +#else + sysp->u.activateRfReq.rfTrx.clkSrc = hdl->clk_src; +#endif /* 2.1.0 */ + sysp->u.activateRfReq.rfTrx.iClkCor = get_clk_cal(hdl); +#if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,4,0) +#if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,1,0) + sysp->u.activateRfReq.rfRx.u8ClkSrc = hdl->clk_src; +#else + sysp->u.activateRfReq.rfRx.clkSrc = hdl->clk_src; +#endif /* 2.1.0 */ + sysp->u.activateRfReq.rfRx.iClkCor = get_clk_cal(hdl); +#endif /* API 2.4.0 */ +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,2,0) + if (has_external_atten(hdl)) { + LOGP(DL1C, LOGL_INFO, "Using external attenuator.\n"); + sysp->u.activateRfReq.rfTrx.u8UseExtAtten = 1; + sysp->u.activateRfReq.rfTrx.fMaxTxPower = + (float) get_p_trxout_target_mdBm(trx, 0) / 1000; + } +#endif /* 2.2.0 */ +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,8,1) + /* maximum cell size in quarter-bits, 90 == 12.456 km */ + sysp->u.activateRfReq.u8MaxCellSize = 90; +#endif +#endif /* !HW_SYSMOBTS_V1 */ + } else { + sysp->id = SuperFemto_PrimId_DeactivateRfReq; + } + + return l1if_req_compl(hdl, msg, activate_rf_compl_cb, NULL); +} + +static void mute_handle_ts(struct gsm_bts_trx_ts *ts, int is_muted) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ts->lchan); i++) { + struct gsm_lchan *lchan = &ts->lchan[i]; + + if (!is_muted) + continue; + + if (lchan->state != LCHAN_S_ACTIVE) + continue; + + /* skip channels that might be active for another reason */ + if (lchan->type == GSM_LCHAN_CCCH) + continue; + if (lchan->type == GSM_LCHAN_PDTCH) + continue; + + if (lchan->s <= 0) + continue; + + lchan->s = 0; + rsl_tx_conn_fail(lchan, RSL_ERR_RADIO_LINK_FAIL); + } +} + +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0) +static int mute_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); + SuperFemto_Prim_t *sysp = msgb_sysprim(resp); + GsmL1_Status_t status; + + status = sysp->u.muteRfCnf.status; + + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_ERROR, "Rx RF-MUTE.conf with status %s\n", + get_value_string(femtobts_l1status_names, status)); + oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 0); + } else { + int i; + + LOGP(DL1C, LOGL_INFO, "Rx RF-MUTE.conf with status=%s\n", + get_value_string(femtobts_l1status_names, status)); + bts_update_status(BTS_STATUS_RF_MUTE, fl1h->last_rf_mute[0]); + oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 1); + + osmo_static_assert( + ARRAY_SIZE(trx->ts) >= ARRAY_SIZE(fl1h->last_rf_mute), + ts_array_size); + + for (i = 0; i < ARRAY_SIZE(fl1h->last_rf_mute); ++i) + mute_handle_ts(&trx->ts[i], fl1h->last_rf_mute[i]); + } + + msgb_free(resp); + + return 0; +} +#endif + +/* mute/unmute RF time slots */ +int l1if_mute_rf(struct femtol1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb) +{ + const uint8_t unmuted[8] = { 0,0,0,0,0,0,0,0 }; + struct msgb *msg = sysp_msgb_alloc(); + SuperFemto_Prim_t *sysp = msgb_sysprim(msg); + struct gsm_bts_trx *trx = hdl->phy_inst->trx; + int i; + + LOGP(DL1C, LOGL_INFO, "Tx RF-MUTE.req (%d, %d, %d, %d, %d, %d, %d, %d)\n", + mute[0], mute[1], mute[2], mute[3], + mute[4], mute[5], mute[6], mute[7] + ); + +#if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(3,6,0) + LOGP(DL1C, LOGL_ERROR, "RF-MUTE.req not supported by SuperFemto\n"); + msgb_free(msg); + /* always acknowledge an un-MUTE (which is a no-op if MUTE is not supported */ + if (!memcmp(mute, unmuted, ARRAY_SIZE(mute))) { + bts_update_status(BTS_STATUS_RF_MUTE, mute[0]); + oml_mo_rf_lock_chg(&trx->mo, mute, 1); + for (i = 0; i < ARRAY_SIZE(mute); ++i) + mute_handle_ts(&trx->ts[i], mute[i]); + return 0; + } + return -ENOTSUP; +#else + sysp->id = SuperFemto_PrimId_MuteRfReq; + memcpy(sysp->u.muteRfReq.u8Mute, mute, sizeof(sysp->u.muteRfReq.u8Mute)); + /* save for later use */ + memcpy(hdl->last_rf_mute, mute, sizeof(hdl->last_rf_mute)); + + return l1if_req_compl(hdl, msg, cb ? cb : mute_rf_compl_cb, NULL); +#endif /* < 3.6.0 */ +} + +/* call-back on arrival of DSP+FPGA version + band capability */ +static int info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + SuperFemto_Prim_t *sysp = msgb_sysprim(resp); + SuperFemto_SystemInfoCnf_t *sic = &sysp->u.systemInfoCnf; + struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); + int rc; + + fl1h->hw_info.dsp_version[0] = sic->dspVersion.major; + fl1h->hw_info.dsp_version[1] = sic->dspVersion.minor; + fl1h->hw_info.dsp_version[2] = sic->dspVersion.build; + + fl1h->hw_info.fpga_version[0] = sic->fpgaVersion.major; + fl1h->hw_info.fpga_version[1] = sic->fpgaVersion.minor; + fl1h->hw_info.fpga_version[2] = sic->fpgaVersion.build; + +#ifndef HW_SYSMOBTS_V1 + fl1h->hw_info.ver_major = sic->boardVersion.rev; + fl1h->hw_info.ver_minor = sic->boardVersion.option; +#endif + + LOGP(DL1C, LOGL_INFO, "DSP v%u.%u.%u, FPGA v%u.%u.%u\nn", + sic->dspVersion.major, sic->dspVersion.minor, + sic->dspVersion.build, sic->fpgaVersion.major, + sic->fpgaVersion.minor, sic->fpgaVersion.build); + +#ifdef HW_SYSMOBTS_V1 + if (sic->rfBand.gsm850) + fl1h->hw_info.band_support |= GSM_BAND_850; + if (sic->rfBand.gsm900) + fl1h->hw_info.band_support |= GSM_BAND_900; + if (sic->rfBand.dcs1800) + fl1h->hw_info.band_support |= GSM_BAND_1800; + if (sic->rfBand.pcs1900) + fl1h->hw_info.band_support |= GSM_BAND_1900; +#endif + + if (!(fl1h->hw_info.band_support & trx->bts->band)) + LOGP(DL1C, LOGL_FATAL, "BTS band %s not supported by hw\n", + gsm_band_name(trx->bts->band)); + + if (l1if_dsp_ver(fl1h) < L1_VER_SHIFT(5,3,3)) + fl1h->rtp_hr_jumble_needed = true; + + /* Request the activation */ + l1if_activate_rf(fl1h, 1); + +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,4,0) + /* load calibration tables (if we know their path) */ + rc = calib_load(fl1h); + if (rc < 0) + LOGP(DL1C, LOGL_ERROR, "Operating without calibration; " + "unable to load tables!\n"); +#else + LOGP(DL1C, LOGL_NOTICE, "Operating without calibration " + "as software was compiled against old header files\n"); +#endif + + msgb_free(resp); + + /* FIXME: clock related */ + return 0; +} + +/* request DSP+FPGA code versions + band capability */ +static int l1if_get_info(struct femtol1_hdl *hdl) +{ + struct msgb *msg = sysp_msgb_alloc(); + SuperFemto_Prim_t *sysp = msgb_sysprim(msg); + + sysp->id = SuperFemto_PrimId_SystemInfoReq; + + return l1if_req_compl(hdl, msg, info_compl_cb, NULL); +} + +static int reset_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); + SuperFemto_Prim_t *sysp = msgb_sysprim(resp); + GsmL1_Status_t status = sysp->u.layer1ResetCnf.status; + + LOGP(DL1C, LOGL_NOTICE, "Rx L1-RESET.conf (status=%s)\n", + get_value_string(femtobts_l1status_names, status)); + + msgb_free(resp); + + /* If we're coming out of reset .. */ + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_FATAL, "L1-RESET.conf with status %s\n", + get_value_string(femtobts_l1status_names, status)); + bts_shutdown(trx->bts, "L1-RESET failure"); + } + + /* as we cannot get the current DSP trace flags, we simply + * set them to zero (or whatever dsp_trace_f has been initialized to */ + l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f); + + /* obtain version information on DSP/FPGA and band capabilities */ + l1if_get_info(fl1h); + + return 0; +} + +int l1if_reset(struct femtol1_hdl *hdl) +{ + struct msgb *msg = sysp_msgb_alloc(); + SuperFemto_Prim_t *sysp = msgb_sysprim(msg); + sysp->id = SuperFemto_PrimId_Layer1ResetReq; + + return l1if_req_compl(hdl, msg, reset_compl_cb, NULL); +} + +/* set the trace flags within the DSP */ +int l1if_set_trace_flags(struct femtol1_hdl *hdl, uint32_t flags) +{ + struct msgb *msg = sysp_msgb_alloc(); + SuperFemto_Prim_t *sysp = msgb_sysprim(msg); + + LOGP(DL1C, LOGL_INFO, "Tx SET-TRACE-FLAGS.req (0x%08x)\n", + flags); + + sysp->id = SuperFemto_PrimId_SetTraceFlagsReq; + sysp->u.setTraceFlagsReq.u32Tf = flags; + + hdl->dsp_trace_f = flags; + + /* There is no confirmation we could wait for */ + if (osmo_wqueue_enqueue(&hdl->write_q[MQ_SYS_WRITE], msg) != 0) { + LOGP(DL1C, LOGL_ERROR, "MQ_SYS_WRITE queue full. Dropping msg\n"); + msgb_free(msg); + return -EAGAIN; + } + return 0; +} + +/* get those femtol1_hdl.hw_info elements that sre in EEPROM */ +static int get_hwinfo_eeprom(struct femtol1_hdl *fl1h) +{ + eeprom_SysInfo_t sysinfo; + int val, rc; + + rc = sysmobts_get_type(&val); + if (rc < 0) + return rc; + fl1h->hw_info.model_nr = val; + + rc = sysmobts_par_get_int(SYSMOBTS_PAR_MODEL_FLAGS, &val); + if (rc < 0) + return rc; + fl1h->hw_info.model_flags = val; + + rc = sysmobts_get_trx(&val); + if (rc < 0) + return rc; + fl1h->hw_info.trx_nr = val; + + rc = eeprom_ReadSysInfo(&sysinfo); + if (rc != EEPROM_SUCCESS) { + /* some early units don't yet have the EEPROM + * information structure */ + LOGP(DL1C, LOGL_ERROR, "Unable to read band support " + "from EEPROM, assuming all bands\n"); + fl1h->hw_info.band_support = GSM_BAND_850 | GSM_BAND_900 | GSM_BAND_1800 | GSM_BAND_1900; + return 0; + } + + if (sysinfo.u8GSM850) + fl1h->hw_info.band_support |= GSM_BAND_850; + if (sysinfo.u8GSM900) + fl1h->hw_info.band_support |= GSM_BAND_900; + if (sysinfo.u8DCS1800) + fl1h->hw_info.band_support |= GSM_BAND_1800; + if (sysinfo.u8PCS1900) + fl1h->hw_info.band_support |= GSM_BAND_1900; + + return 0; +} + +/* Set the clock calibration to the value read from the eeprom. */ +static void clk_cal_use_eeprom(struct femtol1_hdl *hdl) +{ + struct phy_instance *pinst = hdl->phy_inst; + eeprom_RfClockCal_t rf_clk; + int rc; + + if (!pinst->u.sysmobts.clk_use_eeprom) + return; + + rc = eeprom_ReadRfClockCal(&rf_clk); + if (rc != EEPROM_SUCCESS) { + LOGP(DL1C, LOGL_ERROR, "Failed to read from EEPROM.\n"); + return; + } + + hdl->clk_cal = rf_clk.iClkCor; + LOGP(DL1C, LOGL_NOTICE, + "Read clock calibration(%d) from EEPROM.\n", hdl->clk_cal); +} + +struct femtol1_hdl *l1if_open(struct phy_instance *pinst) +{ + struct femtol1_hdl *fl1h; + int rc; + +#ifndef HW_SYSMOBTS_V1 + LOGP(DL1C, LOGL_INFO, "sysmoBTSv2 L1IF compiled against API headers " + "v%u.%u.%u\n", SUPERFEMTO_API_VERSION >> 16, + (SUPERFEMTO_API_VERSION >> 8) & 0xff, + SUPERFEMTO_API_VERSION & 0xff); +#else + LOGP(DL1C, LOGL_INFO, "sysmoBTSv1 L1IF compiled against API headers " + "v%u.%u.%u\n", FEMTOBTS_API_VERSION >> 16, + (FEMTOBTS_API_VERSION >> 8) & 0xff, + FEMTOBTS_API_VERSION & 0xff); +#endif + + fl1h = talloc_zero(pinst, struct femtol1_hdl); + if (!fl1h) + return NULL; + INIT_LLIST_HEAD(&fl1h->wlc_list); + + fl1h->phy_inst = pinst; + fl1h->dsp_trace_f = pinst->u.sysmobts.dsp_trace_f; + fl1h->clk_src = pinst->u.sysmobts.clk_src; + fl1h->clk_cal = pinst->u.sysmobts.clk_cal; + clk_cal_use_eeprom(fl1h); + get_hwinfo_eeprom(fl1h); +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(2,1,0) + if (fl1h->clk_src == SuperFemto_ClkSrcId_None) { + if (fl1h->hw_info.model_nr == 2050) { + /* On the sysmoBTS 2050, we don't have an OCXO but + * start with the TCXO and will sync it with the PPS + * of the GPS in case there is a fix. */ + fl1h->clk_src = SuperFemto_ClkSrcId_Tcxo; + LOGP(DL1C, LOGL_INFO, "Clock source defaulting to GPS 1PPS " + "on sysmoBTS 2050\n"); + } else { + /* default clock source: OCXO */ + fl1h->clk_src = SuperFemto_ClkSrcId_Ocxo; + } + } +#else + if (fl1h->clk_src == SF_CLKSRC_NONE) + fl1h->clk_src = SF_CLKSRC_OCXO; +#endif + + rc = l1if_transport_open(MQ_SYS_WRITE, fl1h); + if (rc < 0) { + talloc_free(fl1h); + return NULL; + } + + rc = l1if_transport_open(MQ_L1_WRITE, fl1h); + if (rc < 0) { + l1if_transport_close(MQ_SYS_WRITE, fl1h); + talloc_free(fl1h); + return NULL; + } + + l1if_reset(fl1h); + + return fl1h; +} + +int l1if_close(struct femtol1_hdl *fl1h) +{ + l1if_transport_close(MQ_L1_WRITE, fl1h); + l1if_transport_close(MQ_SYS_WRITE, fl1h); + return 0; +} + +#ifdef HW_SYSMOBTS_V1 +int l1if_rf_clock_info_reset(struct femtol1_hdl *fl1h) +{ + LOGP(DL1C, LOGL_ERROR, "RfClock calibration not supported on v1 hw.\n"); + return -ENOTSUP; +} + +int l1if_rf_clock_info_correct(struct femtol1_hdl *fl1h) +{ + LOGP(DL1C, LOGL_ERROR, "RfClock calibration not supported on v1 hw.\n"); + return -ENOTSUP; +} + +#else +static int clock_reset_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + msgb_free(resp); + return 0; +} + +static int clock_setup_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + SuperFemto_Prim_t *sysp = msgb_sysprim(resp); + + if (sysp->u.rfClockSetupCnf.status != GsmL1_Status_Success) + LOGP(DL1C, LOGL_ERROR, "Rx RfClockSetupConf failed with: %d\n", + sysp->u.rfClockSetupCnf.status); + msgb_free(resp); + return 0; +} + +static int clock_correct_info_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); + SuperFemto_Prim_t *sysp = msgb_sysprim(resp); + + LOGP(DL1C, LOGL_NOTICE, + "RfClockInfo iClkCor=%d/clkSrc=%s Err=%d/ErrRes=%d/clkSrc=%s\n", + sysp->u.rfClockInfoCnf.rfTrx.iClkCor, + get_value_string(femtobts_clksrc_names, + sysp->u.rfClockInfoCnf.rfTrx.clkSrc), + sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErr, + sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes, + get_value_string(femtobts_clksrc_names, + sysp->u.rfClockInfoCnf.rfTrxClkCal.clkSrc)); + + if (sysp->u.rfClockInfoCnf.rfTrx.clkSrc == SuperFemto_ClkSrcId_GpsPps) { + LOGP(DL1C, LOGL_ERROR, + "Calibrating GPS against GPS doesn not make sense.\n"); + msgb_free(resp); + return -1; + } + + if (sysp->u.rfClockInfoCnf.rfTrxClkCal.clkSrc == SuperFemto_ClkSrcId_None) { + LOGP(DL1C, LOGL_ERROR, + "No reference clock set. Please reset first.\n"); + msgb_free(resp); + return -1; + } + + if (sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes == 0) { + LOGP(DL1C, LOGL_ERROR, + "Couldn't determine the clock difference.\n"); + msgb_free(resp); + return -1; + } + + fl1h->clk_cal = sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErr; + fl1h->phy_inst->u.sysmobts.clk_use_eeprom = 0; + msgb_free(resp); + + /* + * Let's reset the counter and this will lead to applying the + * new calibration. + */ + l1if_rf_clock_info_reset(fl1h); + + return 0; +} + +int l1if_rf_clock_info_reset(struct femtol1_hdl *fl1h) +{ + struct msgb *msg = sysp_msgb_alloc(); + SuperFemto_Prim_t *sysp = msgb_sysprim(msg); + + /* Set GPS/PPS as reference */ + sysp->id = SuperFemto_PrimId_RfClockSetupReq; + sysp->u.rfClockSetupReq.rfTrx.iClkCor = get_clk_cal(fl1h); + sysp->u.rfClockSetupReq.rfTrx.clkSrc = fl1h->clk_src; + sysp->u.rfClockSetupReq.rfTrxClkCal.clkSrc = SuperFemto_ClkSrcId_GpsPps; + l1if_req_compl(fl1h, msg, clock_setup_cb, NULL); + + /* Reset the error counters */ + msg = sysp_msgb_alloc(); + sysp = msgb_sysprim(msg); + + sysp->id = SuperFemto_PrimId_RfClockInfoReq; + sysp->u.rfClockInfoReq.u8RstClkCal = 1; + + return l1if_req_compl(fl1h, msg, clock_reset_cb, NULL); +} + +int l1if_rf_clock_info_correct(struct femtol1_hdl *fl1h) +{ + struct msgb *msg = sysp_msgb_alloc(); + SuperFemto_Prim_t *sysp = msgb_sysprim(msg); + + sysp->id = SuperFemto_PrimId_RfClockInfoReq; + sysp->u.rfClockInfoReq.u8RstClkCal = 0; + + return l1if_req_compl(fl1h, msg, clock_correct_info_cb, NULL); +} + +#endif + +static void set_power_param(struct trx_power_params *out, + int trx_p_max_out_dBm, + int int_pa_nominal_gain_dB) +{ + out->trx_p_max_out_mdBm = to_mdB(trx_p_max_out_dBm); + out->pa.nominal_gain_mdB = to_mdB(int_pa_nominal_gain_dB); +} + +static void fill_trx_power_params(struct gsm_bts_trx *trx, + struct phy_instance *pinst) +{ + struct femtol1_hdl *fl1h = pinst->u.sysmobts.hdl; + + switch (fl1h->hw_info.model_nr) { + case 1020: + set_power_param(&trx->power_params, 23, 10); + break; + case 1100: + set_power_param(&trx->power_params, 23, 17); + break; + case 2050: + set_power_param(&trx->power_params, 37, 0); + break; + default: + LOGP(DL1C, LOGL_NOTICE, "Unknown/Unsupported " + "sysmoBTS Model Number %u\n", + fl1h->hw_info.model_nr); + /* fall-through */ + case 0xffff: + /* sysmoBTS 1002 without any setting in EEPROM */ + LOGP(DL1C, LOGL_NOTICE, "Assuming 1002 for sysmoBTS " + "Model number %u\n", fl1h->hw_info.model_nr); + case 1002: + set_power_param(&trx->power_params, 23, 0); + } +} + + +int bts_model_phy_link_open(struct phy_link *plink) +{ + struct phy_instance *pinst = phy_instance_by_num(plink, 0); + struct femtol1_hdl *hdl; + struct gsm_bts *bts; + + OSMO_ASSERT(pinst); + + phy_link_state_set(plink, PHY_LINK_CONNECTING); + + pinst->u.sysmobts.hdl = l1if_open(pinst); + if (!pinst->u.sysmobts.hdl) { + LOGP(DL1C, LOGL_FATAL, "Cannot open L1 interface\n"); + return -EIO; + } + + fill_trx_power_params(pinst->trx, pinst); + + bts = pinst->trx->bts; + if (pinst->trx == bts->c0) { + int rc; + rc = get_p_max_out_mdBm(bts->c0); + if (rc < 0) { + LOGP(DL1C, LOGL_NOTICE, "Cannot determine nominal " + "transmit power. Assuming 23dBm.\n"); + } + bts->c0->nominal_power = rc; + } + + hdl = pinst->u.sysmobts.hdl; + osmo_strlcpy(bts->sub_model, sysmobts_model(hdl->hw_info.model_nr, hdl->hw_info.trx_nr), sizeof(bts->sub_model)); + snprintf(pinst->version, sizeof(pinst->version), "%u.%u dsp %u.%u.%u fpga %u.%u.%u", + hdl->hw_info.ver_major, hdl->hw_info.ver_minor, + hdl->hw_info.dsp_version[0], hdl->hw_info.dsp_version[1], hdl->hw_info.dsp_version[2], + hdl->hw_info.fpga_version[0], hdl->hw_info.fpga_version[1], hdl->hw_info.fpga_version[2]); + + phy_link_state_set(plink, PHY_LINK_CONNECTED); + + return 0; +} diff --git a/src/osmo-bts-sysmo/l1_if.h b/src/osmo-bts-sysmo/l1_if.h new file mode 100644 index 0000000..1b214be --- /dev/null +++ b/src/osmo-bts-sysmo/l1_if.h @@ -0,0 +1,171 @@ +#ifndef _FEMTO_L1_H +#define _FEMTO_L1_H + +#include +#include +#include +#include +#include + +#include + +#include + +#include + +enum { + MQ_SYS_READ, + MQ_L1_READ, +#ifndef HW_SYSMOBTS_V1 + MQ_TCH_READ, + MQ_PDTCH_READ, +#endif + _NUM_MQ_READ +}; + +enum { + MQ_SYS_WRITE, + MQ_L1_WRITE, +#ifndef HW_SYSMOBTS_V1 + MQ_TCH_WRITE, + MQ_PDTCH_WRITE, +#endif + _NUM_MQ_WRITE +}; + +struct calib_send_state { + const char *path; + int last_file_idx; +}; + +enum { + FIXUP_UNITILIAZED, + FIXUP_NEEDED, + FIXUP_NOT_NEEDED, +}; + +struct femtol1_hdl { + struct gsm_time gsm_time; + uint32_t hLayer1; /* handle to the L1 instance in the DSP */ + uint32_t dsp_trace_f; /* currently operational DSP trace flags */ + int clk_cal; + uint8_t clk_src; + struct llist_head wlc_list; + + struct phy_instance *phy_inst; /* Reference to PHY instance */ + + struct osmo_timer_list alive_timer; + unsigned int alive_prim_cnt; + + struct osmo_fd read_ofd[_NUM_MQ_READ]; /* osmo file descriptors */ + struct osmo_wqueue write_q[_NUM_MQ_WRITE]; + + struct { + /* from DSP/FPGA after L1 Init */ + uint8_t dsp_version[3]; + uint8_t fpga_version[3]; + uint32_t band_support; /* bitmask of GSM_BAND_* */ + uint8_t ver_major; + uint8_t ver_minor; + /* from EEPROM */ + uint16_t model_nr; + uint16_t model_flags; + uint8_t trx_nr; + } hw_info; + + int fixup_needed; + bool rtp_hr_jumble_needed; + + struct calib_send_state st; + + uint8_t last_rf_mute[8]; + + /* for l1_fwd */ + void *priv; +}; + + +#define L1_VER_SHIFT(x,y,z) ((x << 16) | (y << 8) | (z)) + +static inline uint32_t l1if_dsp_ver(struct femtol1_hdl *fl1h) +{ + const uint8_t *v = fl1h->hw_info.dsp_version; + return L1_VER_SHIFT(v[0], v[1], v[2]); +} + +static inline uint32_t l1if_fpga_ver(struct femtol1_hdl *fl1h) +{ + const uint8_t *v = fl1h->hw_info.fpga_version; + return L1_VER_SHIFT(v[0], v[1], v[2]); +} + +#define msgb_l1prim(msg) ((GsmL1_Prim_t *)(msg)->l1h) +#define msgb_sysprim(msg) ((SuperFemto_Prim_t *)(msg)->l1h) + +typedef int l1if_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, void *data); + +/* send a request primitive to the L1 and schedule completion call-back */ +int l1if_req_compl(struct femtol1_hdl *fl1h, struct msgb *msg, + l1if_compl_cb *cb, void *cb_data); +int l1if_gsm_req_compl(struct femtol1_hdl *fl1h, struct msgb *msg, + l1if_compl_cb *cb, void *cb_data); + +struct femtol1_hdl *l1if_open(struct phy_instance *pinst); +int l1if_close(struct femtol1_hdl *hdl); +int l1if_reset(struct femtol1_hdl *hdl); +int l1if_activate_rf(struct femtol1_hdl *hdl, int on); +int l1if_set_trace_flags(struct femtol1_hdl *hdl, uint32_t flags); +int l1if_set_txpower(struct femtol1_hdl *fl1h, float tx_power); +int l1if_mute_rf(struct femtol1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb); + +struct msgb *l1p_msgb_alloc(void); +struct msgb *sysp_msgb_alloc(void); + +uint32_t l1if_lchan_to_hLayer(struct gsm_lchan *lchan); +struct gsm_lchan *l1if_hLayer_to_lchan(struct gsm_bts_trx *trx, uint32_t hLayer); + +/* tch.c */ +int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len, + const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn, + bool use_cache, bool marker); +int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg); +int l1if_tch_fill(struct gsm_lchan *lchan, uint8_t *l1_buffer); +struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan, uint32_t fn); + +/* ciphering */ +int l1if_set_ciphering(struct femtol1_hdl *fl1h, + struct gsm_lchan *lchan, + int dir_downlink); + +/* channel control */ +int l1if_rsl_chan_act(struct gsm_lchan *lchan); +int l1if_rsl_chan_rel(struct gsm_lchan *lchan); +int l1if_rsl_chan_mod(struct gsm_lchan *lchan); +int l1if_rsl_deact_sacch(struct gsm_lchan *lchan); +int l1if_rsl_mode_modify(struct gsm_lchan *lchan); + +/* calibration loading */ +int calib_load(struct femtol1_hdl *fl1h); +int get_clk_cal(struct femtol1_hdl *hdl); + +/* on-line re-calibration */ +int l1if_rf_clock_info_reset(struct femtol1_hdl *fl1h); +int l1if_rf_clock_info_correct(struct femtol1_hdl *fl1h); + +/* public helpers for test */ +int bts_check_for_ciph_cmd(struct femtol1_hdl *fl1h, + struct msgb *msg, struct gsm_lchan *lchan); + +static inline struct femtol1_hdl *trx_femtol1_hdl(struct gsm_bts_trx *trx) +{ + struct phy_instance *pinst = trx_phy_instance(trx); + OSMO_ASSERT(pinst); + return pinst->u.sysmobts.hdl; +} + +static inline struct gsm_bts_trx *femtol1_hdl_trx(struct femtol1_hdl *fl1h) +{ + OSMO_ASSERT(fl1h->phy_inst); + return fl1h->phy_inst->trx; +} +#endif /* _FEMTO_L1_H */ diff --git a/src/osmo-bts-sysmo/l1_transp.h b/src/osmo-bts-sysmo/l1_transp.h new file mode 100644 index 0000000..b2967f9 --- /dev/null +++ b/src/osmo-bts-sysmo/l1_transp.h @@ -0,0 +1,14 @@ +#ifndef _FEMTOL1_TRANSPH_ +#define _FEMTOL1_TRANSPH_ + +#include + +/* functions a transport calls on arrival of primitive from BTS */ +int l1if_handle_l1prim(int wq, struct femtol1_hdl *fl1h, struct msgb *msg); +int l1if_handle_sysprim(struct femtol1_hdl *fl1h, struct msgb *msg); + +/* functions exported by a transport */ +int l1if_transport_open(int q, struct femtol1_hdl *fl1h); +int l1if_transport_close(int q, struct femtol1_hdl *fl1h); + +#endif /* _FEMTOL1_TRANSP_H */ diff --git a/src/osmo-bts-sysmo/l1_transp_fwd.c b/src/osmo-bts-sysmo/l1_transp_fwd.c new file mode 100644 index 0000000..87c230b --- /dev/null +++ b/src/osmo-bts-sysmo/l1_transp_fwd.c @@ -0,0 +1,152 @@ +/* Interface handler for Sysmocom L1 (forwarding) */ + +/* (C) 2011 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "femtobts.h" +#include "l1_if.h" +#include "l1_transp.h" +#include "l1_fwd.h" + +static const uint16_t fwd_udp_ports[] = { + [MQ_SYS_WRITE] = L1FWD_SYS_PORT, + [MQ_L1_WRITE] = L1FWD_L1_PORT, +#ifndef HW_SYSMOBTS_V1 + [MQ_TCH_WRITE] = L1FWD_TCH_PORT, + [MQ_PDTCH_WRITE]= L1FWD_PDTCH_PORT, +#endif +}; + +static int fwd_read_cb(struct osmo_fd *ofd) +{ + struct msgb *msg = msgb_alloc_headroom(SYSMOBTS_PRIM_SIZE, 128, "udp_rx"); + struct femtol1_hdl *fl1h = ofd->data; + int rc; + + if (!msg) + return -ENOMEM; + + msg->l1h = msg->data; + rc = read(ofd->fd, msg->l1h, msgb_tailroom(msg)); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "Short read from UDP\n"); + msgb_free(msg); + return rc; + } else if (rc == 0) { + LOGP(DL1C, LOGL_ERROR, "Len=0 from UDP\n"); + msgb_free(msg); + return rc; + } + msgb_put(msg, rc); + + if (ofd->priv_nr == MQ_SYS_WRITE) + rc = l1if_handle_sysprim(fl1h, msg); + else + rc = l1if_handle_l1prim(ofd->priv_nr, fl1h, msg); + + return rc; +} + +static int prim_write_cb(struct osmo_fd *ofd, struct msgb *msg) +{ + /* write to the fd */ + return write(ofd->fd, msg->l1h, msgb_l1len(msg)); +} + +int l1if_transport_open(int q, struct femtol1_hdl *fl1h) +{ + int rc; + char *bts_host = getenv("L1FWD_BTS_HOST"); + + switch (q) { + case MQ_L1_WRITE: + LOGP(DL1C, LOGL_INFO, "sizeof(GsmL1_Prim_t) = %zu\n", + sizeof(GsmL1_Prim_t)); + break; + case MQ_SYS_WRITE: + LOGP(DL1C, LOGL_INFO, "sizeof(SuperFemto_Prim_t) = %zu\n", + sizeof(SuperFemto_Prim_t)); + break; + } + + if (!bts_host) { + fprintf(stderr, "You have to set the L1FWD_BTS_HOST environment variable\n"); + exit(2); + } + + struct osmo_wqueue *wq = &fl1h->write_q[q]; + struct osmo_fd *ofd = &wq->bfd; + + osmo_wqueue_init(wq, 10); + wq->write_cb = prim_write_cb; + wq->read_cb = fwd_read_cb; + + ofd->data = fl1h; + ofd->priv_nr = q; + ofd->when |= BSC_FD_READ; + + rc = osmo_sock_init_ofd(ofd, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, + bts_host, fwd_udp_ports[q], + OSMO_SOCK_F_CONNECT); + if (rc < 0) + return rc; + + return 0; +} + +int l1if_transport_close(int q, struct femtol1_hdl *fl1h) +{ + struct osmo_wqueue *wq = &fl1h->write_q[q]; + struct osmo_fd *ofd = &wq->bfd; + + osmo_wqueue_clear(wq); + osmo_fd_unregister(ofd); + close(ofd->fd); + + return 0; +} diff --git a/src/osmo-bts-sysmo/l1_transp_hw.c b/src/osmo-bts-sysmo/l1_transp_hw.c new file mode 100644 index 0000000..01bc200 --- /dev/null +++ b/src/osmo-bts-sysmo/l1_transp_hw.c @@ -0,0 +1,329 @@ +/* Interface handler for Sysmocom L1 (real hardware) */ + +/* (C) 2011 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "femtobts.h" +#include "l1_if.h" +#include "l1_transp.h" + + +#ifdef HW_SYSMOBTS_V1 +#define DEV_SYS_DSP2ARM_NAME "/dev/msgq/femtobts_dsp2arm" +#define DEV_SYS_ARM2DSP_NAME "/dev/msgq/femtobts_arm2dsp" +#define DEV_L1_DSP2ARM_NAME "/dev/msgq/gsml1_dsp2arm" +#define DEV_L1_ARM2DSP_NAME "/dev/msgq/gsml1_arm2dsp" +#else +#define DEV_SYS_DSP2ARM_NAME "/dev/msgq/superfemto_dsp2arm" +#define DEV_SYS_ARM2DSP_NAME "/dev/msgq/superfemto_arm2dsp" +#define DEV_L1_DSP2ARM_NAME "/dev/msgq/gsml1_sig_dsp2arm" +#define DEV_L1_ARM2DSP_NAME "/dev/msgq/gsml1_sig_arm2dsp" + +#define DEV_TCH_DSP2ARM_NAME "/dev/msgq/gsml1_tch_dsp2arm" +#define DEV_TCH_ARM2DSP_NAME "/dev/msgq/gsml1_tch_arm2dsp" +#define DEV_PDTCH_DSP2ARM_NAME "/dev/msgq/gsml1_pdtch_dsp2arm" +#define DEV_PDTCH_ARM2DSP_NAME "/dev/msgq/gsml1_pdtch_arm2dsp" +#endif + +static const char *rd_devnames[] = { + [MQ_SYS_READ] = DEV_SYS_DSP2ARM_NAME, + [MQ_L1_READ] = DEV_L1_DSP2ARM_NAME, +#ifndef HW_SYSMOBTS_V1 + [MQ_TCH_READ] = DEV_TCH_DSP2ARM_NAME, + [MQ_PDTCH_READ] = DEV_PDTCH_DSP2ARM_NAME, +#endif +}; + +static const char *wr_devnames[] = { + [MQ_SYS_WRITE] = DEV_SYS_ARM2DSP_NAME, + [MQ_L1_WRITE] = DEV_L1_ARM2DSP_NAME, +#ifndef HW_SYSMOBTS_V1 + [MQ_TCH_WRITE] = DEV_TCH_ARM2DSP_NAME, + [MQ_PDTCH_WRITE]= DEV_PDTCH_ARM2DSP_NAME, +#endif +}; + +/* + * Make sure that all structs we read fit into the SYSMOBTS_PRIM_SIZE + */ +osmo_static_assert(sizeof(GsmL1_Prim_t) + 128 <= SYSMOBTS_PRIM_SIZE, l1_prim) +osmo_static_assert(sizeof(SuperFemto_Prim_t) + 128 <= SYSMOBTS_PRIM_SIZE, super_prim) + +static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what) +{ + struct osmo_wqueue *queue; + + queue = container_of(fd, struct osmo_wqueue, bfd); + + if (what & BSC_FD_READ) + queue->read_cb(fd); + + if (what & BSC_FD_EXCEPT) + queue->except_cb(fd); + + if (what & BSC_FD_WRITE) { + struct iovec iov[5]; + struct msgb *msg, *tmp; + int written, count = 0; + + fd->when &= ~BSC_FD_WRITE; + + llist_for_each_entry(msg, &queue->msg_queue, list) { + /* more writes than we have */ + if (count >= ARRAY_SIZE(iov)) + break; + + iov[count].iov_base = msg->l1h; + iov[count].iov_len = msgb_l1len(msg); + count += 1; + } + + /* TODO: check if all lengths are the same. */ + + + /* Nothing scheduled? This should not happen. */ + if (count == 0) { + if (!llist_empty(&queue->msg_queue)) + fd->when |= BSC_FD_WRITE; + return 0; + } + + written = writev(fd->fd, iov, count); + if (written < 0) { + /* nothing written?! */ + if (!llist_empty(&queue->msg_queue)) + fd->when |= BSC_FD_WRITE; + return 0; + } + + /* now delete the written entries */ + written = written / iov[0].iov_len; + count = 0; + llist_for_each_entry_safe(msg, tmp, &queue->msg_queue, list) { + queue->current_length -= 1; + + llist_del(&msg->list); + msgb_free(msg); + + count += 1; + if (count >= written) + break; + } + + if (!llist_empty(&queue->msg_queue)) + fd->when |= BSC_FD_WRITE; + } + + return 0; +} + +static int prim_size_for_queue(int queue) +{ + switch (queue) { + case MQ_SYS_WRITE: + return sizeof(SuperFemto_Prim_t); + case MQ_L1_WRITE: +#ifndef HW_SYSMOBTS_V1 + case MQ_TCH_WRITE: + case MQ_PDTCH_WRITE: +#endif + return sizeof(GsmL1_Prim_t); + default: + /* The compiler can't know that priv_nr is an enum. Assist. */ + LOGP(DL1C, LOGL_FATAL, "writing on a wrong queue: %d\n", + queue); + assert(false); + break; + } +} + +/* callback when there's something to read from the l1 msg_queue */ +static int read_dispatch_one(struct femtol1_hdl *fl1h, struct msgb *msg, int queue) +{ + switch (queue) { + case MQ_SYS_WRITE: + return l1if_handle_sysprim(fl1h, msg); + case MQ_L1_WRITE: +#ifndef HW_SYSMOBTS_V1 + case MQ_TCH_WRITE: + case MQ_PDTCH_WRITE: +#endif + return l1if_handle_l1prim(queue, fl1h, msg); + default: + /* The compiler can't know that priv_nr is an enum. Assist. */ + LOGP(DL1C, LOGL_FATAL, "writing on a wrong queue: %d\n", + queue); + assert(false); + break; + } +}; + +static int l1if_fd_cb(struct osmo_fd *ofd, unsigned int what) +{ + int i, rc; + + const uint32_t prim_size = prim_size_for_queue(ofd->priv_nr); + uint32_t count; + + struct iovec iov[3]; + struct msgb *msg[ARRAY_SIZE(iov)]; + + for (i = 0; i < ARRAY_SIZE(iov); ++i) { + msg[i] = msgb_alloc_headroom(prim_size + 128, 128, "1l_fd"); + msg[i]->l1h = msg[i]->data; + + iov[i].iov_base = msg[i]->l1h; + iov[i].iov_len = msgb_tailroom(msg[i]); + } + + rc = readv(ofd->fd, iov, ARRAY_SIZE(iov)); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "failed to read from fd: %s\n", strerror(errno)); + /* N. B: we do not abort to let the cycle below cleanup allocated memory properly, + the return value is ignored by the caller anyway. + TODO: use libexplain's explain_readv() to provide detailed error description */ + count = 0; + } else + count = rc / prim_size; + + for (i = 0; i < count; ++i) { + msgb_put(msg[i], prim_size); + read_dispatch_one(ofd->data, msg[i], ofd->priv_nr); + } + + for (i = count; i < ARRAY_SIZE(iov); ++i) + msgb_free(msg[i]); + + return 1; +} + +/* callback when we can write to one of the l1 msg_queue devices */ +static int l1fd_write_cb(struct osmo_fd *ofd, struct msgb *msg) +{ + int rc; + + rc = write(ofd->fd, msg->l1h, msgb_l1len(msg)); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "error writing to L1 msg_queue: %s\n", + strerror(errno)); + return rc; + } else if (rc < msg->len) { + LOGP(DL1C, LOGL_ERROR, "short write to L1 msg_queue: " + "%u < %u\n", rc, msg->len); + return -EIO; + } + + return 0; +} + +int l1if_transport_open(int q, struct femtol1_hdl *hdl) +{ + int rc; + + /* Step 1: Open all msg_queue file descriptors */ + struct osmo_fd *read_ofd = &hdl->read_ofd[q]; + struct osmo_wqueue *wq = &hdl->write_q[q]; + struct osmo_fd *write_ofd = &hdl->write_q[q].bfd; + + rc = open(rd_devnames[q], O_RDONLY); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "[%d] unable to open %s for reading: %s\n", + q, rd_devnames[q], strerror(errno)); + return rc; + } + read_ofd->fd = rc; + read_ofd->priv_nr = q; + read_ofd->data = hdl; + read_ofd->cb = l1if_fd_cb; + read_ofd->when = BSC_FD_READ; + rc = osmo_fd_register(read_ofd); + if (rc < 0) { + close(read_ofd->fd); + read_ofd->fd = -1; + return rc; + } + + rc = open(wr_devnames[q], O_WRONLY); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "[%d] unable to open %s for writing: %s\n", + q, wr_devnames[q], strerror(errno)); + goto out_read; + } + osmo_wqueue_init(wq, 10); + wq->write_cb = l1fd_write_cb; + write_ofd->cb = wqueue_vector_cb; + write_ofd->fd = rc; + write_ofd->priv_nr = q; + write_ofd->data = hdl; + write_ofd->when = BSC_FD_WRITE; + rc = osmo_fd_register(write_ofd); + if (rc < 0) { + close(write_ofd->fd); + write_ofd->fd = -1; + goto out_read; + } + + return 0; + +out_read: + close(hdl->read_ofd[q].fd); + osmo_fd_unregister(&hdl->read_ofd[q]); + + return rc; +} + +int l1if_transport_close(int q, struct femtol1_hdl *hdl) +{ + struct osmo_fd *read_ofd = &hdl->read_ofd[q]; + struct osmo_fd *write_ofd = &hdl->write_q[q].bfd; + + osmo_fd_unregister(read_ofd); + close(read_ofd->fd); + read_ofd->fd = -1; + + osmo_fd_unregister(write_ofd); + close(write_ofd->fd); + write_ofd->fd = -1; + + return 0; +} diff --git a/src/osmo-bts-sysmo/main.c b/src/osmo-bts-sysmo/main.c new file mode 100644 index 0000000..b63d07e --- /dev/null +++ b/src/osmo-bts-sysmo/main.c @@ -0,0 +1,194 @@ +/* Main program for Sysmocom BTS */ + +/* (C) 2011-2015 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define SYSMOBTS_RF_LOCK_PATH "/var/lock/bts_rf_lock" + +#include "utils.h" +#include "eeprom.h" +#include "l1_if.h" +#include "hw_misc.h" +#include "oml_router.h" + +int bts_model_init(struct gsm_bts *bts) +{ + struct stat st; + static struct osmo_fd accept_fd, read_fd; + int rc; + + bts->variant = BTS_OSMO_SYSMO; + bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3); + + rc = oml_router_init(bts, OML_ROUTER_PATH, &accept_fd, &read_fd); + if (rc < 0) { + fprintf(stderr, "Error creating the OML router: %s rc=%d\n", + OML_ROUTER_PATH, rc); + exit(1); + } + + if (stat(SYSMOBTS_RF_LOCK_PATH, &st) == 0) { + LOGP(DL1C, LOGL_NOTICE, "Not starting BTS due to RF_LOCK file present\n"); + exit(23); + } + + gsm_bts_set_feature(bts, BTS_FEAT_GPRS); + gsm_bts_set_feature(bts, BTS_FEAT_EGPRS); + gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS); + gsm_bts_set_feature(bts, BTS_FEAT_AGCH_PCH_PROP); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_EFR); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_AMR); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_AMR); + + bts_model_vty_init(bts); + + return 0; +} + +int bts_model_oml_estab(struct gsm_bts *bts) +{ + return 0; +} + +void bts_update_status(enum bts_global_status which, int on) +{ + static uint64_t states = 0; + uint64_t old_states = states; + int led_rf_active_on; + + if (on) + states |= (1ULL << which); + else + states &= ~(1ULL << which); + + led_rf_active_on = + (states & (1ULL << BTS_STATUS_RF_ACTIVE)) && + !(states & (1ULL << BTS_STATUS_RF_MUTE)); + + LOGP(DL1C, LOGL_INFO, + "Set global status #%d to %d (%04llx -> %04llx), LEDs: ACT %d\n", + which, on, + (long long)old_states, (long long)states, + led_rf_active_on); + + sysmobts_led_set(LED_RF_ACTIVE, led_rf_active_on); +} + +void bts_model_print_help() +{ + printf( + " -w --hw-version Print the targeted HW Version\n" + " -M --pcu-direct Force PCU to access message queue for " + "PDCH dchannel directly\n" + ); +}; + +static void print_hwversion() +{ +#ifdef HW_SYSMOBTS_V1 + printf("sysmobts was compiled for hw version 1.\n"); +#else + printf("sysmobts was compiled for hw version 2.\n"); +#endif +} + +int bts_model_handle_options(int argc, char **argv) +{ + int num_errors = 0; + + while (1) { + int option_idx = 0, c; + static const struct option long_options[] = { + /* specific to this hardware */ + { "hw-version", 0, 0, 'w' }, + { "pcu-direct", 0, 0, 'M' }, + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "wM", + long_options, &option_idx); + if (c == -1) + break; + + switch (c) { + case 'M': + pcu_direct = 1; + break; + case 'w': + print_hwversion(); + exit(0); + break; + default: + num_errors++; + break; + } + } + + return num_errors; +} + +void bts_model_phy_link_set_defaults(struct phy_link *plink) +{ +} + +void bts_model_phy_instance_set_defaults(struct phy_instance *pinst) +{ + pinst->u.sysmobts.clk_use_eeprom = 1; +} + +void bts_model_abis_close(struct gsm_bts *bts) +{ + /* for now, we simply terminate the program and re-spawn */ + bts_shutdown(bts, "Abis close"); +} + +int main(int argc, char **argv) +{ + return bts_main(argc, argv); +} diff --git a/src/osmo-bts-sysmo/misc/sysmobts-calib.c b/src/osmo-bts-sysmo/misc/sysmobts-calib.c new file mode 100644 index 0000000..a111d1d --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts-calib.c @@ -0,0 +1,537 @@ +/* OCXO/TCXO based calibration utility */ + +/* + * (C) 2012-2013 Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include + +#define _GNU_SOURCE +#include + +#include +#include + +#include + +#include + +#include "sysmobts-layer1.h" + +enum actions { + ACTION_SCAN, + ACTION_CALIB, + ACTION_BCCH, + ACTION_BCCH_CCCH, +}; + +static const char *modes[] = { + [ACTION_SCAN] = "scan", + [ACTION_CALIB] = "calibrate", + [ACTION_BCCH] = "bcch", + [ACTION_BCCH_CCCH] = "bcch_ccch", +}; + +static const char *bands[] = { + [GsmL1_FreqBand_850] = "850", + [GsmL1_FreqBand_900] = "900", + [GsmL1_FreqBand_1800] = "1800", + [GsmL1_FreqBand_1900] = "1900", +}; + +struct channel_pair { + int min; + int max; +}; + +static const struct channel_pair arfcns[] = { + [GsmL1_FreqBand_850] = { .min = 128, .max = 251 }, + [GsmL1_FreqBand_900] = { .min = 1, .max = 124 }, + [GsmL1_FreqBand_1800] = { .min = 512, .max = 885 }, + [GsmL1_FreqBand_1900] = { .min = 512, .max = 810 }, + +}; + +static const char *clk_source[] = { + [SuperFemto_ClkSrcId_Ocxo] = "ocxo", + [SuperFemto_ClkSrcId_Tcxo] = "tcxo", + [SuperFemto_ClkSrcId_External] = "external", + [SuperFemto_ClkSrcId_GpsPps] = "gps", + [SuperFemto_ClkSrcId_Trx] = "trx", + [SuperFemto_ClkSrcId_Rx] = "rx", + [SuperFemto_ClkSrcId_Edge] = "edge", + [SuperFemto_ClkSrcId_NetList] = "netlisten", +}; + +static const struct value_string sapi_names[GsmL1_Sapi_NUM+1] = { + { GsmL1_Sapi_Fcch, "FCCH" }, + { GsmL1_Sapi_Sch, "SCH" }, + { GsmL1_Sapi_Sacch, "SACCH" }, + { GsmL1_Sapi_Sdcch, "SDCCH" }, + { GsmL1_Sapi_Bcch, "BCCH" }, + { GsmL1_Sapi_Pch, "PCH" }, + { GsmL1_Sapi_Agch, "AGCH" }, + { GsmL1_Sapi_Cbch, "CBCH" }, + { GsmL1_Sapi_Rach, "RACH" }, + { GsmL1_Sapi_TchF, "TCH/F" }, + { GsmL1_Sapi_FacchF, "FACCH/F" }, + { GsmL1_Sapi_TchH, "TCH/H" }, + { GsmL1_Sapi_FacchH, "FACCH/H" }, + { GsmL1_Sapi_Nch, "NCH" }, + { GsmL1_Sapi_Pdtch, "PDTCH" }, + { GsmL1_Sapi_Pacch, "PACCH" }, + { GsmL1_Sapi_Pbcch, "PBCCH" }, + { GsmL1_Sapi_Pagch, "PAGCH" }, + { GsmL1_Sapi_Ppch, "PPCH" }, + { GsmL1_Sapi_Pnch, "PNCH" }, + { GsmL1_Sapi_Ptcch, "PTCCH" }, + { GsmL1_Sapi_Prach, "PRACH" }, + { 0, NULL } +}; + +static int action = ACTION_SCAN; +static int band = GsmL1_FreqBand_900; +static int calib = SuperFemto_ClkSrcId_Ocxo; +static int source = SuperFemto_ClkSrcId_NetList; +static int dsp_flags = 0x0; +static int cal_arfcn = 0; +static int initial_cor = 0; +static int steps = -1; + +static void print_usage(void) +{ + printf("Usage: sysmobts-calib ARGS\n"); +} + +static void print_help(void) +{ + printf(" -h --help this text\n"); + printf(" -c --clock " + "ocxo|tcxo|external|gps|trx|rx|edge\n"); + printf(" -s --calibration-source " + "ocxo|tcxo|external|gps|trx|rx|edge|netlisten\n"); + printf(" -b --band 850|900|1800|1900\n"); + printf(" -m --mode scan|calibrate|bcch|bcch_ccch\n"); + printf(" -a --arfcn NR arfcn for calibration\n"); + printf(" -d --dsp-flags NR dsp mask for debug log\n"); + printf(" -t --threshold level\n"); + printf(" -i --initial-clock-correction COR.\n"); + printf(" -t --steps STEPS\n"); +} + +static int find_value(const char **array, int size, char *value) +{ + int i = 0; + for (i = 0; i < size; ++i) { + if (array[i] == NULL) + continue; + if (strcmp(value, array[i]) == 0) + return i; + } + + printf("Failed to find: '%s'\n", value); + exit(-2); +} + +static void handle_options(int argc, char **argv) +{ + while (1) { + int option_index = 0, c; + static struct option long_options[] = { + {"help", 0, 0, 'h'}, + {"calibration-source", 1, 0, 's'}, + {"clock", 1, 0, 'c'}, + {"mode", 1, 0, 'm'}, + {"band", 1, 0, 'b'}, + {"dsp-flags", 1, 0, 'd'}, + {"arfcn", 1, 0, 'a'}, + {"initial-clock-correction", 1, 0, 'i'}, + {"steps", 1, 0, 't'}, + {0, 0, 0, 0}, + }; + + c = getopt_long(argc, argv, "hs:c:m:b:d:a:i:t:", + long_options, &option_index); + if (c == -1) + break; + switch (c) { + case 'h': + print_usage(); + print_help(); + exit(0); + case 's': + source = find_value(clk_source, + ARRAY_SIZE(clk_source), optarg); + break; + case 'c': + calib = find_value(clk_source, + ARRAY_SIZE(clk_source), optarg); + break; + case 'm': + action = find_value(modes, + ARRAY_SIZE(modes), optarg); + break; + case 'b': + band = find_value(bands, + ARRAY_SIZE(bands), optarg); + break; + case 'd': + dsp_flags = strtol(optarg, NULL, 16); + break; + case 'a': + cal_arfcn = atoi(optarg); + break; + case 'i': + initial_cor = atoi(optarg); + break; + case 't': + steps = atoi(optarg); + break; + default: + printf("Unhandled option, terminating.\n"); + exit(-1); + } + } + + if (source == calib) { + printf("Clock source and reference clock may not be the same.\n"); + exit(-3); + } + + if (calib == SuperFemto_ClkSrcId_NetList) { + printf("Clock may not be network listen.\n"); + exit(-4); + } + + if (action == ACTION_CALIB && source == SuperFemto_ClkSrcId_NetList) { + if (cal_arfcn == 0) { + printf("Please specify the reference ARFCN.\n"); + exit(-5); + } + + if (cal_arfcn < arfcns[band].min || cal_arfcn > arfcns[band].max) { + printf("ARFCN(%d) is not in the given band.\n", cal_arfcn); + exit(-6); + } + } +} + +#define CHECK_RC(rc) \ + if (rc != 0) \ + return EXIT_FAILURE; + +#define CHECK_RC_MSG(rc, msg) \ + if (rc != 0) { \ + printf("%s: %d\n", msg, rc); \ + return EXIT_FAILURE; \ + } +#define CHECK_COND_MSG(cond, rc, msg) \ + if (cond) { \ + printf("%s: %d\n", msg, rc); \ + return EXIT_FAILURE; \ + } + +struct scan_result +{ + uint16_t arfcn; + float rssi; +}; + +static int scan_cmp(const void *arg1, const void *arg2) +{ + struct scan_result *elem1 = (struct scan_result *) arg1; + struct scan_result *elem2 = (struct scan_result * )arg2; + + float diff = elem1->rssi - elem2->rssi; + if (diff > 0.0) + return 1; + else if (diff < 0.0) + return -1; + else + return 0; +} + +static int scan_band() +{ + int arfcn, rc, i; + + /* Scan results.. at most 400 items */ + struct scan_result results[400]; + memset(&results, 0, sizeof(results)); + int num_scan_results = 0; + + printf("Going to scan bands.\n"); + + for (arfcn = arfcns[band].min; arfcn <= arfcns[band].max; ++arfcn) { + float mean_rssi; + + printf("."); + fflush(stdout); + rc = power_scan(band, arfcn, 10, &mean_rssi); + CHECK_RC_MSG(rc, "Power Measurement failed"); + + results[num_scan_results].arfcn = arfcn; + results[num_scan_results].rssi = mean_rssi; + num_scan_results++; + } + + qsort(results, num_scan_results, sizeof(struct scan_result), scan_cmp); + printf("\nSorted scan results (weakest first):\n"); + for (i = 0; i < num_scan_results; ++i) + printf("ARFCN %3d: %.4f\n", results[i].arfcn, results[i].rssi); + + return 0; +} + +static int calib_get_clock_error(void) +{ + int rc, clkErr, clkErrRes; + + printf("Going to determine the clock offset.\n"); + + rc = rf_clock_info(&clkErr, &clkErrRes); + CHECK_RC_MSG(rc, "Clock info failed.\n"); + + if (clkErr == 0 && clkErrRes == 0) { + printf("Failed to get the clock info. Are both clocks present?\n"); + return -1; + } + + /* + * Empiric gps error determination. With revE and firmware v3.3 + * the clock error for TCXO to GPS appears to have a different + * sign. The device in question doesn't have a networklisten mode + * so it is impossible to verify that this only applies to GPS. + */ + if (source == SuperFemto_ClkSrcId_GpsPps) + clkErr *= -1; + + + /* this is an absolute clock error */ + printf("The calibration value is: %d\n", clkErr); + return 0; +} + +static int calib_clock_after_sync(void) +{ + int rc, clkErr, clkErrRes, iteration, cor; + + iteration = 0; + cor = initial_cor; + + printf("Trying to calibrate now and reducing clock error.\n"); + + for (iteration = 0; iteration < steps || steps <= 0; ++iteration) { + if (steps > 0) + printf("Iteration %d/%d with correction: %d\n", iteration, steps, cor); + else + printf("Iteration %d with correction: %d\n", iteration, cor); + + rc = rf_clock_info(&clkErr, &clkErrRes); + CHECK_RC_MSG(rc, "Clock info failed.\n"); + + /* + * TODO: use the clock error resolution here, implement it as a + * a PID controller.. + */ + + /* Picocell class requires 0.1ppm.. but that is 'too easy' */ + if (fabs(clkErr / 1000.0f) <= 0.05f) { + printf("The calibration value is: %d\n", cor); + return 1; + } + + cor -= clkErr / 2; + rc = set_clock_cor(cor, calib, source); + CHECK_RC_MSG(rc, "Clock correction failed.\n"); + } + + return -1; +} + +static int find_initial_clock(HANDLE layer1, int *clock) +{ + int i; + + printf("Trying to find an initial clock value.\n"); + + for (i = 0; i < 1000; ++i) { + int rc; + int cor = i * 150; + rc = wait_for_sync(layer1, cor, calib, source); + if (rc == 1) { + printf("Found initial clock offset: %d\n", cor); + *clock = cor; + break; + } else { + CHECK_RC_MSG(rc, "Failed to set new clock value.\n"); + } + + cor = i * -150; + rc = wait_for_sync(layer1, cor, calib, source); + if (rc == 1) { + printf("Found initial clock offset: %d\n", cor); + *clock = cor; + break; + } else { + CHECK_RC_MSG(rc, "Failed to set new clock value.\n"); + } + } + + return 0; +} + +static int calib_clock_netlisten(void) +{ + int rc, cor = initial_cor; + float mean_rssi; + HANDLE layer1; + + rc = power_scan(band, cal_arfcn, 10, &mean_rssi); + CHECK_RC_MSG(rc, "ARFCN measurement scan failed"); + if (mean_rssi < -118.0f) + printf("ARFCN has weak signal for calibration: %f\n", mean_rssi); + + /* initial lock */ + rc = follow_sch(band, cal_arfcn, calib, source, &layer1); + if (rc == -23) + rc = find_initial_clock(layer1, &cor); + CHECK_RC_MSG(rc, "Following SCH failed"); + + /* now try to calibrate it */ + rc = set_clock_cor(cor, calib, source); + CHECK_RC_MSG(rc, "Clock setup failed."); + + calib_clock_after_sync(); + + rc = mph_close(layer1); + CHECK_RC_MSG(rc, "MPH-Close"); + + return EXIT_SUCCESS; +} + +static int calib_clock(void) +{ + int rc; + + /* now try to calibrate it */ + rc = set_clock_cor(initial_cor, calib, source); + CHECK_RC_MSG(rc, "Clock setup failed."); + + calib_get_clock_error(); + + return EXIT_SUCCESS; +} + +static int bcch_follow(void) +{ + int rc, cor = initial_cor; + float mean_rssi; + HANDLE layer1; + + rc = power_scan(band, cal_arfcn, 10, &mean_rssi); + CHECK_RC_MSG(rc, "ARFCN measurement scan failed"); + if (mean_rssi < -118.0f) + printf("ARFCN has weak signal for calibration: %f\n", mean_rssi); + + /* initial lock */ + rc = follow_sch(band, cal_arfcn, calib, source, &layer1); + if (rc == -23) + rc = find_initial_clock(layer1, &cor); + CHECK_RC_MSG(rc, "Following SCH failed"); + + /* identify the BSIC and set it as TSC */ + rc = find_bsic(); + CHECK_COND_MSG(rc < 0, rc, "Identifying the BSIC failed"); + rc = set_tsc_from_bsic(layer1, rc); + CHECK_RC_MSG(rc, "Setting the TSC failed"); + + + /* follow the bcch */ + rc = follow_bcch(layer1); + CHECK_RC_MSG(rc, "Follow BCCH"); + + /* follow the pch */ + if (action == ACTION_BCCH_CCCH) { + rc = follow_pch(layer1); + CHECK_RC_MSG(rc, "Follow BCCH/CCCH"); + } + + /* now wait for the PhDataInd */ + for (;;) { + uint32_t fn; + uint8_t block; + uint8_t data[23]; + size_t size; + struct gsm_time gsmtime; + GsmL1_Sapi_t sapi; + + rc = wait_for_data(data, &size, &fn, &block, &sapi); + if (rc == 1) + continue; + CHECK_RC_MSG(rc, "No Data Indication"); + + gsm_fn2gsmtime(&gsmtime, fn); + printf("%02u/%02u/%02u %6s %s\n", + gsmtime.t1, gsmtime.t2, gsmtime.t3, + get_value_string(sapi_names, sapi), + osmo_hexdump(data, size)); + } + + rc = mph_close(layer1); + CHECK_RC_MSG(rc, "MPH-Close"); + + return EXIT_SUCCESS; +} + +int main(int argc, char **argv) +{ + int rc; + + handle_options(argc, argv); + printf("Initializing the Layer1\n"); + rc = initialize_layer1(dsp_flags); + CHECK_RC(rc); + + printf("Fetching system info.\n"); + rc = print_system_info(); + CHECK_RC(rc); + + printf("Opening RF frontend with clock(%d) and correction(%d)\n", + calib, initial_cor); + rc = activate_rf_frontend(calib, initial_cor); + CHECK_RC(rc); + + if (action == ACTION_SCAN) + return scan_band(); + else if (action == ACTION_BCCH || action == ACTION_BCCH_CCCH) + return bcch_follow(); + else { + if (source == SuperFemto_ClkSrcId_NetList) + return calib_clock_netlisten(); + return calib_clock(); + } + + return EXIT_SUCCESS; +} diff --git a/src/osmo-bts-sysmo/misc/sysmobts-layer1.c b/src/osmo-bts-sysmo/misc/sysmobts-layer1.c new file mode 100644 index 0000000..4b34f50 --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts-layer1.c @@ -0,0 +1,800 @@ +/* Layer1 handling for the DSP/FPGA */ +/* + * (C) 2012-2013 Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "sysmobts-layer1.h" + +#define ARRAY_SIZE(ar) (sizeof(ar)/sizeof((ar)[0])) + +#define BTS_DSP2ARM "/dev/msgq/superfemto_dsp2arm" +#define BTS_ARM2DSP "/dev/msgq/superfemto_arm2dsp" +#define L1_SIG_ARM2DSP "/dev/msgq/gsml1_sig_arm2dsp" +#define L1_SIG_DSP2ARM "/dev/msgq/gsml1_sig_dsp2arm" + +int set_clock_cor(int clock_cor, int calib, int source); +static int wait_read_ignore(int seconds); + +static int sys_dsp2arm = -1, + sys_arm2dsp = -1, + sig_dsp2arm = -1, + sig_arm2dsp = -1; + +static int sync_indicated = 0; +static int time_indicated = 0; + +static int open_devices() +{ + sys_dsp2arm = open(BTS_DSP2ARM, O_RDONLY); + if (sys_dsp2arm == -1) { + perror("Failed to open dsp2arm system queue"); + return -1; + } + + sys_arm2dsp = open(BTS_ARM2DSP, O_WRONLY); + if (sys_arm2dsp == -1) { + perror("Failed to open arm2dsp system queue"); + return -2; + } + + sig_dsp2arm = open(L1_SIG_DSP2ARM, O_RDONLY); + if (sig_dsp2arm == -1) { + perror("Failed to open dsp2arm sig queue"); + return -3; + } + + sig_arm2dsp = open(L1_SIG_ARM2DSP, O_WRONLY); + if (sig_arm2dsp == -1) { + perror("Failed to open arm2dsp sig queue"); + return -4; + } + + return 0; +} + +/** + * Send a primitive to the system queue + */ +static int send_primitive(int primitive, SuperFemto_Prim_t *prim) +{ + prim->id = primitive; + return write(sys_arm2dsp, prim, sizeof(*prim)) != sizeof(*prim); +} + +/** + * Wait for a confirmation + */ +static int wait_primitive(int wait_for, SuperFemto_Prim_t *prim) +{ + memset(prim, 0, sizeof(*prim)); + int rc = read(sys_dsp2arm, prim, sizeof(*prim)); + if (rc != sizeof(*prim)) { + printf("Short read in %s: %d\n", __func__, rc); + return -1; + } + + if (prim->id != wait_for) { + printf("Got primitive %d but waited for %d\n", + prim->id, wait_for); + return -2; + } + + return 0; +} + +/* The Cnf for the Req, assume it is a +1 */ +static int answer_for(int primitive) +{ + return primitive + 1; +} + +static int send_recv_primitive(int p, SuperFemto_Prim_t *prim) +{ + int rc; + rc = send_primitive(p, prim); + if (rc != 0) + return -1; + + rc = wait_primitive(answer_for(p), prim); + if (rc != 0) + return -2; + return 0; +} + +static int answer_for_sig(int prim) +{ + static const GsmL1_PrimId_t cnf[] = { + [GsmL1_PrimId_MphInitReq] = GsmL1_PrimId_MphInitCnf, + [GsmL1_PrimId_MphCloseReq] = GsmL1_PrimId_MphCloseCnf, + [GsmL1_PrimId_MphConnectReq] = GsmL1_PrimId_MphConnectCnf, + [GsmL1_PrimId_MphActivateReq] = GsmL1_PrimId_MphActivateCnf, + [GsmL1_PrimId_MphConfigReq] = GsmL1_PrimId_MphConfigCnf, + [GsmL1_PrimId_MphMeasureReq] = GsmL1_PrimId_MphMeasureCnf, + }; + + if (prim < 0 || prim >= ARRAY_SIZE(cnf)) { + printf("Unknown primitive: %d\n", prim); + exit(-3); + } + + return cnf[prim]; +} + +static int is_indication(int prim) +{ + return + prim == GsmL1_PrimId_MphTimeInd || + prim == GsmL1_PrimId_MphSyncInd || + prim == GsmL1_PrimId_PhConnectInd || + prim == GsmL1_PrimId_PhReadyToSendInd || + prim == GsmL1_PrimId_PhDataInd || + prim == GsmL1_PrimId_PhRaInd; +} + + +static int send_recv_sig_prim(int p, GsmL1_Prim_t *prim) +{ + int rc; + prim->id = p; + rc = write(sig_arm2dsp, prim, sizeof(*prim)); + if (rc != sizeof(*prim)) { + printf("Failed to write: %d\n", rc); + return -1; + } + + do { + rc = read(sig_dsp2arm, prim, sizeof(*prim)); + if (rc != sizeof(*prim)) { + printf("Failed to read: %d\n", rc); + return -2; + } + } while (is_indication(prim->id)); + + if (prim->id != answer_for_sig(p)) { + printf("Wrong L1 result got %d wanted %d for prim: %d\n", + prim->id, answer_for_sig(p), p); + return -3; + } + + return 0; +} + +static int wait_for_indication(int p, GsmL1_Prim_t *prim) +{ + int rc; + memset(prim, 0, sizeof(*prim)); + + struct timespec start_time, now_time; + clock_gettime(CLOCK_MONOTONIC, &start_time); + + /* + * TODO: select.... with timeout. The below will work 99% as we will + * get time indications very soonish after the connect + */ + for (;;) { + clock_gettime(CLOCK_MONOTONIC, &now_time); + if (now_time.tv_sec - start_time.tv_sec > 10) { + printf("Timeout waiting for indication.\n"); + return -4; + } + + rc = read(sig_dsp2arm, prim, sizeof(*prim)); + if (rc != sizeof(*prim)) { + printf("Failed to read.\n"); + return -1; + } + + if (!is_indication(prim->id)) { + printf("No indication: %d\n", prim->id); + return -2; + } + + if (p != prim->id && prim->id == GsmL1_PrimId_MphSyncInd) { + printf("Got sync.\n"); + sync_indicated = 1; + continue; + } + if (p != prim->id && prim->id == GsmL1_PrimId_MphTimeInd) { + time_indicated = 1; + continue; + } + + if (p != prim->id) { + printf("Wrong indication got %d wanted %d\n", + prim->id, p); + return -3; + } + + break; + } + + return 0; +} + +static int set_trace_flags(uint32_t dsp) +{ + SuperFemto_Prim_t prim; + memset(&prim, 0, sizeof(prim)); + + prim.u.setTraceFlagsReq.u32Tf = dsp; + return send_primitive(SuperFemto_PrimId_SetTraceFlagsReq, &prim); +} + +static int reset_and_wait() +{ + int rc; + SuperFemto_Prim_t prim; + memset(&prim, 0, sizeof(prim)); + + rc = send_recv_primitive(SuperFemto_PrimId_Layer1ResetReq, &prim); + if (rc != 0) + return -1; + if (prim.u.layer1ResetCnf.status != GsmL1_Status_Success) + return -2; + return 0; +} + +/** + * Open the message queues and (re-)initialize the DSP and FPGA + */ +int initialize_layer1(uint32_t dsp_flags) +{ + if (open_devices() != 0) { + printf("Failed to open devices.\n"); + return -1; + } + + if (set_trace_flags(dsp_flags) != 0) { + printf("Failed to set dsp flags.\n"); + return -2; + } + if (reset_and_wait() != 0) { + printf("Failed to reset the firmware.\n"); + return -3; + } + return 0; +} + +/** + * Print systems infos + */ +int print_system_info() +{ + int rc; + SuperFemto_Prim_t prim; + memset(&prim, 0, sizeof(prim)); + + rc = send_recv_primitive(SuperFemto_PrimId_SystemInfoReq, &prim); + if (rc != 0) { + printf("Failed to send SystemInfoRequest.\n"); + return -1; + } + + if (prim.u.systemInfoCnf.status != GsmL1_Status_Success) { + printf("Failed to request SystemInfoRequest.\n"); + return -2; + } + +#define INFO_DSP(x) x.u.systemInfoCnf.dspVersion +#define INFO_FPGA(x) x.u.systemInfoCnf.fpgaVersion +#ifdef FEMTOBTS_NO_BOARD_VERSION +#define BOARD_REV(x) -1 +#define BOARD_OPT(x) -1 +#define COMPILED_MAJOR (FEMTOBTS_API_VERSION >> 16) +#define COMPILED_MINOR ((FEMTOBTS_API_VERSION >> 8) & 0xff) +#define COMPILED_BUILD (FEMTOBTS_API_VERSION & 0xff) +#else +#define BOARD_REV(x) x.u.systemInfoCnf.boardVersion.rev +#define BOARD_OPT(x) x.u.systemInfoCnf.boardVersion.option +#define COMPILED_MAJOR (SUPERFEMTO_API_VERSION >> 16) +#define COMPILED_MINOR ((SUPERFEMTO_API_VERSION >> 8) & 0xff) +#define COMPILED_BUILD (SUPERFEMTO_API_VERSION & 0xff) +#endif + + printf("Compiled against: v%u.%u.%u\n", + COMPILED_MAJOR, COMPILED_MINOR, COMPILED_BUILD); + printf("Running DSP v%d.%d.%d FPGA v%d.%d.%d Rev: %d Option: %d\n", + INFO_DSP(prim).major, INFO_DSP(prim).minor, INFO_DSP(prim).build, + INFO_FPGA(prim).major, INFO_FPGA(prim).minor, INFO_FPGA(prim).build, + BOARD_REV(prim), BOARD_OPT(prim)); + + if (COMPILED_MAJOR != INFO_DSP(prim).major || COMPILED_MINOR != INFO_DSP(prim).minor) { + printf("WARNING! WARNING! WARNING! WARNING! WARNING\n"); + printf("You might run this against an incompatible firmware.\n"); + printf("Continuing anyway but the result might be broken\n"); + } +#undef INFO_DSP +#undef INFO_FPGA +#undef BOARD_REV +#undef BOARD_OPT +#undef COMPILED_MAJOR +#undef COMPILED_MINOR +#undef COMPILED_BUILD + return 0; +} + +int activate_rf_frontend(int clock_source, int initial_cor) +{ + int rc; + SuperFemto_Prim_t prim; + memset(&prim, 0, sizeof(prim)); + + prim.u.activateRfReq.timing.u8TimSrc = 1; + prim.u.activateRfReq.msgq.u8UseTchMsgq = 0; + prim.u.activateRfReq.msgq.u8UsePdtchMsgq = 0; + + prim.u.activateRfReq.rfTrx.iClkCor = initial_cor; + prim.u.activateRfReq.rfTrx.clkSrc = clock_source; +#if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,4,0) + prim.u.activateRfReq.rfRx.iClkCor = initial_cor; + prim.u.activateRfReq.rfRx.clkSrc = clock_source; +#endif + + rc = send_recv_primitive(SuperFemto_PrimId_ActivateRfReq, &prim); + return rc; +} + +static int mph_init(int band, int arfcn, HANDLE *layer1) +{ + int rc; + GsmL1_Prim_t prim; + memset(&prim, 0, sizeof(prim)); + + prim.u.mphInitReq.deviceParam.devType = GsmL1_DevType_Rxd; + prim.u.mphInitReq.deviceParam.freqBand = band; + prim.u.mphInitReq.deviceParam.u16Arfcn = arfcn; + prim.u.mphInitReq.deviceParam.u16BcchArfcn = arfcn; + prim.u.mphInitReq.deviceParam.fRxPowerLevel = -75.f; + prim.u.mphInitReq.deviceParam.u8AutoTA = 1; + + rc = send_recv_sig_prim(GsmL1_PrimId_MphInitReq, &prim); + if (rc != 0) { + printf("Failed to initialize the physical channel.\n"); + return -1; + } + + if (prim.u.mphInitCnf.status != GsmL1_Status_Success) { + printf("MPH Init failed.\n"); + return -2; + } + +#if 0 + if (prim.u.mphInitCnf.freqBand != band) { + printf("Layer1 ignored the band: %d\n", + prim.u.mphInitCnf.freqBand); + return -3; + } +#endif + + *layer1 = prim.u.mphInitCnf.hLayer1; + return 0; +} + +int mph_close(HANDLE layer1) +{ + int rc; + GsmL1_Prim_t prim; + memset(&prim, 0, sizeof(prim)); + + prim.u.mphCloseReq.hLayer1 = layer1; + rc = send_recv_sig_prim(GsmL1_PrimId_MphCloseReq, &prim); + if (rc != 0) { + printf("Failed to close the MPH\n"); + return -6; + } + if (prim.u.mphCloseCnf.status != GsmL1_Status_Success) { + printf("MPH Close failed.\n"); + return -7; + } + + return 0; +} + +int follow_sch(int band, int arfcn, int clock, int ref, HANDLE *layer1) +{ + int rc; + GsmL1_Prim_t prim; + + time_indicated = 0; + sync_indicated = 0; + + rc = mph_init(band, arfcn, layer1); + if (rc != 0) + return rc; + + /* 1.) Connect */ + memset(&prim, 0, sizeof(prim)); + prim.u.mphConnectReq.hLayer1 = *layer1; + prim.u.mphConnectReq.u8Tn = 0; + prim.u.mphConnectReq.logChComb = GsmL1_LogChComb_IV; + rc = send_recv_sig_prim(GsmL1_PrimId_MphConnectReq, &prim); + if (rc != 0) { + printf("Failed to connect.\n"); + return -1; + } + if (prim.u.mphConnectCnf.status != GsmL1_Status_Success) { + printf("Connect failed.\n"); + return -2; + } + if (prim.u.mphConnectCnf.u8Tn != 0) { + printf("Wrong timeslot.\n"); + return -3; + } + + /* 2.) Activate */ + memset(&prim, 0, sizeof(prim)); + prim.u.mphActivateReq.hLayer1 = *layer1; + prim.u.mphActivateReq.u8Tn = 0; + prim.u.mphActivateReq.sapi = GsmL1_Sapi_Sch; + prim.u.mphActivateReq.dir = GsmL1_Dir_RxDownlink; + rc = send_recv_sig_prim(GsmL1_PrimId_MphActivateReq, &prim); + if (rc != 0) { + printf("Activation failed.\n"); + return -4; + } + if (prim.u.mphActivateCnf.status != GsmL1_Status_Success) { + printf("Activation not successful.\n"); + return -5; + } + + /* 3.) Wait for indication... TODO: check... */ + printf("Waiting for connect indication.\n"); + rc = wait_for_indication(GsmL1_PrimId_PhConnectInd, &prim); + if (rc != 0) { + printf("Didn't get a connect indication.\n"); + return rc; + } + + /* 4.) Indication Syndication TODO: check... */ + if (!sync_indicated) { + printf("Waiting for sync indication.\n"); + rc = wait_for_indication(GsmL1_PrimId_MphSyncInd, &prim); + if (rc < 0) { + printf("Didn't get a sync indication.\n"); + return -23; + } else if (rc == 0) { + if (!prim.u.mphSyncInd.u8Synced) { + printf("Failed to get sync.\n"); + return -23; + } else { + printf("Synced.\n"); + } + } + } else { + printf("Already synced.\n"); + } + + return 0; +} + +static int follow_sapi(HANDLE layer1, const GsmL1_Sapi_t sapi) +{ + int rc; + GsmL1_Prim_t prim; + + /* 1.) Activate BCCH or such... */ + memset(&prim, 0, sizeof(prim)); + prim.u.mphActivateReq.hLayer1 = layer1; + prim.u.mphActivateReq.u8Tn = 0; + prim.u.mphActivateReq.sapi = sapi; + prim.u.mphActivateReq.dir = GsmL1_Dir_RxDownlink; + + rc = send_recv_sig_prim(GsmL1_PrimId_MphActivateReq, &prim); + if (rc != 0) { + printf("Activation failed.\n"); + return -4; + } + if (prim.u.mphActivateCnf.status != GsmL1_Status_Success) { + printf("Activation not successful.\n"); + return -5; + } + + /* 2.) Wait for indication... */ + printf("Waiting for connect indication.\n"); + rc = wait_for_indication(GsmL1_PrimId_PhConnectInd, &prim); + if (rc != 0) { + printf("Didn't get a connect indication.\n"); + return rc; + } + + if (prim.u.phConnectInd.sapi != sapi) { + printf("Got a connect indication for the wrong type: %d\n", + prim.u.phConnectInd.sapi); + return -6; + } + + /* 3.) Wait for PhDataInd... */ + printf("Waiting for data.\n"); + rc = wait_for_indication(GsmL1_PrimId_PhDataInd, &prim); + if (rc != 0) { + printf("Didn't get data.\n"); + return rc; + } + + return 0; +} + +int follow_bcch(HANDLE layer1) +{ + return follow_sapi(layer1, GsmL1_Sapi_Bcch); +} + +int follow_pch(HANDLE layer1) +{ + return follow_sapi(layer1, GsmL1_Sapi_Pch); +} + +int find_bsic(void) +{ + int rc, i; + GsmL1_Prim_t prim; + + printf("Waiting for SCH data.\n"); + for (i = 0; i < 10; ++i) { + uint8_t bsic; + rc = wait_for_indication(GsmL1_PrimId_PhDataInd, &prim); + if (rc < 0) { + printf("Didn't get SCH data.\n"); + return rc; + } + if (prim.u.phDataInd.sapi != GsmL1_Sapi_Sch) + continue; + + bsic = (prim.u.phDataInd.msgUnitParam.u8Buffer[0] >> 2) & 0xFF; + return bsic; + } + + printf("Giving up finding the SCH\n"); + return -1; +} + +int set_tsc_from_bsic(HANDLE layer1, int bsic) +{ + int rc; + int tsc = bsic & 0x7; + GsmL1_Prim_t prim; + + memset(&prim, 0, sizeof(prim)); + prim.u.mphConfigReq.hLayer3 = 0x23; + prim.u.mphConfigReq.hLayer1 = layer1; + prim.u.mphConfigReq.cfgParamId = GsmL1_ConfigParamId_SetNbTsc; + prim.u.mphConfigReq.cfgParams.setNbTsc.u8NbTsc = tsc; + rc = send_recv_sig_prim(GsmL1_PrimId_MphConfigReq, &prim); + if (rc != 0) { + printf("Failed to send configure.\n"); + } + + if (prim.u.mphConfigCnf.status != GsmL1_Status_Success) { + printf("Failed to set the config cnf.\n"); + return -1; + } + + return 0; +} + +int set_clock_cor(int clock_cor, int calib, int source) +{ + int rc; + SuperFemto_Prim_t prim; + memset(&prim, 0, sizeof(prim)); + + prim.u.rfClockSetupReq.rfTrx.iClkCor = clock_cor; + prim.u.rfClockSetupReq.rfTrx.clkSrc = calib; +#if SUPERFEMTO_API_VERSION < SUPERFEMTO_API(2,4,0) + prim.u.rfClockSetupReq.rfRx.iClkCor = clock_cor; + prim.u.rfClockSetupReq.rfRx.clkSrc = calib; +#endif + prim.u.rfClockSetupReq.rfTrxClkCal.clkSrc = source; + + rc = send_recv_primitive(SuperFemto_PrimId_RfClockSetupReq, &prim); + if (rc != 0) { + printf("Failed to set the clock setup.\n"); + return -1; + } + if (prim.u.rfClockSetupCnf.status != GsmL1_Status_Success) { + printf("Clock setup was not successfull.\n"); + return -2; + } + + return 0; +} + +int rf_clock_info(int *clkErr, int *clkErrRes) +{ + SuperFemto_Prim_t prim; + memset(&prim, 0, sizeof(prim)); + + int rc; + + /* reset the counter */ + prim.u.rfClockInfoReq.u8RstClkCal = 1; + rc = send_recv_primitive(SuperFemto_PrimId_RfClockInfoReq, &prim); + if (rc != 0) { + printf("Failed to reset the clock info.\n"); + return -1; + } + + /* wait for a value */ + wait_read_ignore(15); + + /* ask for the current counter/error */ + memset(&prim, 0, sizeof(prim)); + prim.u.rfClockInfoReq.u8RstClkCal = 0; + rc = send_recv_primitive(SuperFemto_PrimId_RfClockInfoReq, &prim); + if (rc != 0) { + printf("Failed to get the clock info.\n"); + return -2; + } + + printf("Error: %d Res: %d\n", + prim.u.rfClockInfoCnf.rfTrxClkCal.iClkErr, + prim.u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes); + *clkErr = prim.u.rfClockInfoCnf.rfTrxClkCal.iClkErr; + *clkErrRes = prim.u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes; + return 0; +} + +int power_scan(int band, int arfcn, int duration, float *mean_rssi) +{ + int rc; + HANDLE layer1; + GsmL1_Prim_t prim; + + /* init */ + rc = mph_init(band, arfcn, &layer1); + if (rc != 0) + return rc; + + /* mph measure request */ + memset(&prim, 0, sizeof(prim)); + prim.u.mphMeasureReq.hLayer1 = layer1; + prim.u.mphMeasureReq.u32Duration = duration; + rc = send_recv_sig_prim(GsmL1_PrimId_MphMeasureReq, &prim); + if (rc != 0) { + printf("Failed to send measurement request.\n"); + return -4; + } + + if (prim.u.mphMeasureCnf.status != GsmL1_Status_Success) { + printf("MphMeasureReq was not confirmed.\n"); + return -5; + } + + *mean_rssi = prim.u.mphMeasureCnf.fMeanRssi; + + /* close */ + rc = mph_close(layer1); + return rc; +} + +/** + * Wait for indication... + */ +int wait_for_sync(HANDLE layer1, int cor, int calib, int source) +{ + GsmL1_Prim_t prim; + int rc; + + rc = set_clock_cor(cor, calib, source); + if (rc != 0) { + printf("Failed to set the clock correction.\n"); + return -1; + } + + sync_indicated = 0; + rc = wait_for_indication(GsmL1_PrimId_MphSyncInd, &prim); + if (rc < 0 && rc != -4) { + return rc; + } else if (rc == 0) { + if (!prim.u.mphSyncInd.u8Synced) { + printf("Failed to get sync.\n"); + return 0; + } + printf("Synced.\n"); + return 1; + } + + return 0; +} + +int wait_for_data(uint8_t *data, size_t *size, uint32_t *fn, uint8_t *block, GsmL1_Sapi_t *sap) +{ + GsmL1_Prim_t prim; + int rc; + + rc = wait_for_indication(GsmL1_PrimId_PhDataInd, &prim); + if (rc < 0) + return rc; + if (prim.u.phDataInd.sapi == GsmL1_Sapi_Sch) + return 1; + + *size = prim.u.phDataInd.msgUnitParam.u8Size; + *fn = prim.u.phDataInd.u32Fn; + *block = prim.u.phDataInd.u8BlockNbr; + *sap = prim.u.phDataInd.sapi; + memcpy(data, prim.u.phDataInd.msgUnitParam.u8Buffer, *size); + return 0; +} + +/** + * Make sure the pipe is not running full. + * + */ +static int wait_read_ignore(int seconds) +{ + int max, rc; + fd_set fds; + struct timeval timeout; + + max = sys_dsp2arm > sig_dsp2arm ? sys_dsp2arm : sig_dsp2arm; + + timeout.tv_sec = seconds; + timeout.tv_usec = 0; + + while (1) { + FD_ZERO(&fds); + FD_SET(sys_dsp2arm, &fds); + FD_SET(sig_dsp2arm, &fds); + + + rc = select(max + 1, &fds, NULL, NULL, &timeout); + if (rc == -1) { + printf("Failed to select.\n"); + return -1; + } else if (rc) { + if (FD_ISSET(sys_dsp2arm, &fds)) { + SuperFemto_Prim_t prim; + rc = read(sys_dsp2arm, &prim, sizeof(prim)); + if (rc != sizeof(prim)) { + perror("Failed to read system primitive"); + return -2; + } + } + if (FD_ISSET(sig_dsp2arm, &fds)) { + GsmL1_Prim_t prim; + rc = read(sig_dsp2arm, &prim, sizeof(prim)); + if (rc != sizeof(prim)) { + perror("Failed to read signal primitiven"); + return -3; + } + } + } else if (timeout.tv_sec <= 0 && timeout.tv_usec <= 0) { + break; + } + +#ifndef __linux__ +#error "Non portable code" +#endif + } + return 0; +} diff --git a/src/osmo-bts-sysmo/misc/sysmobts-layer1.h b/src/osmo-bts-sysmo/misc/sysmobts-layer1.h new file mode 100644 index 0000000..e7d59c9 --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts-layer1.h @@ -0,0 +1,45 @@ +#ifndef SYSMOBTS_LAYER_H +#define SYSMOBTS_LAYER_H + +#include + +#ifdef FEMTOBTS_API_VERSION +#define SuperFemto_PrimId_t FemtoBts_PrimId_t +#define SuperFemto_Prim_t FemtoBts_Prim_t +#define SuperFemto_PrimId_SystemInfoReq FemtoBts_PrimId_SystemInfoReq +#define SuperFemto_PrimId_SystemInfoCnf FemtoBts_PrimId_SystemInfoCnf +#define SuperFemto_SystemInfoCnf_t FemtoBts_SystemInfoCnf_t +#define SuperFemto_PrimId_SystemFailureInd FemtoBts_PrimId_SystemFailureInd +#define SuperFemto_PrimId_ActivateRfReq FemtoBts_PrimId_ActivateRfReq +#define SuperFemto_PrimId_ActivateRfCnf FemtoBts_PrimId_ActivateRfCnf +#define SuperFemto_PrimId_DeactivateRfReq FemtoBts_PrimId_DeactivateRfReq +#define SuperFemto_PrimId_DeactivateRfCnf FemtoBts_PrimId_DeactivateRfCnf +#define SuperFemto_PrimId_SetTraceFlagsReq FemtoBts_PrimId_SetTraceFlagsReq +#define SuperFemto_PrimId_RfClockInfoReq FemtoBts_PrimId_RfClockInfoReq +#define SuperFemto_PrimId_RfClockInfoCnf FemtoBts_PrimId_RfClockInfoCnf +#define SuperFemto_PrimId_RfClockSetupReq FemtoBts_PrimId_RfClockSetupReq +#define SuperFemto_PrimId_RfClockSetupCnf FemtoBts_PrimId_RfClockSetupCnf +#define SuperFemto_PrimId_Layer1ResetReq FemtoBts_PrimId_Layer1ResetReq +#define SuperFemto_PrimId_Layer1ResetCnf FemtoBts_PrimId_Layer1ResetCnf +#define SuperFemto_PrimId_NUM FemtoBts_PrimId_NUM +#define HW_SYSMOBTS_V1 1 +#define SUPERFEMTO_API(x,y,z) FEMTOBTS_API(x,y,z) +#endif + +extern int initialize_layer1(uint32_t dsp_flags); +extern int print_system_info(); +extern int activate_rf_frontend(int clock_source, int clock_cor); +extern int power_scan(int band, int arfcn, int duration, float *mean_rssi); +extern int follow_sch(int band, int arfcn, int calib, int reference, HANDLE *layer1); +extern int follow_bch(HANDLE layer1); +extern int find_bsic(void); +extern int set_tsc_from_bsic(HANDLE layer1, int bsic); +extern int set_clock_cor(int clock_corr, int calib, int source); +extern int rf_clock_info(int *clkErr, int *clkErrRes); +extern int mph_close(HANDLE layer1); +extern int wait_for_sync(HANDLE layer1, int cor, int calib, int source); +extern int follow_bcch(HANDLE layer1); +extern int follow_pch(HANDLE layer1); +extern int wait_for_data(uint8_t *data, size_t *size, uint32_t *fn, uint8_t *block, GsmL1_Sapi_t *sapi); + +#endif diff --git a/src/osmo-bts-sysmo/misc/sysmobts_eeprom.h b/src/osmo-bts-sysmo/misc/sysmobts_eeprom.h new file mode 100644 index 0000000..b7a27fb --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_eeprom.h @@ -0,0 +1,44 @@ +#ifndef _SYSMOBTS_EEPROM_H +#define _SYSMOBTS_EEPROM_H + +#include + +struct sysmobts_net_cfg { + uint8_t mode; /* 0 */ + uint32_t ip; /* 1 - 4 */ + uint32_t mask; /* 5 - 8 */ + uint32_t gw; /* 9 - 12 */ + uint32_t dns; /* 13 - 16 */ +} __attribute__((packed)); + +struct sysmobts_eeprom { /* offset */ + uint8_t eth_mac[6]; /* 0-5 */ + uint8_t _pad0[10]; /* 6-15 */ + uint16_t unused1; /* 16-17 */ + uint8_t temp1_max; /* 18 */ + uint8_t temp2_max; /* 19 */ + uint32_t serial_nr; /* 20-23 */ + uint32_t operational_hours; /* 24-27 */ + uint32_t boot_count; /* 28-31 */ + uint16_t model_nr; /* 32-33 */ + uint16_t model_flags; /* 34-35 */ + uint8_t trx_nr; /* 36 */ + uint8_t boot_state[48]; /* 37-84 */ + uint8_t _pad1[18]; /* 85-102 */ + struct sysmobts_net_cfg net_cfg;/* 103-119 */ + uint8_t crc; /* 120 */ + uint8_t gpg_key[128]; /* 121-249 */ +} __attribute__((packed)); + +enum sysmobts_model_number { + MODEL_SYSMOBTS_1002 = 1002, + MODEL_SYSMOBTS_1020 = 1020, + MODEL_SYSMOBTS_2050 = 2050, +}; + +enum sysmobts_net_mode { + NET_MODE_DHCP, + NET_MODE_STATIC, +}; + +#endif diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr.c new file mode 100644 index 0000000..a008073 --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr.c @@ -0,0 +1,336 @@ +/* Main program for SysmoBTS management daemon */ + +/* (C) 2012 by Harald Welte + * (C) 2014 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "misc/sysmobts_misc.h" +#include "misc/sysmobts_mgr.h" +#include "misc/sysmobts_par.h" + +static int bts_type; +static int trx_number; + +static int no_eeprom_write = 0; +static int daemonize = 0; +void *tall_mgr_ctx; + +/* every 6 hours means 365*4 = 1460 EEprom writes per year (max) */ +#define TEMP_TIMER_SECS (6 * 3600) + +/* every 1 hours means 365*24 = 8760 EEprom writes per year (max) */ +#define HOURS_TIMER_SECS (1 * 3600) + +/* the initial state */ +static struct sysmobts_mgr_instance manager = { + .config_file = "sysmobts-mgr.cfg", + .rf_limit = { + .thresh_warn = 60, + .thresh_crit = 78, + }, + .digital_limit = { + .thresh_warn = 60, + .thresh_crit = 78, + }, + .board_limit = { + .thresh_warn = 60, + .thresh_crit = 78, + }, + .pa_limit = { + .thresh_warn = 60, + .thresh_crit = 100, + }, + .action_warn = 0, + .action_crit = TEMP_ACT_PA_OFF, + .state = STATE_NORMAL, +}; + + +static int classify_bts(void) +{ + int rc; + + rc = sysmobts_get_type(&bts_type); + if (rc < 0) { + fprintf(stderr, "Failed to get model number.\n"); + return -1; + } + + rc = sysmobts_get_trx(&trx_number); + if (rc < 0) { + fprintf(stderr, "Failed to get the trx number.\n"); + return -1; + } + + return 0; +} + +int sysmobts_bts_type(void) +{ + return bts_type; +} + +int sysmobts_trx_number(void) +{ + return trx_number; +} + +int is_sbts2050(void) +{ + return bts_type == 2050; +} + +int is_sbts2050_trx(int trx) +{ + return trx_number == trx; +} + +int is_sbts2050_master(void) +{ + if (!is_sbts2050()) + return 0; + if (!is_sbts2050_trx(0)) + return 0; + return 1; +} + +static struct osmo_timer_list temp_timer; +static void check_temp_timer_cb(void *unused) +{ + sysmobts_check_temp(no_eeprom_write); + + osmo_timer_schedule(&temp_timer, TEMP_TIMER_SECS, 0); +} + +static struct osmo_timer_list hours_timer; +static void hours_timer_cb(void *unused) +{ + sysmobts_update_hours(no_eeprom_write); + + osmo_timer_schedule(&hours_timer, HOURS_TIMER_SECS, 0); +} + +static void print_help(void) +{ + printf("sysmobts-mgr [-nsD] [-d cat]\n"); + printf(" -n Do not write to EEPROM\n"); + printf(" -s Disable color\n"); + printf(" -d CAT enable debugging\n"); + printf(" -D daemonize\n"); + printf(" -c Specify the filename of the config file\n"); +} + +static int parse_options(int argc, char **argv) +{ + int opt; + + while ((opt = getopt(argc, argv, "nhsd:c:")) != -1) { + switch (opt) { + case 'n': + no_eeprom_write = 1; + break; + case 'h': + print_help(); + return -1; + 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': + manager.config_file = optarg; + break; + default: + return -1; + } + } + + return 0; +} + +static void signal_handler(int signal) +{ + fprintf(stderr, "signal %u received\n", signal); + + switch (signal) { + case SIGINT: + case SIGTERM: + sysmobts_check_temp(no_eeprom_write); + sysmobts_update_hours(no_eeprom_write); + exit(0); + break; + case SIGABRT: + case SIGUSR1: + case SIGUSR2: + talloc_report_full(tall_mgr_ctx, stderr); + break; + default: + break; + } +} + +static struct log_info_cat mgr_log_info_cat[] = { + [DTEMP] = { + .name = "DTEMP", + .description = "Temperature monitoring", + .color = "\033[1;35m", + .enabled = 1, .loglevel = LOGL_INFO, + }, + [DFW] = { + .name = "DFW", + .description = "DSP/FPGA firmware management", + .color = "\033[1;36m", + .enabled = 1, .loglevel = LOGL_INFO, + }, + [DFIND] = { + .name = "DFIND", + .description = "ipaccess-find handling", + .color = "\033[1;37m", + .enabled = 1, .loglevel = LOGL_INFO, + }, + [DCALIB] = { + .name = "DCALIB", + .description = "Calibration handling", + .color = "\033[1;37m", + .enabled = 1, .loglevel = LOGL_INFO, + }, +}; + +static const struct log_info mgr_log_info = { + .cat = mgr_log_info_cat, + .num_cat = ARRAY_SIZE(mgr_log_info_cat), +}; + +int main(int argc, char **argv) +{ + int rc; + struct ctrl_connection *ccon; + + tall_mgr_ctx = talloc_named_const(NULL, 1, "bts manager"); + msgb_talloc_ctx_init(tall_mgr_ctx, 0); + + srand(time(NULL)); + + osmo_init_logging2(tall_mgr_ctx, &mgr_log_info); + if (classify_bts() != 0) + exit(2); + + osmo_init_ignore_signals(); + signal(SIGINT, &signal_handler); + signal(SIGTERM, &signal_handler); + signal(SIGUSR1, &signal_handler); + signal(SIGUSR2, &signal_handler); + + rc = parse_options(argc, argv); + if (rc < 0) + exit(2); + + sysmobts_mgr_vty_init(); + logging_vty_add_cmds(&mgr_log_info); + rc = sysmobts_mgr_parse_config(&manager); + if (rc < 0) { + LOGP(DFIND, LOGL_FATAL, "Cannot parse config file\n"); + exit(1); + } + + rc = telnet_init(tall_mgr_ctx, NULL, OSMO_VTY_PORT_BTSMGR); + if (rc < 0) { + fprintf(stderr, "Error initializing telnet\n"); + exit(1); + } + + /* start temperature check timer */ + temp_timer.cb = check_temp_timer_cb; + check_temp_timer_cb(NULL); + + /* start operational hours timer */ + hours_timer.cb = hours_timer_cb; + hours_timer_cb(NULL); + + /* start uc temperature check timer */ + sbts2050_uc_initialize(); + + /* handle broadcast messages for ipaccess-find */ + if (sysmobts_mgr_nl_init() != 0) + exit(3); + + /* Initialize the temperature control */ + ccon = osmo_ctrl_conn_alloc(tall_mgr_ctx, NULL); + rc = -1; + if (ccon) { + ccon->write_queue.bfd.data = ccon; + rc = osmo_sock_init_ofd(&ccon->write_queue.bfd, AF_INET, + SOCK_STREAM, IPPROTO_TCP, + "localhost", OSMO_CTRL_PORT_BTS, + OSMO_SOCK_F_CONNECT); + } + if (rc < 0) + LOGP(DLCTRL, LOGL_ERROR, "Can't connect to CTRL @ localhost:%u\n", + OSMO_CTRL_PORT_BTS); + else + LOGP(DLCTRL, LOGL_NOTICE, "CTRL connected to locahost:%u\n", + OSMO_CTRL_PORT_BTS); + + sysmobts_mgr_temp_init(&manager, ccon); + + if (sysmobts_mgr_calib_init(&manager) != 0) + exit(3); + + if (daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + exit(1); + } + } + + + while (1) { + log_reset_context(); + osmo_select_main(0); + } +} diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr.h b/src/osmo-bts-sysmo/misc/sysmobts_mgr.h new file mode 100644 index 0000000..88f4e24 --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr.h @@ -0,0 +1,122 @@ +#ifndef _SYSMOBTS_MGR_H +#define _SYSMOBTS_MGR_H + +#include +#include +#include +#include +#include + +#include + +#include + +enum { + DTEMP, + DFW, + DFIND, + DCALIB, +}; + + +enum { +#if 0 + TEMP_ACT_PWR_CONTRL = 0x1, +#endif + TEMP_ACT_SLAVE_OFF = 0x4, + TEMP_ACT_PA_OFF = 0x8, + TEMP_ACT_BTS_SRV_OFF = 0x10, +}; + +/* actions only for normal state */ +enum { +#if 0 + TEMP_ACT_NORM_PW_CONTRL = 0x1, +#endif + TEMP_ACT_NORM_SLAVE_ON = 0x4, + TEMP_ACT_NORM_PA_ON = 0x8, + TEMP_ACT_NORM_BTS_SRV_ON= 0x10, +}; + +enum sysmobts_temp_state { + STATE_NORMAL, /* Everything is fine */ + STATE_WARNING_HYST, /* Go back to normal next? */ + STATE_WARNING, /* We are above the warning threshold */ + STATE_CRITICAL, /* We have an issue. Wait for below warning */ +}; + +/** + * Temperature Limits. We separate from a threshold + * that will generate a warning and one that is so + * severe that an action will be taken. + */ +struct sysmobts_temp_limit { + int thresh_warn; + int thresh_crit; +}; + +enum mgr_vty_node { + MGR_NODE = _LAST_OSMOVTY_NODE + 1, + + ACT_NORM_NODE, + ACT_WARN_NODE, + ACT_CRIT_NODE, + LIMIT_RF_NODE, + LIMIT_DIGITAL_NODE, + LIMIT_BOARD_NODE, + LIMIT_PA_NODE, +}; + +struct sysmobts_mgr_instance { + const char *config_file; + + struct sysmobts_temp_limit rf_limit; + struct sysmobts_temp_limit digital_limit; + + /* Only available on sysmobts 2050 */ + struct sysmobts_temp_limit board_limit; + struct sysmobts_temp_limit pa_limit; + + int action_norm; + int action_warn; + int action_crit; + + enum sysmobts_temp_state state; + + struct { + int initial_calib_started; + int is_up; + struct osmo_timer_list recon_timer; + struct ipa_client_conn *bts_conn; + + int state; + struct osmo_timer_list timer; + uint32_t last_seqno; + + /* gps structure to see if there is a fix */ + int gps_open; + struct osmo_fd gpsfd; + struct gps_data_t gpsdata; + struct osmo_timer_list fix_timeout; + + /* Loop/Re-try control */ + int calib_from_loop; + struct osmo_timer_list calib_timeout; + } calib; +}; + +int sysmobts_mgr_vty_init(void); +int sysmobts_mgr_parse_config(struct sysmobts_mgr_instance *mgr); +int sysmobts_mgr_nl_init(void); +int sysmobts_mgr_temp_init(struct sysmobts_mgr_instance *mgr, + struct ctrl_connection *ctrl); +const char *sysmobts_mgr_temp_get_state(enum sysmobts_temp_state state); + + +int sysmobts_mgr_calib_init(struct sysmobts_mgr_instance *mgr); +int sysmobts_mgr_calib_run(struct sysmobts_mgr_instance *mgr); + + +extern void *tall_mgr_ctx; + +#endif diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_2050.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_2050.c new file mode 100644 index 0000000..12961e3 --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_2050.c @@ -0,0 +1,384 @@ +/* (C) 2014 by 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include "sysmobts_misc.h" +#include "sysmobts_par.h" +#include "sysmobts_mgr.h" +#include "btsconfig.h" + +#include +#include +#include +#include + +#include +#include +#include + +#ifdef BUILD_SBTS2050 +#include + +#define SERIAL_ALLOC_SIZE 300 +#define SIZE_HEADER_RSP 5 +#define SIZE_HEADER_CMD 4 + +struct uc { + int id; + int fd; + const char *path; +}; + +struct ucinfo { + uint16_t id; + int master; + int slave; + int pa; +}; + +static struct uc ucontrol0 = { + .id = 0, + .path = "/dev/ttyS0", + .fd = -1, +}; + +/********************************************************************** + * Functions read/write from serial interface + *********************************************************************/ +static int hand_serial_read(int fd, struct msgb *msg, int numbytes) +{ + int rc, bread = 0; + + if (numbytes > msgb_tailroom(msg)) + return -ENOSPC; + + while (bread < numbytes) { + rc = read(fd, msg->tail, numbytes - bread); + if (rc < 0) + return -1; + if (rc == 0) + break; + + bread += rc; + msgb_put(msg, rc); + } + + return bread; +} + +static int hand_serial_write(int fd, struct msgb *msg) +{ + int rc, bwritten = 0; + + while (msg->len > 0) { + rc = write(fd, msg->data, msg->len); + if (rc <= 0) + return -1; + + msgb_pull(msg, rc); + bwritten += rc; + } + + return bwritten; +} + +/********************************************************************** + * Functions request information to Microcontroller + *********************************************************************/ +static void add_parity(cmdpkt_t *command) +{ + int n; + uint8_t parity = 0x00; + for (n = 0; n < SIZE_HEADER_CMD+command->u8Len; n++) + parity ^= ((uint8_t *)command)[n]; + + command->cmd.raw[command->u8Len] = parity; +} + +static struct msgb *sbts2050_ucinfo_sndrcv(struct uc *ucontrol, const struct ucinfo *info) +{ + int num, rc; + cmdpkt_t *command; + rsppkt_t *response; + struct msgb *msg; + fd_set fdread; + struct timeval tout = { + .tv_sec = 10, + }; + + switch (info->id) { + case SBTS2050_TEMP_RQT: + num = sizeof(command->cmd.tempGet); + break; + case SBTS2050_PWR_RQT: + num = sizeof(command->cmd.pwrSetState); + break; + case SBTS2050_PWR_STATUS: + num = sizeof(command->cmd.pwrGetStatus); + break; + default: + return NULL; + } + num = num + SIZE_HEADER_CMD+1; + + msg = msgb_alloc(SERIAL_ALLOC_SIZE, "Message Microcontroller"); + if (msg == NULL) { + LOGP(DTEMP, LOGL_ERROR, "Error creating msg\n"); + return NULL; + } + command = (cmdpkt_t *) msgb_put(msg, num); + + command->u16Magic = 0xCAFE; + switch (info->id) { + case SBTS2050_TEMP_RQT: + command->u8Id = info->id; + command->u8Len = sizeof(command->cmd.tempGet); + break; + case SBTS2050_PWR_RQT: + command->u8Id = info->id; + command->u8Len = sizeof(command->cmd.pwrSetState); + command->cmd.pwrSetState.u1MasterEn = !!info->master; + command->cmd.pwrSetState.u1SlaveEn = !!info->slave; + command->cmd.pwrSetState.u1PwrAmpEn = !!info->pa; + break; + case SBTS2050_PWR_STATUS: + command->u8Id = info->id; + command->u8Len = sizeof(command->cmd.pwrGetStatus); + break; + default: + goto err; + } + + add_parity(command); + + if (hand_serial_write(ucontrol->fd, msg) < 0) + goto err; + + msgb_reset(msg); + + FD_ZERO(&fdread); + FD_SET(ucontrol->fd, &fdread); + + num = SIZE_HEADER_RSP; + while (1) { + rc = select(ucontrol->fd+1, &fdread, NULL, NULL, &tout); + if (rc > 0) { + if (hand_serial_read(ucontrol->fd, msg, num) < 0) + goto err; + + response = (rsppkt_t *)msg->data; + + if (response->u8Id != info->id || msg->len <= 0 || + response->i8Error != RQT_SUCCESS) + goto err; + + if (msg->len == SIZE_HEADER_RSP + response->u8Len + 1) + break; + + num = response->u8Len + 1; + } else + goto err; + } + + return msg; + +err: + msgb_free(msg); + return NULL; +} + +/********************************************************************** + * Get power status function + *********************************************************************/ +int sbts2050_uc_get_status(struct sbts2050_power_status *status) +{ + struct msgb *msg; + const struct ucinfo info = { + .id = SBTS2050_PWR_STATUS, + }; + rsppkt_t *response; + + memset(status, 0, sizeof(*status)); + msg = sbts2050_ucinfo_sndrcv(&ucontrol0, &info); + + if (msg == NULL) { + LOGP(DTEMP, LOGL_ERROR, + "Error requesting power status.\n"); + return -1; + } + + response = (rsppkt_t *)msg->data; + + status->main_supply_current = response->rsp.pwrGetStatus.u8MainSupplyA / 64.f; + + status->master_enabled = response->rsp.pwrGetStatus.u1MasterEn; + status->master_voltage = response->rsp.pwrGetStatus.u8MasterV / 32.f; + status->master_current = response->rsp.pwrGetStatus.u8MasterA / 64.f;; + + status->slave_enabled = response->rsp.pwrGetStatus.u1SlaveEn; + status->slave_voltage = response->rsp.pwrGetStatus.u8SlaveV / 32.f; + status->slave_current = response->rsp.pwrGetStatus.u8SlaveA / 64.f; + + status->pa_enabled = response->rsp.pwrGetStatus.u1PwrAmpEn; + status->pa_voltage = response->rsp.pwrGetStatus.u8PwrAmpV / 4.f; + status->pa_current = response->rsp.pwrGetStatus.u8PwrAmpA / 64.f; + + status->pa_bias_voltage = response->rsp.pwrGetStatus.u8PwrAmpBiasV / 16.f; + + msgb_free(msg); + return 0; +} + +/********************************************************************** + * Uc Power Switching handling + *********************************************************************/ +int sbts2050_uc_set_power(int pmaster, int pslave, int ppa) +{ + struct msgb *msg; + const struct ucinfo info = { + .id = SBTS2050_PWR_RQT, + .master = pmaster, + .slave = pslave, + .pa = ppa + }; + + msg = sbts2050_ucinfo_sndrcv(&ucontrol0, &info); + + if (msg == NULL) { + LOGP(DTEMP, LOGL_ERROR, "Error switching off some unit.\n"); + return -1; + } + + LOGP(DTEMP, LOGL_DEBUG, "Switch off/on success:\n" + "MASTER %s\n" + "SLAVE %s\n" + "PA %s\n", + pmaster ? "ON" : "OFF", + pslave ? "ON" : "OFF", + ppa ? "ON" : "OFF"); + + msgb_free(msg); + return 0; +} + +/********************************************************************** + * Uc temperature handling + *********************************************************************/ +int sbts2050_uc_check_temp(int *temp_pa, int *temp_board) +{ + rsppkt_t *response; + struct msgb *msg; + const struct ucinfo info = { + .id = SBTS2050_TEMP_RQT, + }; + + msg = sbts2050_ucinfo_sndrcv(&ucontrol0, &info); + + if (msg == NULL) { + LOGP(DTEMP, LOGL_ERROR, "Error reading temperature\n"); + return -1; + } + + response = (rsppkt_t *)msg->data; + + *temp_board = response->rsp.tempGet.i8BrdTemp; + *temp_pa = response->rsp.tempGet.i8PaTemp; + + LOGP(DTEMP, LOGL_DEBUG, "Temperature Board: %+3d C, " + "Tempeture PA: %+3d C\n", + response->rsp.tempGet.i8BrdTemp, + response->rsp.tempGet.i8PaTemp); + msgb_free(msg); + return 0; +} + +void sbts2050_uc_initialize(void) +{ + if (!is_sbts2050()) + return; + + ucontrol0.fd = osmo_serial_init(ucontrol0.path, 115200); + if (ucontrol0.fd < 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to open the serial interface\n"); + return; + } + + if (is_sbts2050_master()) { + LOGP(DTEMP, LOGL_NOTICE, "Going to enable the PA.\n"); + sbts2050_uc_set_pa_power(1); + } +} + +int sbts2050_uc_set_pa_power(int on_off) +{ + struct sbts2050_power_status status; + if (sbts2050_uc_get_status(&status) != 0) { + LOGP(DTEMP, LOGL_ERROR, "Failed to read current power status.\n"); + return -1; + } + + return sbts2050_uc_set_power(status.master_enabled, status.slave_enabled, on_off); +} + +int sbts2050_uc_set_slave_power(int on_off) +{ + struct sbts2050_power_status status; + if (sbts2050_uc_get_status(&status) != 0) { + LOGP(DTEMP, LOGL_ERROR, "Failed to read current power status.\n"); + return -1; + } + + return sbts2050_uc_set_power( + status.master_enabled, + on_off, + status.pa_enabled); +} +#else +void sbts2050_uc_initialize(void) +{ + LOGP(DTEMP, LOGL_NOTICE, "sysmoBTS2050 was not enabled at compile time.\n"); +} + +int sbts2050_uc_check_temp(int *temp_pa, int *temp_board) +{ + LOGP(DTEMP, LOGL_ERROR, "sysmoBTS2050 compiled without temp support.\n"); + *temp_pa = *temp_board = 99999; + return -1; +} + +int sbts2050_uc_get_status(struct sbts2050_power_status *status) +{ + memset(status, 0, sizeof(*status)); + LOGP(DTEMP, LOGL_ERROR, "sysmoBTS2050 compiled without status support.\n"); + return -1; +} + +int sbts2050_uc_set_pa_power(int on_off) +{ + LOGP(DTEMP, LOGL_ERROR, "sysmoBTS2050 compiled without PA support.\n"); + return -1; +} + +int sbts2050_uc_set_slave_power(int on_off) +{ + LOGP(DTEMP, LOGL_ERROR, "sysmoBTS2050 compiled without UC support.\n"); + return -1; +} + +#endif diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_calib.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_calib.c new file mode 100644 index 0000000..62509f2 --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_calib.c @@ -0,0 +1,538 @@ +/* OCXO/TCXO calibration control for SysmoBTS management daemon */ + +/* + * (C) 2014,2015 by Holger Hans Peter Freyther + * (C) 2014 by Harald Welte for the IPA code from the oml router + * + * 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 . + * + */ + +#include "misc/sysmobts_mgr.h" +#include "misc/sysmobts_misc.h" +#include "osmo-bts/msg_utils.h" + +#include +#include + +#include + +#include +#include + +#include +#include +#include + +static int calib_run(struct sysmobts_mgr_instance *mgr, int from_loop); +static void calib_state_reset(struct sysmobts_mgr_instance *mgr, int reason); +static void request_clock_reset(struct sysmobts_mgr_instance *mgr); +static void bts_updown_cb(struct ipa_client_conn *link, int up); + +enum calib_state { + CALIB_INITIAL, + CALIB_GPS_WAIT_FOR_FIX, + CALIB_CTR_RESET, + CALIB_CTR_WAIT, + CALIB_COR_SET, +}; + +enum calib_result { + CALIB_FAIL_START, + CALIB_FAIL_GPS, + CALIB_FAIL_CTRL, + CALIB_SUCESS, +}; + +static void calib_loop_run(void *_data) +{ + int rc; + struct sysmobts_mgr_instance *mgr = _data; + + LOGP(DCALIB, LOGL_NOTICE, "Going to calibrate the system.\n"); + rc = calib_run(mgr, 1); + if (rc != 0) + calib_state_reset(mgr, CALIB_FAIL_START); +} + +static void mgr_gps_close(struct sysmobts_mgr_instance *mgr) +{ + if (!mgr->calib.gps_open) + return; + + osmo_timer_del(&mgr->calib.fix_timeout); + + osmo_fd_unregister(&mgr->calib.gpsfd); + gps_close(&mgr->calib.gpsdata); + memset(&mgr->calib.gpsdata, 0, sizeof(mgr->calib.gpsdata)); + mgr->calib.gps_open = 0; +} + +static void mgr_gps_checkfix(struct sysmobts_mgr_instance *mgr) +{ + struct gps_data_t *data = &mgr->calib.gpsdata; + + /* No 2D fix yet */ + if (data->fix.mode < MODE_2D) { + LOGP(DCALIB, LOGL_DEBUG, "Fix mode not enough: %d\n", + data->fix.mode); + return; + } + + /* The trimble driver is broken...add some sanity checking */ + if (data->satellites_used < 1) { + LOGP(DCALIB, LOGL_DEBUG, "Not enough satellites used: %d\n", + data->satellites_used); + return; + } + + LOGP(DCALIB, LOGL_NOTICE, "Got a GPS fix continuing.\n"); + osmo_timer_del(&mgr->calib.fix_timeout); + mgr_gps_close(mgr); + request_clock_reset(mgr); +} + +static int mgr_gps_read(struct osmo_fd *fd, unsigned int what) +{ + int rc; + struct sysmobts_mgr_instance *mgr = fd->data; + + rc = gps_read(&mgr->calib.gpsdata); + if (rc == -1) { + LOGP(DCALIB, LOGL_ERROR, "gpsd vanished during read.\n"); + calib_state_reset(mgr, CALIB_FAIL_GPS); + return -1; + } + + if (rc > 0) + mgr_gps_checkfix(mgr); + return 0; +} + +static void mgr_gps_fix_timeout(void *_data) +{ + struct sysmobts_mgr_instance *mgr = _data; + + LOGP(DCALIB, LOGL_ERROR, "Failed to acquire GPRS fix.\n"); + calib_state_reset(mgr, CALIB_FAIL_GPS); +} + +static void mgr_gps_open(struct sysmobts_mgr_instance *mgr) +{ + int rc; + + rc = gps_open("localhost", DEFAULT_GPSD_PORT, &mgr->calib.gpsdata); + if (rc != 0) { + LOGP(DCALIB, LOGL_ERROR, "Failed to connect to GPS %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_GPS); + return; + } + + mgr->calib.gps_open = 1; + gps_stream(&mgr->calib.gpsdata, WATCH_ENABLE, NULL); + + mgr->calib.gpsfd.data = mgr; + mgr->calib.gpsfd.cb = mgr_gps_read; + mgr->calib.gpsfd.when = BSC_FD_READ | BSC_FD_EXCEPT; + mgr->calib.gpsfd.fd = mgr->calib.gpsdata.gps_fd; + if (osmo_fd_register(&mgr->calib.gpsfd) < 0) { + LOGP(DCALIB, LOGL_ERROR, "Failed to register GPSD fd\n"); + calib_state_reset(mgr, CALIB_FAIL_GPS); + } + + mgr->calib.state = CALIB_GPS_WAIT_FOR_FIX; + mgr->calib.fix_timeout.data = mgr; + mgr->calib.fix_timeout.cb = mgr_gps_fix_timeout; + osmo_timer_schedule(&mgr->calib.fix_timeout, 60, 0); + LOGP(DCALIB, LOGL_NOTICE, + "Opened the GPSD connection waiting for fix: %d\n", + mgr->calib.gpsfd.fd); +} + +static void send_ctrl_cmd(struct sysmobts_mgr_instance *mgr, + struct msgb *msg) +{ + ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL); + ipa_prepend_header(msg, IPAC_PROTO_OSMO); + ipa_client_conn_send(mgr->calib.bts_conn, msg); +} + +static void send_set_ctrl_cmd_int(struct sysmobts_mgr_instance *mgr, + const char *key, const int val) +{ + struct msgb *msg; + int ret; + + msg = msgb_alloc_headroom(1024, 128, "CTRL SET"); + ret = snprintf((char *) msg->data, 4096, "SET %u %s %d", + mgr->calib.last_seqno++, key, val); + msg->l2h = msgb_put(msg, ret); + return send_ctrl_cmd(mgr, msg); +} + +static void send_set_ctrl_cmd(struct sysmobts_mgr_instance *mgr, + const char *key, const char *val) +{ + struct msgb *msg; + int ret; + + msg = msgb_alloc_headroom(1024, 128, "CTRL SET"); + ret = snprintf((char *) msg->data, 4096, "SET %u %s %s", + mgr->calib.last_seqno++, key, val); + msg->l2h = msgb_put(msg, ret); + return send_ctrl_cmd(mgr, msg); +} + +static void send_get_ctrl_cmd(struct sysmobts_mgr_instance *mgr, + const char *key) +{ + struct msgb *msg; + int ret; + + msg = msgb_alloc_headroom(1024, 128, "CTRL GET"); + ret = snprintf((char *) msg->data, 4096, "GET %u %s", + mgr->calib.last_seqno++, key); + msg->l2h = msgb_put(msg, ret); + return send_ctrl_cmd(mgr, msg); +} + +static int calib_run(struct sysmobts_mgr_instance *mgr, int from_loop) +{ + if (!mgr->calib.is_up) { + LOGP(DCALIB, LOGL_ERROR, "Control interface not connected.\n"); + return -1; + } + + if (mgr->calib.state != CALIB_INITIAL) { + LOGP(DCALIB, LOGL_ERROR, "Calib is already in progress.\n"); + return -2; + } + + mgr->calib.calib_from_loop = from_loop; + + /* From now on everything will be handled from the failure */ + mgr->calib.initial_calib_started = 1; + mgr_gps_open(mgr); + return 0; +} + +int sysmobts_mgr_calib_run(struct sysmobts_mgr_instance *mgr) +{ + return calib_run(mgr, 0); +} + +static void request_clock_reset(struct sysmobts_mgr_instance *mgr) +{ + send_set_ctrl_cmd(mgr, "trx.0.clock-info", "1"); + mgr->calib.state = CALIB_CTR_RESET; +} + +static void calib_state_reset(struct sysmobts_mgr_instance *mgr, int outcome) +{ + if (mgr->calib.calib_from_loop) { + /* + * In case of success calibrate in two hours again + * and in case of a failure in some minutes. + */ + int timeout = 2 * 60 * 60; + if (outcome != CALIB_SUCESS) + timeout = 5 * 60; + + mgr->calib.calib_timeout.data = mgr; + mgr->calib.calib_timeout.cb = calib_loop_run; + osmo_timer_schedule(&mgr->calib.calib_timeout, timeout, 0); + } + + mgr->calib.state = CALIB_INITIAL; + osmo_timer_del(&mgr->calib.timer); + + mgr_gps_close(mgr); +} + +static void calib_get_clock_err_cb(void *_data) +{ + struct sysmobts_mgr_instance *mgr = _data; + + LOGP(DCALIB, LOGL_DEBUG, + "Requesting current clock-info.\n"); + send_get_ctrl_cmd(mgr, "trx.0.clock-info"); +} + +static void handle_ctrl_reset_resp( + struct sysmobts_mgr_instance *mgr, + struct ctrl_cmd *cmd) +{ + if (strcmp(cmd->variable, "trx.0.clock-info") != 0) { + LOGP(DCALIB, LOGL_ERROR, + "Unexpected variable: %s\n", cmd->variable); + calib_state_reset(mgr, CALIB_FAIL_CTRL); + return; + } + + if (strcmp(cmd->reply, "success") != 0) { + LOGP(DCALIB, LOGL_ERROR, + "Unexpected reply: %s\n", cmd->variable); + calib_state_reset(mgr, CALIB_FAIL_CTRL); + return; + } + + mgr->calib.state = CALIB_CTR_WAIT; + mgr->calib.timer.cb = calib_get_clock_err_cb; + mgr->calib.timer.data = mgr; + osmo_timer_schedule(&mgr->calib.timer, 60, 0); + LOGP(DCALIB, LOGL_DEBUG, + "Reset the calibration counter. Waiting 60 seconds.\n"); +} + +static void handle_ctrl_get_resp( + struct sysmobts_mgr_instance *mgr, + struct ctrl_cmd *cmd) +{ + char *saveptr = NULL; + char *clk_cur; + char *clk_src; + char *cal_err; + char *cal_res; + char *cal_src; + int cal_err_int; + + if (strcmp(cmd->variable, "trx.0.clock-info") != 0) { + LOGP(DCALIB, LOGL_ERROR, + "Unexpected variable: %s\n", cmd->variable); + calib_state_reset(mgr, CALIB_FAIL_CTRL); + return; + } + + clk_cur = strtok_r(cmd->reply, ",", &saveptr); + clk_src = strtok_r(NULL, ",", &saveptr); + cal_err = strtok_r(NULL, ",", &saveptr); + cal_res = strtok_r(NULL, ",", &saveptr); + cal_src = strtok_r(NULL, ",", &saveptr); + + if (!clk_cur || !clk_src || !cal_err || !cal_res || !cal_src) { + LOGP(DCALIB, LOGL_ERROR, "Parse error on clock-info reply\n"); + calib_state_reset(mgr, CALIB_FAIL_CTRL); + return; + + } + cal_err_int = atoi(cal_err); + LOGP(DCALIB, LOGL_NOTICE, + "Calibration CUR(%s) SRC(%s) ERR(%s/%d) RES(%s) SRC(%s)\n", + clk_cur, clk_src, cal_err, cal_err_int, cal_res, cal_src); + + if (strcmp(cal_res, "0") == 0) { + LOGP(DCALIB, LOGL_ERROR, "Invalid clock resolution. Giving up\n"); + calib_state_reset(mgr, CALIB_FAIL_CTRL); + return; + } + + /* Now we can finally set the new value */ + LOGP(DCALIB, LOGL_NOTICE, + "Going to apply %d as new clock correction.\n", + -cal_err_int); + send_set_ctrl_cmd_int(mgr, "trx.0.clock-correction", -cal_err_int); + mgr->calib.state = CALIB_COR_SET; +} + +static void handle_ctrl_set_cor( + struct sysmobts_mgr_instance *mgr, + struct ctrl_cmd *cmd) +{ + if (strcmp(cmd->variable, "trx.0.clock-correction") != 0) { + LOGP(DCALIB, LOGL_ERROR, + "Unexpected variable: %s\n", cmd->variable); + calib_state_reset(mgr, CALIB_FAIL_CTRL); + return; + } + + if (strcmp(cmd->reply, "success") != 0) { + LOGP(DCALIB, LOGL_ERROR, + "Unexpected reply: %s\n", cmd->variable); + calib_state_reset(mgr, CALIB_FAIL_CTRL); + return; + } + + LOGP(DCALIB, LOGL_NOTICE, + "Calibration process completed\n"); + calib_state_reset(mgr, CALIB_SUCESS); +} + +static void handle_ctrl(struct sysmobts_mgr_instance *mgr, struct msgb *msg) +{ + struct ctrl_cmd *cmd = ctrl_cmd_parse(tall_mgr_ctx, msg); + if (!cmd) { + LOGP(DCALIB, LOGL_ERROR, "Failed to parse command/response\n"); + return; + } + + switch (cmd->type) { + case CTRL_TYPE_GET_REPLY: + switch (mgr->calib.state) { + case CALIB_CTR_WAIT: + handle_ctrl_get_resp(mgr, cmd); + break; + default: + LOGP(DCALIB, LOGL_ERROR, + "Unhandled response in state: %d %s/%s\n", + mgr->calib.state, cmd->variable, cmd->reply); + calib_state_reset(mgr, CALIB_FAIL_CTRL); + break; + }; + break; + case CTRL_TYPE_SET_REPLY: + switch (mgr->calib.state) { + case CALIB_CTR_RESET: + handle_ctrl_reset_resp(mgr, cmd); + break; + case CALIB_COR_SET: + handle_ctrl_set_cor(mgr, cmd); + break; + default: + LOGP(DCALIB, LOGL_ERROR, + "Unhandled response in state: %d %s/%s\n", + mgr->calib.state, cmd->variable, cmd->reply); + calib_state_reset(mgr, CALIB_FAIL_CTRL); + break; + }; + break; + case CTRL_TYPE_TRAP: + /* ignore any form of trap */ + break; + default: + LOGP(DCALIB, LOGL_ERROR, + "Unhandled CTRL response: %d. Resetting state\n", + cmd->type); + calib_state_reset(mgr, CALIB_FAIL_CTRL); + break; + } + + talloc_free(cmd); +} + +/* Schedule a connect towards the BTS */ +static void schedule_bts_connect(struct sysmobts_mgr_instance *mgr) +{ + DEBUGP(DLCTRL, "Scheduling BTS connect\n"); + osmo_timer_schedule(&mgr->calib.recon_timer, 1, 0); +} + +/* BTS re-connect timer call-back */ +static void bts_recon_timer_cb(void *data) +{ + int rc; + struct sysmobts_mgr_instance *mgr = data; + + /* The connection failures are to be expected during boot */ + mgr->calib.bts_conn->ofd->when |= BSC_FD_WRITE; + rc = ipa_client_conn_open(mgr->calib.bts_conn); + if (rc < 0) { + LOGP(DLCTRL, LOGL_NOTICE, "Failed to connect to BTS.\n"); + schedule_bts_connect(mgr); + } +} + +static int bts_read_cb(struct ipa_client_conn *link, struct msgb *msg) +{ + int rc; + struct ipaccess_head *hh = (struct ipaccess_head *) msgb_l1(msg); + struct ipaccess_head_ext *hh_ext; + + DEBUGP(DCALIB, "Received data from BTS: %s\n", + osmo_hexdump(msgb_data(msg), msgb_length(msg))); + + /* regular message handling */ + rc = msg_verify_ipa_structure(msg); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Invalid IPA message from BTS (rc=%d)\n", rc); + goto err; + } + + switch (hh->proto) { + case IPAC_PROTO_IPACCESS: + /* handle the core IPA CCM messages in libosmoabis */ + ipa_ccm_rcvmsg_bts_base(msg, link->ofd); + msgb_free(msg); + break; + case IPAC_PROTO_OSMO: + hh_ext = (struct ipaccess_head_ext *) hh->data; + switch (hh_ext->proto) { + case IPAC_PROTO_EXT_CTRL: + handle_ctrl(link->data, msg); + break; + default: + LOGP(DCALIB, LOGL_NOTICE, + "Unhandled osmo ID %u from BTS\n", hh_ext->proto); + }; + msgb_free(msg); + break; + default: + LOGP(DCALIB, LOGL_NOTICE, + "Unhandled stream ID %u from BTS\n", hh->proto); + msgb_free(msg); + break; + } + + return 0; +err: + msgb_free(msg); + return -1; +} + +/* link to BSC has gone up or down */ +static void bts_updown_cb(struct ipa_client_conn *link, int up) +{ + struct sysmobts_mgr_instance *mgr = link->data; + + LOGP(DLCTRL, LOGL_INFO, "BTS connection %s\n", up ? "up" : "down"); + + if (up) { + mgr->calib.is_up = 1; + mgr->calib.last_seqno = 0; + + if (!mgr->calib.initial_calib_started) + calib_run(mgr, 1); + } else { + mgr->calib.is_up = 0; + schedule_bts_connect(mgr); + calib_state_reset(mgr, CALIB_FAIL_CTRL); + } +} + +int sysmobts_mgr_calib_init(struct sysmobts_mgr_instance *mgr) +{ + if (!is_sbts2050_master()) { + LOGP(DCALIB, LOGL_NOTICE, + "Calib is only possible on the sysmoBTS2050 master\n"); + return 0; + } + + mgr->calib.bts_conn = ipa_client_conn_create(tall_mgr_ctx, NULL, 0, + "localhost", 4238, + bts_updown_cb, bts_read_cb, + NULL, mgr); + if (!mgr->calib.bts_conn) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to create IPA connection\n"); + return -1; + } + + mgr->calib.recon_timer.cb = bts_recon_timer_cb; + mgr->calib.recon_timer.data = mgr; + schedule_bts_connect(mgr); + + return 0; +} diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_nl.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_nl.c new file mode 100644 index 0000000..48a0312 --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_nl.c @@ -0,0 +1,186 @@ +/* NetworkListen for SysmoBTS management daemon */ + +/* + * (C) 2014 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include "misc/sysmobts_mgr.h" +#include "misc/sysmobts_misc.h" +#include "misc/sysmobts_nl.h" +#include "misc/sysmobts_par.h" + +#include + +#include + +#include +#include +#include + +#include + +#include +#include + +#include +#include + +static struct osmo_fd nl_fd; + +/* + * The TLV structure in IPA messages in UDP packages is a bit + * weird. First the header appears to have an extra NULL byte + * and second the L16 of the L16TV needs to include +1 for the + * tag. The default msgb/tlv and libosmo-abis routines do not + * provide this. + */ + +static void ipaccess_prepend_header_quirk(struct msgb *msg, int proto) +{ + struct ipaccess_head *hh; + + /* prepend the ip.access header */ + hh = (struct ipaccess_head *) msgb_push(msg, sizeof(*hh) + 1); + hh->len = htons(msg->len - sizeof(*hh) - 1); + hh->proto = proto; +} + +static void quirk_l16tv_put(struct msgb *msg, uint16_t len, uint8_t tag, + const uint8_t *val) +{ + uint8_t *buf = msgb_put(msg, len + 2 + 1); + + *buf++ = (len + 1) >> 8; + *buf++ = (len + 1) & 0xff; + *buf++ = tag; + memcpy(buf, val, len); +} + +/* + * We don't look at the content of the request yet and lie + * about most of the responses. + */ +static void respond_to(struct sockaddr_in *src, struct osmo_fd *fd, + uint8_t *data, size_t len) +{ + static int fetched_info = 0; + static char mac_str[20] = {0, }; + static char *model_name; + static char ser_str[20] = {0, }; + + struct sockaddr_in loc_addr; + int rc; + char loc_ip[INET_ADDRSTRLEN]; + struct msgb *msg = msgb_alloc_headroom(512, 128, "ipa get response"); + if (!msg) { + LOGP(DFIND, LOGL_ERROR, "Failed to allocate msgb\n"); + return; + } + + if (!fetched_info) { + uint8_t mac[6]; + int serno; + + /* fetch the MAC */ + sysmobts_par_get_buf(SYSMOBTS_PAR_MAC, mac, sizeof(mac)); + snprintf(mac_str, sizeof(mac_str), "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", + mac[0], mac[1], mac[2], + mac[3], mac[4], mac[5]); + + /* fetch the serial number */ + sysmobts_par_get_int(SYSMOBTS_PAR_SERNR, &serno); + snprintf(ser_str, sizeof(ser_str), "%d", serno); + + /* fetch the model and trx number */ + model_name = sysmobts_model(sysmobts_bts_type(), sysmobts_trx_number()); + fetched_info = 1; + } + + if (source_for_dest(&src->sin_addr, &loc_addr.sin_addr) != 0) { + LOGP(DFIND, LOGL_ERROR, "Failed to determine local source\n"); + return; + } + + msgb_put_u8(msg, IPAC_MSGT_ID_RESP); + + /* append MAC addr */ + quirk_l16tv_put(msg, strlen(mac_str) + 1, IPAC_IDTAG_MACADDR, (uint8_t *) mac_str); + + /* append ip address */ + inet_ntop(AF_INET, &loc_addr.sin_addr, loc_ip, sizeof(loc_ip)); + quirk_l16tv_put(msg, strlen(loc_ip) + 1, IPAC_IDTAG_IPADDR, (uint8_t *) loc_ip); + + /* append the serial number */ + quirk_l16tv_put(msg, strlen(ser_str) + 1, IPAC_IDTAG_SERNR, (uint8_t *) ser_str); + + /* abuse some flags */ + quirk_l16tv_put(msg, strlen(model_name) + 1, IPAC_IDTAG_UNIT, (uint8_t *) model_name); + + /* ip.access nanoBTS would reply to port==3006 */ + ipaccess_prepend_header_quirk(msg, IPAC_PROTO_IPACCESS); + rc = sendto(fd->fd, msg->data, msg->len, 0, (struct sockaddr *)src, sizeof(*src)); + if (rc != msg->len) + LOGP(DFIND, LOGL_ERROR, + "Failed to send with rc(%d) errno(%d)\n", rc, errno); +} + +static int ipaccess_bcast(struct osmo_fd *fd, unsigned int what) +{ + uint8_t data[2048]; + char src[INET_ADDRSTRLEN]; + struct sockaddr_in addr = {}; + socklen_t len = sizeof(addr); + int rc; + + rc = recvfrom(fd->fd, data, sizeof(data), 0, + (struct sockaddr *) &addr, &len); + if (rc <= 0) { + LOGP(DFIND, LOGL_ERROR, + "Failed to read from socket errno(%d)\n", errno); + return -1; + } + + LOGP(DFIND, LOGL_DEBUG, + "Received request from: %s size %d\n", + inet_ntop(AF_INET, &addr.sin_addr, src, sizeof(src)), rc); + + if (rc < 6) + return 0; + + if (data[2] != IPAC_PROTO_IPACCESS || data[4] != IPAC_MSGT_ID_GET) + return 0; + + respond_to(&addr, fd, data + 6, rc - 6); + return 0; +} + +int sysmobts_mgr_nl_init(void) +{ + int rc; + + nl_fd.cb = ipaccess_bcast; + rc = osmo_sock_init_ofd(&nl_fd, AF_INET, SOCK_DGRAM, IPPROTO_UDP, + "0.0.0.0", 3006, OSMO_SOCK_F_BIND); + if (rc < 0) { + perror("Socket creation"); + return -1; + } + + return 0; +} diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_temp.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_temp.c new file mode 100644 index 0000000..1be56ac --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_temp.c @@ -0,0 +1,321 @@ +/* Temperature control for SysmoBTS management daemon */ + +/* + * (C) 2014 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include "misc/sysmobts_mgr.h" +#include "misc/sysmobts_misc.h" + +#include + +#include +#include + +#include + +static struct sysmobts_mgr_instance *s_mgr; +static struct osmo_timer_list temp_ctrl_timer; + +static const struct value_string state_names[] = { + { STATE_NORMAL, "NORMAL" }, + { STATE_WARNING_HYST, "WARNING (HYST)" }, + { STATE_WARNING, "WARNING" }, + { STATE_CRITICAL, "CRITICAL" }, + { 0, NULL } +}; + +const char *sysmobts_mgr_temp_get_state(enum sysmobts_temp_state state) +{ + return get_value_string(state_names, state); +} + +static int next_state(enum sysmobts_temp_state current_state, int critical, int warning) +{ + int next_state = -1; + switch (current_state) { + case STATE_NORMAL: + if (critical) + next_state = STATE_CRITICAL; + else if (warning) + next_state = STATE_WARNING; + break; + case STATE_WARNING_HYST: + if (critical) + next_state = STATE_CRITICAL; + else if (warning) + next_state = STATE_WARNING; + else + next_state = STATE_NORMAL; + break; + case STATE_WARNING: + if (critical) + next_state = STATE_CRITICAL; + else if (!warning) + next_state = STATE_WARNING_HYST; + break; + case STATE_CRITICAL: + if (!critical && !warning) + next_state = STATE_WARNING; + break; + }; + + return next_state; +} + +static void handle_normal_actions(int actions) +{ + /* switch off the PA */ + if (actions & TEMP_ACT_NORM_PA_ON) { + if (!is_sbts2050()) { + LOGP(DTEMP, LOGL_NOTICE, + "PA can only be switched-on on the master\n"); + } else if (sbts2050_uc_set_pa_power(1) != 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to switch on the PA\n"); + } else { + LOGP(DTEMP, LOGL_NOTICE, + "Switched on the PA as normal action.\n"); + } + } + + if (actions & TEMP_ACT_NORM_SLAVE_ON) { + if (!is_sbts2050()) { + LOGP(DTEMP, LOGL_NOTICE, + "Slave on only possible on the sysmoBTS2050\n"); + } else if (sbts2050_uc_set_slave_power(1) != 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to switch on the slave BTS\n"); + } else { + LOGP(DTEMP, LOGL_NOTICE, + "Switched on the slave as normal action.\n"); + } + } + + if (actions & TEMP_ACT_NORM_BTS_SRV_ON) { + LOGP(DTEMP, LOGL_NOTICE, + "Going to switch on the BTS service\n"); + /* + * TODO: use/create something like nspawn that serializes + * and used SIGCHLD/waitpid to pick up the dead processes + * without invoking shell. + */ + system("/bin/systemctl start osmo-bts-sysmo"); + } +} + +static void handle_actions(int actions) +{ + /* switch off the PA */ + if (actions & TEMP_ACT_PA_OFF) { + if (!is_sbts2050()) { + LOGP(DTEMP, LOGL_NOTICE, + "PA can only be switched-off on the master\n"); + } else if (sbts2050_uc_set_pa_power(0) != 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to switch off the PA. Stop BTS?\n"); + } else { + LOGP(DTEMP, LOGL_NOTICE, + "Switched off the PA due temperature.\n"); + } + } + + if (actions & TEMP_ACT_SLAVE_OFF) { + if (!is_sbts2050()) { + LOGP(DTEMP, LOGL_NOTICE, + "Slave off only possible on the sysmoBTS2050\n"); + } else if (sbts2050_uc_set_slave_power(0) != 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to switch off the slave BTS\n"); + } else { + LOGP(DTEMP, LOGL_NOTICE, + "Switched off the slave due temperature\n"); + } + } + + if (actions & TEMP_ACT_BTS_SRV_OFF) { + LOGP(DTEMP, LOGL_NOTICE, + "Going to switch off the BTS service\n"); + /* + * TODO: use/create something like nspawn that serializes + * and used SIGCHLD/waitpid to pick up the dead processes + * without invoking shell. + */ + system("/bin/systemctl stop osmo-bts-sysmo"); + } +} + +/** + * Go back to normal! Depending on the configuration execute the normal + * actions that could (start to) undo everything we did in the other + * states. What is still missing is the power increase/decrease depending + * on the state. E.g. starting from WARNING_HYST we might want to slowly + * ramp up the output power again. + */ +static void execute_normal_act(struct sysmobts_mgr_instance *manager) +{ + LOGP(DTEMP, LOGL_NOTICE, "System is back to normal temperature.\n"); + handle_normal_actions(manager->action_norm); +} + +static void execute_warning_act(struct sysmobts_mgr_instance *manager) +{ + LOGP(DTEMP, LOGL_NOTICE, "System has reached temperature warning.\n"); + handle_actions(manager->action_warn); +} + +static void execute_critical_act(struct sysmobts_mgr_instance *manager) +{ + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical warning.\n"); + handle_actions(manager->action_crit); +} + +static void sysmobts_mgr_temp_handle(struct sysmobts_mgr_instance *manager, + struct ctrl_connection *ctrl, int critical, + int warning) +{ + int new_state = next_state(manager->state, critical, warning); + struct ctrl_cmd *rep; + char *oml_alert = NULL; + + /* Nothing changed */ + if (new_state < 0) + return; + + LOGP(DTEMP, LOGL_NOTICE, "Moving from state %s to %s.\n", + get_value_string(state_names, manager->state), + get_value_string(state_names, new_state)); + manager->state = new_state; + switch (manager->state) { + case STATE_NORMAL: + execute_normal_act(manager); + break; + case STATE_WARNING_HYST: + /* do nothing? Maybe start to increase transmit power? */ + break; + case STATE_WARNING: + execute_warning_act(manager); + oml_alert = "Temperature Warning"; + break; + case STATE_CRITICAL: + execute_critical_act(manager); + oml_alert = "Temperature Critical"; + break; + }; + + if (!oml_alert) + return; + + rep = ctrl_cmd_create(tall_mgr_ctx, CTRL_TYPE_SET); + if (!rep) { + LOGP(DTEMP, LOGL_ERROR, "OML alert creation failed for %s.\n", + oml_alert); + return; + } + + rep->id = talloc_asprintf(rep, "%d", rand()); + rep->variable = "oml-alert"; + rep->value = oml_alert; + LOGP(DTEMP, LOGL_ERROR, "OML alert sent: %d\n", + ctrl_cmd_send(&ctrl->write_queue, rep)); + talloc_free(rep); +} + +static void temp_ctrl_check(struct ctrl_connection *ctrl) +{ + int rc; + int warn_thresh_passed = 0; + int crit_thresh_passed = 0; + + LOGP(DTEMP, LOGL_DEBUG, "Going to check the temperature.\n"); + + /* Read the current digital temperature */ + rc = sysmobts_temp_get(SYSMOBTS_TEMP_DIGITAL, SYSMOBTS_TEMP_INPUT); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to read the digital temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + int temp = rc / 1000; + if (temp > s_mgr->digital_limit.thresh_warn) + warn_thresh_passed = 1; + if (temp > s_mgr->digital_limit.thresh_crit) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "Digital temperature is: %d\n", temp); + } + + /* Read the current RF temperature */ + rc = sysmobts_temp_get(SYSMOBTS_TEMP_RF, SYSMOBTS_TEMP_INPUT); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to read the RF temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + int temp = rc / 1000; + if (temp > s_mgr->rf_limit.thresh_warn) + warn_thresh_passed = 1; + if (temp > s_mgr->rf_limit.thresh_crit) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "RF temperature is: %d\n", temp); + } + + if (is_sbts2050()) { + int temp_pa, temp_board; + + rc = sbts2050_uc_check_temp(&temp_pa, &temp_board); + if (rc != 0) { + /* XXX what do here? */ + LOGP(DTEMP, LOGL_ERROR, + "Failed to read the temperature! Reboot?!\n"); + warn_thresh_passed = 1; + crit_thresh_passed = 1; + } else { + LOGP(DTEMP, LOGL_DEBUG, "SBTS2050 board(%d) PA(%d)\n", + temp_board, temp_pa); + if (temp_pa > s_mgr->pa_limit.thresh_warn) + warn_thresh_passed = 1; + if (temp_pa > s_mgr->pa_limit.thresh_crit) + crit_thresh_passed = 1; + if (temp_board > s_mgr->board_limit.thresh_warn) + warn_thresh_passed = 1; + if (temp_board > s_mgr->board_limit.thresh_crit) + crit_thresh_passed = 1; + } + } + + sysmobts_mgr_temp_handle(s_mgr, ctrl, crit_thresh_passed, + warn_thresh_passed); +} + +static void temp_ctrl_check_cb(void *ctrl) +{ + temp_ctrl_check(ctrl); + /* Check every two minutes? XXX make it configurable! */ + osmo_timer_schedule(&temp_ctrl_timer, 2 * 60, 0); +} + +int sysmobts_mgr_temp_init(struct sysmobts_mgr_instance *mgr, + struct ctrl_connection *ctrl) +{ + s_mgr = mgr; + temp_ctrl_timer.cb = temp_ctrl_check_cb; + temp_ctrl_timer.data = ctrl; + temp_ctrl_check_cb(ctrl); + return 0; +} diff --git a/src/osmo-bts-sysmo/misc/sysmobts_mgr_vty.c b/src/osmo-bts-sysmo/misc/sysmobts_mgr_vty.c new file mode 100644 index 0000000..444ee7c --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_mgr_vty.c @@ -0,0 +1,531 @@ +/* (C) 2014 by sysmocom - s.f.m.c. GmbH + * + * All Rights Reserved + * + * Author: Alvaro Neira Ayuso + * + * 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 . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "sysmobts_misc.h" +#include "sysmobts_mgr.h" +#include "btsconfig.h" + +static struct sysmobts_mgr_instance *s_mgr; + +static const char copyright[] = + "(C) 2012 by Harald Welte \r\n" + "(C) 2014 by Holger Hans Peter Freyther\r\n" + "License AGPLv3+: GNU AGPL version 2 or later \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"; + +static int go_to_parent(struct vty *vty) +{ + switch (vty->node) { + case MGR_NODE: + vty->node = CONFIG_NODE; + break; + case ACT_NORM_NODE: + case ACT_WARN_NODE: + case ACT_CRIT_NODE: + case LIMIT_RF_NODE: + case LIMIT_DIGITAL_NODE: + case LIMIT_BOARD_NODE: + case LIMIT_PA_NODE: + vty->node = MGR_NODE; + break; + default: + vty->node = CONFIG_NODE; + } + return vty->node; +} + +static int is_config_node(struct vty *vty, int node) +{ + switch (node) { + case MGR_NODE: + case ACT_NORM_NODE: + case ACT_WARN_NODE: + case ACT_CRIT_NODE: + case LIMIT_RF_NODE: + case LIMIT_DIGITAL_NODE: + case LIMIT_BOARD_NODE: + case LIMIT_PA_NODE: + return 1; + default: + return 0; + } +} + +static struct vty_app_info vty_info = { + .name = "sysmobts-mgr", + .version = PACKAGE_VERSION, + .go_parent_cb = go_to_parent, + .is_config_node = is_config_node, + .copyright = copyright, +}; + + +#define MGR_STR "Configure sysmobts-mgr\n" + +static struct cmd_node mgr_node = { + MGR_NODE, + "%s(sysmobts-mgr)# ", + 1, +}; + +static struct cmd_node act_norm_node = { + ACT_NORM_NODE, + "%s(action-normal)# ", + 1, +}; + +static struct cmd_node act_warn_node = { + ACT_WARN_NODE, + "%s(action-warn)# ", + 1, +}; + +static struct cmd_node act_crit_node = { + ACT_CRIT_NODE, + "%s(action-critical)# ", + 1, +}; + +static struct cmd_node limit_rf_node = { + LIMIT_RF_NODE, + "%s(limit-rf)# ", + 1, +}; + +static struct cmd_node limit_digital_node = { + LIMIT_DIGITAL_NODE, + "%s(limit-digital)# ", + 1, +}; + +static struct cmd_node limit_board_node = { + LIMIT_BOARD_NODE, + "%s(limit-board)# ", + 1, +}; + +static struct cmd_node limit_pa_node = { + LIMIT_PA_NODE, + "%s(limit-pa)# ", + 1, +}; + +DEFUN(cfg_mgr, cfg_mgr_cmd, + "sysmobts-mgr", + MGR_STR) +{ + vty->node = MGR_NODE; + return CMD_SUCCESS; +} + +static void write_temp_limit(struct vty *vty, const char *name, + struct sysmobts_temp_limit *limit) +{ + vty_out(vty, " %s%s", name, VTY_NEWLINE); + vty_out(vty, " threshold warning %d%s", + limit->thresh_warn, VTY_NEWLINE); + vty_out(vty, " threshold critical %d%s", + limit->thresh_crit, VTY_NEWLINE); +} + +static void write_norm_action(struct vty *vty, const char *name, int actions) +{ + vty_out(vty, " %s%s", name, VTY_NEWLINE); + vty_out(vty, " %spa-on%s", + (actions & TEMP_ACT_NORM_PA_ON) ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %sbts-service-on%s", + (actions & TEMP_ACT_NORM_BTS_SRV_ON) ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %sslave-on%s", + (actions & TEMP_ACT_NORM_SLAVE_ON) ? "" : "no ", VTY_NEWLINE); +} + +static void write_action(struct vty *vty, const char *name, int actions) +{ + vty_out(vty, " %s%s", name, VTY_NEWLINE); +#if 0 + vty_out(vty, " %spower-control%s", + (actions & TEMP_ACT_PWR_CONTRL) ? "" : "no ", VTY_NEWLINE); + + /* only on the sysmobts 2050 */ + vty_out(vty, " %smaster-off%s", + (actions & TEMP_ACT_MASTER_OFF) ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %sslave-off%s", + (actions & TEMP_ACT_MASTER_OFF) ? "" : "no ", VTY_NEWLINE); +#endif + vty_out(vty, " %spa-off%s", + (actions & TEMP_ACT_PA_OFF) ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %sbts-service-off%s", + (actions & TEMP_ACT_BTS_SRV_OFF) ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %sslave-off%s", + (actions & TEMP_ACT_SLAVE_OFF) ? "" : "no ", VTY_NEWLINE); +} + +static int config_write_mgr(struct vty *vty) +{ + vty_out(vty, "sysmobts-mgr%s", VTY_NEWLINE); + + write_temp_limit(vty, "limits rf", &s_mgr->rf_limit); + write_temp_limit(vty, "limits digital", &s_mgr->digital_limit); + write_temp_limit(vty, "limits board", &s_mgr->board_limit); + write_temp_limit(vty, "limits pa", &s_mgr->pa_limit); + + write_norm_action(vty, "actions normal", s_mgr->action_norm); + write_action(vty, "actions warn", s_mgr->action_warn); + write_action(vty, "actions critical", s_mgr->action_crit); + + return CMD_SUCCESS; +} + +static int config_write_dummy(struct vty *vty) +{ + return CMD_SUCCESS; +} + +#define CFG_LIMIT(name, expl, switch_to, variable) \ +DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \ + "limits " #name, \ + "Configure Limits\n" expl) \ +{ \ + vty->node = switch_to; \ + vty->index = &s_mgr->variable; \ + return CMD_SUCCESS; \ +} + +CFG_LIMIT(rf, "RF\n", LIMIT_RF_NODE, rf_limit) +CFG_LIMIT(digital, "Digital\n", LIMIT_DIGITAL_NODE, digital_limit) +CFG_LIMIT(board, "Board\n", LIMIT_BOARD_NODE, board_limit) +CFG_LIMIT(pa, "Power Amplifier\n", LIMIT_PA_NODE, pa_limit) +#undef CFG_LIMIT + +DEFUN(cfg_limit_warning, cfg_thresh_warning_cmd, + "threshold warning <0-200>", + "Threshold to reach\n" "Warning level\n" "Range\n") +{ + struct sysmobts_temp_limit *limit = vty->index; + limit->thresh_warn = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_limit_crit, cfg_thresh_crit_cmd, + "threshold critical <0-200>", + "Threshold to reach\n" "Severe level\n" "Range\n") +{ + struct sysmobts_temp_limit *limit = vty->index; + limit->thresh_crit = atoi(argv[0]); + return CMD_SUCCESS; +} + +#define CFG_ACTION(name, expl, switch_to, variable) \ +DEFUN(cfg_action_##name, cfg_action_##name##_cmd, \ + "actions " #name, \ + "Configure Actions\n" expl) \ +{ \ + vty->node = switch_to; \ + vty->index = &s_mgr->variable; \ + return CMD_SUCCESS; \ +} +CFG_ACTION(normal, "Normal Actions\n", ACT_NORM_NODE, action_norm) +CFG_ACTION(warn, "Warning Actions\n", ACT_WARN_NODE, action_warn) +CFG_ACTION(critical, "Critical Actions\n", ACT_CRIT_NODE, action_crit) +#undef CFG_ACTION + +DEFUN(cfg_action_pa_on, cfg_action_pa_on_cmd, + "pa-on", + "Switch the Power Amplifier on\n") +{ + int *action = vty->index; + *action |= TEMP_ACT_NORM_PA_ON; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_action_pa_on, cfg_no_action_pa_on_cmd, + "no pa-on", + NO_STR "Switch the Power Amplifier on\n") +{ + int *action = vty->index; + *action &= ~TEMP_ACT_NORM_PA_ON; + return CMD_SUCCESS; +} + +DEFUN(cfg_action_bts_srv_on, cfg_action_bts_srv_on_cmd, + "bts-service-on", + "Start the systemd osmo-bts-sysmo.service\n") +{ + int *action = vty->index; + *action |= TEMP_ACT_NORM_BTS_SRV_ON; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_action_bts_srv_on, cfg_no_action_bts_srv_on_cmd, + "no bts-service-on", + NO_STR "Start the systemd osmo-bts-sysmo.service\n") +{ + int *action = vty->index; + *action &= ~TEMP_ACT_NORM_BTS_SRV_ON; + return CMD_SUCCESS; +} + +DEFUN(cfg_action_slave_on, cfg_action_slave_on_cmd, + "slave-on", + "Power-on secondary device on sysmoBTS2050\n") +{ + int *action = vty->index; + *action |= TEMP_ACT_NORM_SLAVE_ON; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_action_slave_on, cfg_no_action_slave_on_cmd, + "no slave-on", + NO_STR "Power-on secondary device on sysmoBTS2050\n") +{ + int *action = vty->index; + *action &= ~TEMP_ACT_NORM_SLAVE_ON; + return CMD_SUCCESS; +} + +DEFUN(cfg_action_pa_off, cfg_action_pa_off_cmd, + "pa-off", + "Switch the Power Amplifier off\n") +{ + int *action = vty->index; + *action |= TEMP_ACT_PA_OFF; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_action_pa_off, cfg_no_action_pa_off_cmd, + "no pa-off", + NO_STR "Do not switch off the Power Amplifier\n") +{ + int *action = vty->index; + *action &= ~TEMP_ACT_PA_OFF; + return CMD_SUCCESS; +} + +DEFUN(cfg_action_bts_srv_off, cfg_action_bts_srv_off_cmd, + "bts-service-off", + "Stop the systemd osmo-bts-sysmo.service\n") +{ + int *action = vty->index; + *action |= TEMP_ACT_BTS_SRV_OFF; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_action_bts_srv_off, cfg_no_action_bts_srv_off_cmd, + "no bts-service-off", + NO_STR "Stop the systemd osmo-bts-sysmo.service\n") +{ + int *action = vty->index; + *action &= ~TEMP_ACT_BTS_SRV_OFF; + return CMD_SUCCESS; +} + +DEFUN(cfg_action_slave_off, cfg_action_slave_off_cmd, + "slave-off", + "Power-off secondary device on sysmoBTS2050\n") +{ + int *action = vty->index; + *action |= TEMP_ACT_SLAVE_OFF; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_action_slave_off, cfg_no_action_slave_off_cmd, + "no slave-off", + NO_STR "Power-off secondary device on sysmoBTS2050\n") +{ + int *action = vty->index; + *action &= ~TEMP_ACT_SLAVE_OFF; + return CMD_SUCCESS; +} + +DEFUN(show_mgr, show_mgr_cmd, "show manager", + SHOW_STR "Display information about the manager") +{ + vty_out(vty, "BTS Control Interface: %s%s", + s_mgr->calib.is_up ? "connected" : "disconnected", VTY_NEWLINE); + vty_out(vty, "Temperature control state: %s%s", + sysmobts_mgr_temp_get_state(s_mgr->state), VTY_NEWLINE); + vty_out(vty, "Current Temperatures%s", VTY_NEWLINE); + vty_out(vty, " Digital: %f Celcius%s", + sysmobts_temp_get(SYSMOBTS_TEMP_DIGITAL, + SYSMOBTS_TEMP_INPUT) / 1000.0f, + VTY_NEWLINE); + vty_out(vty, " RF: %f Celcius%s", + sysmobts_temp_get(SYSMOBTS_TEMP_RF, + SYSMOBTS_TEMP_INPUT) / 1000.0f, + VTY_NEWLINE); + if (is_sbts2050()) { + int temp_pa, temp_board; + struct sbts2050_power_status status; + + vty_out(vty, " sysmoBTS 2050 is %s%s", + is_sbts2050_master() ? "master" : "slave", VTY_NEWLINE); + + sbts2050_uc_check_temp(&temp_pa, &temp_board); + vty_out(vty, " sysmoBTS 2050 PA: %d Celcius%s", temp_pa, VTY_NEWLINE); + vty_out(vty, " sysmoBTS 2050 PA: %d Celcius%s", temp_board, VTY_NEWLINE); + + sbts2050_uc_get_status(&status); + vty_out(vty, "Power Status%s", VTY_NEWLINE); + vty_out(vty, " Main Supply :(ON) [(24.00)Vdc, %4.2f A]%s", + status.main_supply_current, VTY_NEWLINE); + vty_out(vty, " Master SF : %s [%6.2f Vdc, %4.2f A]%s", + status.master_enabled ? "ON " : "OFF", + status.master_voltage, status.master_current, + VTY_NEWLINE); + vty_out(vty, " Slave SF : %s [%6.2f Vdc, %4.2f A]%s", + status.slave_enabled ? "ON" : "OFF", + status.slave_voltage, status.slave_current, + VTY_NEWLINE); + vty_out(vty, " Power Amp : %s [%6.2f Vdc, %4.2f A]%s", + status.pa_enabled ? "ON" : "OFF", + status.pa_voltage, status.pa_current, + VTY_NEWLINE); + vty_out(vty, " PA Bias : %s [%6.2f Vdc, ---- A]%s", + status.pa_enabled ? "ON" : "OFF", + status.pa_bias_voltage, + VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +DEFUN(calibrate_trx, calibrate_trx_cmd, + "trx 0 calibrate-clock", + "Transceiver commands\n" "Transceiver 0\n" + "Calibrate clock against GPS PPS\n") +{ + if (sysmobts_mgr_calib_run(s_mgr) < 0) { + vty_out(vty, "%%Failed to start calibration.%s", VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +static void register_limit(int limit) +{ + install_element(limit, &cfg_thresh_warning_cmd); + install_element(limit, &cfg_thresh_crit_cmd); +} + +static void register_normal_action(int act) +{ + install_element(act, &cfg_action_pa_on_cmd); + install_element(act, &cfg_no_action_pa_on_cmd); + install_element(act, &cfg_action_bts_srv_on_cmd); + install_element(act, &cfg_no_action_bts_srv_on_cmd); + + /* these only work on the sysmobts 2050 */ + install_element(act, &cfg_action_slave_on_cmd); + install_element(act, &cfg_no_action_slave_on_cmd); +} + +static void register_action(int act) +{ +#if 0 + install_element(act, &cfg_action_pwr_contrl_cmd); + install_element(act, &cfg_no_action_pwr_contrl_cmd); +#endif + install_element(act, &cfg_action_pa_off_cmd); + install_element(act, &cfg_no_action_pa_off_cmd); + install_element(act, &cfg_action_bts_srv_off_cmd); + install_element(act, &cfg_no_action_bts_srv_off_cmd); + + /* these only work on the sysmobts 2050 */ + install_element(act, &cfg_action_slave_off_cmd); + install_element(act, &cfg_no_action_slave_off_cmd); +} + +int sysmobts_mgr_vty_init(void) +{ + vty_init(&vty_info); + + install_element_ve(&show_mgr_cmd); + + install_element(ENABLE_NODE, &calibrate_trx_cmd); + + install_node(&mgr_node, config_write_mgr); + install_element(CONFIG_NODE, &cfg_mgr_cmd); + + /* install the limit nodes */ + install_node(&limit_rf_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_rf_cmd); + register_limit(LIMIT_RF_NODE); + + install_node(&limit_digital_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_digital_cmd); + register_limit(LIMIT_DIGITAL_NODE); + + install_node(&limit_board_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_board_cmd); + register_limit(LIMIT_BOARD_NODE); + + install_node(&limit_pa_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_pa_cmd); + register_limit(LIMIT_PA_NODE); + + /* install the normal node */ + install_node(&act_norm_node, config_write_dummy); + install_element(MGR_NODE, &cfg_action_normal_cmd); + register_normal_action(ACT_NORM_NODE); + + /* install the warning and critical node */ + install_node(&act_warn_node, config_write_dummy); + install_element(MGR_NODE, &cfg_action_warn_cmd); + register_action(ACT_WARN_NODE); + + install_node(&act_crit_node, config_write_dummy); + install_element(MGR_NODE, &cfg_action_critical_cmd); + register_action(ACT_CRIT_NODE); + + return 0; +} + +int sysmobts_mgr_parse_config(struct sysmobts_mgr_instance *manager) +{ + int rc; + + s_mgr = manager; + rc = vty_read_config_file(s_mgr->config_file, NULL); + if (rc < 0) { + fprintf(stderr, "Failed to parse the config file: '%s'\n", + s_mgr->config_file); + return rc; + } + + return 0; +} diff --git a/src/osmo-bts-sysmo/misc/sysmobts_misc.c b/src/osmo-bts-sysmo/misc/sysmobts_misc.c new file mode 100644 index 0000000..d996d64 --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_misc.c @@ -0,0 +1,275 @@ +/* (C) 2012 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "btsconfig.h" +#include "sysmobts_misc.h" +#include "sysmobts_par.h" +#include "sysmobts_mgr.h" + +/********************************************************************* + * Temperature handling + *********************************************************************/ + +#define TEMP_PATH "/sys/class/hwmon/hwmon0/device/temp%u_%s" + +static const char *temp_type_str[_NUM_TEMP_TYPES] = { + [SYSMOBTS_TEMP_INPUT] = "input", + [SYSMOBTS_TEMP_LOWEST] = "lowest", + [SYSMOBTS_TEMP_HIGHEST] = "highest", +}; + +int sysmobts_temp_get(enum sysmobts_temp_sensor sensor, + enum sysmobts_temp_type type) +{ + char buf[PATH_MAX]; + char tempstr[8]; + int fd, rc; + + if (sensor < SYSMOBTS_TEMP_DIGITAL || + sensor > SYSMOBTS_TEMP_RF) + return -EINVAL; + + if (type >= ARRAY_SIZE(temp_type_str)) + return -EINVAL; + + snprintf(buf, sizeof(buf)-1, TEMP_PATH, sensor, temp_type_str[type]); + buf[sizeof(buf)-1] = '\0'; + + fd = open(buf, O_RDONLY); + if (fd < 0) + return fd; + + rc = read(fd, tempstr, sizeof(tempstr)); + tempstr[sizeof(tempstr)-1] = '\0'; + if (rc < 0) { + close(fd); + return rc; + } + if (rc == 0) { + close(fd); + return -EIO; + } + + close(fd); + + return atoi(tempstr); +} + +static const struct { + const char *name; + enum sysmobts_temp_sensor sensor; + enum sysmobts_par ee_par; +} temp_data[] = { + { + .name = "digital", + .sensor = SYSMOBTS_TEMP_DIGITAL, + .ee_par = SYSMOBTS_PAR_TEMP_DIG_MAX, + }, { + .name = "rf", + .sensor = SYSMOBTS_TEMP_RF, + .ee_par = SYSMOBTS_PAR_TEMP_RF_MAX, + } +}; + +void sysmobts_check_temp(int no_eeprom_write) +{ + int temp_old[ARRAY_SIZE(temp_data)]; + int temp_hi[ARRAY_SIZE(temp_data)]; + int temp_cur[ARRAY_SIZE(temp_data)]; + int i, rc; + + for (i = 0; i < ARRAY_SIZE(temp_data); i++) { + int ret; + rc = sysmobts_par_get_int(temp_data[i].ee_par, &ret); + temp_old[i] = ret * 1000; + temp_hi[i] = sysmobts_temp_get(temp_data[i].sensor, + SYSMOBTS_TEMP_HIGHEST); + temp_cur[i] = sysmobts_temp_get(temp_data[i].sensor, + SYSMOBTS_TEMP_INPUT); + + if ((temp_cur[i] < 0 && temp_cur[i] > -1000) || + (temp_hi[i] < 0 && temp_hi[i] > -1000)) { + LOGP(DTEMP, LOGL_ERROR, "Error reading temperature\n"); + return; + } + + LOGP(DTEMP, LOGL_DEBUG, "Current %s temperature: %d.%d C\n", + temp_data[i].name, temp_cur[i]/1000, temp_cur[i]%1000); + + if (temp_hi[i] > temp_old[i]) { + LOGP(DTEMP, LOGL_NOTICE, "New maximum %s " + "temperature: %d.%d C\n", temp_data[i].name, + temp_hi[i]/1000, temp_hi[i]%1000); + + if (!no_eeprom_write) { + rc = sysmobts_par_set_int(SYSMOBTS_PAR_TEMP_DIG_MAX, + temp_hi[0]/1000); + if (rc < 0) + LOGP(DTEMP, LOGL_ERROR, "error writing new %s " + "max temp %d (%s)\n", temp_data[i].name, + rc, strerror(errno)); + } + } + } +} + +/********************************************************************* + * Hours handling + *********************************************************************/ +static time_t last_update; + +int sysmobts_update_hours(int no_eeprom_write) +{ + time_t now = time(NULL); + int rc, op_hrs; + + /* first time after start of manager program */ + if (last_update == 0) { + last_update = now; + + rc = sysmobts_par_get_int(SYSMOBTS_PAR_HOURS, &op_hrs); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, "Unable to read " + "operational hours: %d (%s)\n", rc, + strerror(errno)); + return rc; + } + + LOGP(DTEMP, LOGL_INFO, "Total hours of Operation: %u\n", + op_hrs); + + return 0; + } + + if (now >= last_update + 3600) { + rc = sysmobts_par_get_int(SYSMOBTS_PAR_HOURS, &op_hrs); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, "Unable to read " + "operational hours: %d (%s)\n", rc, + strerror(errno)); + return rc; + } + + /* number of hours to increase */ + op_hrs += (now-last_update)/3600; + + LOGP(DTEMP, LOGL_INFO, "Total hours of Operation: %u\n", + op_hrs); + + if (!no_eeprom_write) { + rc = sysmobts_par_set_int(SYSMOBTS_PAR_HOURS, op_hrs); + if (rc < 0) + return rc; + } + + last_update = now; + } + + return 0; +} + +/********************************************************************* + * Firmware reloading + *********************************************************************/ + +#define SYSMOBTS_FW_PATH "/lib/firmware" + +static const char *fw_names[_NUM_FW] = { + [SYSMOBTS_FW_FPGA] = "sysmobts-v2.bit", + [SYSMOBTS_FW_DSP] = "sysmobts-v2.out", +}; +static const char *fw_devs[_NUM_FW] = { + [SYSMOBTS_FW_FPGA] = "/dev/fpgadl_par0", + [SYSMOBTS_FW_DSP] = "/dev/dspdl_dm644x_0", +}; + +int sysmobts_firmware_reload(enum sysmobts_firmware_type type) +{ + char name[PATH_MAX]; + uint8_t buf[1024]; + int fd_in, fd_out, rc; + + if (type >= _NUM_FW) + return -EINVAL; + + snprintf(name, sizeof(name)-1, "%s/%s", + SYSMOBTS_FW_PATH, fw_names[type]); + name[sizeof(name)-1] = '\0'; + + fd_in = open(name, O_RDONLY); + if (fd_in < 0) { + LOGP(DFW, LOGL_ERROR, "unable ot open firmware file %s: %s\n", + name, strerror(errno)); + return fd_in; + } + + fd_out = open(fw_devs[type], O_WRONLY); + if (fd_out < 0) { + LOGP(DFW, LOGL_ERROR, "unable ot open firmware device %s: %s\n", + fw_devs[type], strerror(errno)); + close(fd_in); + return fd_out; + } + + while ((rc = read(fd_in, buf, sizeof(buf)))) { + int written; + + if (rc < 0) { + LOGP(DFW, LOGL_ERROR, "error %d during read " + "from %s: %s\n", rc, name, strerror(errno)); + close(fd_in); + close(fd_out); + return -EIO; + } + + written = write(fd_out, buf, rc); + if (written < rc) { + LOGP(DFW, LOGL_ERROR, "short write during " + "fw write to %s\n", fw_devs[type]); + close(fd_in); + close(fd_out); + return -EIO; + } + } + + close(fd_in); + close(fd_out); + + return 0; +} diff --git a/src/osmo-bts-sysmo/misc/sysmobts_misc.h b/src/osmo-bts-sysmo/misc/sysmobts_misc.h new file mode 100644 index 0000000..06166cf --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_misc.h @@ -0,0 +1,65 @@ +#ifndef _SYSMOBTS_MISC_H +#define _SYSMOBTS_MISC_H + +#include + +enum sysmobts_temp_sensor { + SYSMOBTS_TEMP_DIGITAL = 1, + SYSMOBTS_TEMP_RF = 2, +}; + +enum sysmobts_temp_type { + SYSMOBTS_TEMP_INPUT, + SYSMOBTS_TEMP_LOWEST, + SYSMOBTS_TEMP_HIGHEST, + _NUM_TEMP_TYPES +}; + +int sysmobts_temp_get(enum sysmobts_temp_sensor sensor, + enum sysmobts_temp_type type); + +void sysmobts_check_temp(int no_eeprom_write); + +int sysmobts_update_hours(int no_epprom_write); + +enum sysmobts_firmware_type { + SYSMOBTS_FW_FPGA, + SYSMOBTS_FW_DSP, + _NUM_FW +}; + +int sysmobts_firmware_reload(enum sysmobts_firmware_type type); + + +int sysmobts_bts_type(); +int sysmobts_trx_number(); +int is_sbts2050(void); +int is_sbts2050_trx(int); +int is_sbts2050_master(void); + +struct sbts2050_power_status { + float main_supply_current; + + int master_enabled; + float master_voltage; + float master_current; + + int slave_enabled; + float slave_voltage; + float slave_current; + + int pa_enabled; + float pa_voltage; + float pa_current; + + float pa_bias_voltage; +}; + +int sbts2050_uc_check_temp(int *temp_pa, int *temp_board); +int sbts2050_uc_set_power(int pmaster, int pslave, int ppa); +int sbts2050_uc_get_status(struct sbts2050_power_status *status); +int sbts2050_uc_set_pa_power(int on_off); +int sbts2050_uc_set_slave_power(int on_off); +void sbts2050_uc_initialize(); + +#endif diff --git a/src/osmo-bts-sysmo/misc/sysmobts_nl.c b/src/osmo-bts-sysmo/misc/sysmobts_nl.c new file mode 100644 index 0000000..67aa663 --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_nl.c @@ -0,0 +1,120 @@ +/* Helper for netlink */ + +/* + * (C) 2014 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +#define NLMSG_TAIL(nmsg) \ + ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) + +/** + * In case one binds to 0.0.0.0/INADDR_ANY and wants to know which source + * address will be used when sending a message this function can be used. + * It will ask the routing code of the kernel for the PREFSRC + */ +int source_for_dest(const struct in_addr *dest, struct in_addr *loc_source) +{ + int fd, rc; + struct rtmsg *r; + struct rtattr *rta; + struct { + struct nlmsghdr n; + struct rtmsg r; + char buf[1024]; + } req; + + memset(&req, 0, sizeof(req)); + + fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_ROUTE); + if (fd < 0) { + perror("nl socket"); + return -1; + } + + /* Send a rtmsg and ask for a response */ + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + req.n.nlmsg_type = RTM_GETROUTE; + req.n.nlmsg_seq = 1; + + /* Prepare the routing request */ + req.r.rtm_family = AF_INET; + + /* set the dest */ + rta = NLMSG_TAIL(&req.n); + rta->rta_type = RTA_DST; + rta->rta_len = RTA_LENGTH(sizeof(*dest)); + memcpy(RTA_DATA(rta), dest, sizeof(*dest)); + + /* update sizes for dest */ + req.r.rtm_dst_len = sizeof(*dest) * 8; + req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + RTA_ALIGN(rta->rta_len); + + rc = send(fd, &req, req.n.nlmsg_len, 0); + if (rc != req.n.nlmsg_len) { + perror("short write"); + close(fd); + return -2; + } + + + /* now receive a response and parse it */ + rc = recv(fd, &req, sizeof(req), 0); + if (rc <= 0) { + perror("short read"); + close(fd); + return -3; + } + + if (!NLMSG_OK(&req.n, rc) || req.n.nlmsg_type != RTM_NEWROUTE) { + close(fd); + return -4; + } + + r = NLMSG_DATA(&req.n); + rc -= NLMSG_LENGTH(sizeof(*r)); + rta = RTM_RTA(r); + while (RTA_OK(rta, rc)) { + if (rta->rta_type != RTA_PREFSRC) { + rta = RTA_NEXT(rta, rc); + continue; + } + + /* we are done */ + memcpy(loc_source, RTA_DATA(rta), RTA_PAYLOAD(rta)); + close(fd); + return 0; + } + + close(fd); + return -5; +} diff --git a/src/osmo-bts-sysmo/misc/sysmobts_nl.h b/src/osmo-bts-sysmo/misc/sysmobts_nl.h new file mode 100644 index 0000000..84f4d9c --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_nl.h @@ -0,0 +1,24 @@ +/* + * (C) 2014 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 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 . + * + */ +#pragma once + +struct in_addr; + +int source_for_dest(const struct in_addr *dest, struct in_addr *loc_source); diff --git a/src/osmo-bts-sysmo/misc/sysmobts_par.c b/src/osmo-bts-sysmo/misc/sysmobts_par.c new file mode 100644 index 0000000..de81fff --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_par.c @@ -0,0 +1,382 @@ +/* sysmobts - access to hardware related parameters */ + +/* (C) 2012 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "sysmobts_eeprom.h" +#include "sysmobts_par.h" +#include "eeprom.h" + +#define EEPROM_PATH "/sys/devices/platform/i2c_davinci.1/i2c-1/1-0050/eeprom" + +static const struct osmo_crc8gen_code crc8_ccit = { + .bits = 8, + .poly = 0x83, + .init = 0xFF, + .remainder = 0x00, +}; + +const struct value_string sysmobts_par_names[_NUM_SYSMOBTS_PAR+1] = { + { SYSMOBTS_PAR_MAC, "ethaddr" }, + { SYSMOBTS_PAR_CLK_FACTORY, "clk-factory" }, + { SYSMOBTS_PAR_TEMP_DIG_MAX, "temp-dig-max" }, + { SYSMOBTS_PAR_TEMP_RF_MAX, "temp-rf-max" }, + { SYSMOBTS_PAR_SERNR, "serial-nr" }, + { SYSMOBTS_PAR_HOURS, "hours-running" }, + { SYSMOBTS_PAR_BOOTS, "boot-count" }, + { SYSMOBTS_PAR_KEY, "key" }, + { SYSMOBTS_PAR_MODEL_NR, "model-nr" }, + { SYSMOBTS_PAR_MODEL_FLAGS, "model-flags" }, + { SYSMOBTS_PAR_TRX_NR, "trx-nr" }, + { 0, NULL } +}; + +static struct { + int read; + struct sysmobts_eeprom ee; +} g_ee; + +static struct sysmobts_eeprom *get_eeprom(int update_rqd) +{ + if (update_rqd || g_ee.read == 0) { + int fd, rc; + + fd = open(EEPROM_PATH, O_RDONLY); + if (fd < 0) + return NULL; + + rc = read(fd, &g_ee.ee, sizeof(g_ee.ee)); + + close(fd); + + if (rc < sizeof(g_ee.ee)) + return NULL; + + g_ee.read = 1; + } + + return &g_ee.ee; +} + +static int set_eeprom(struct sysmobts_eeprom *ee) +{ + int fd, rc; + + memcpy(&g_ee.ee, ee, sizeof(*ee)); + + fd = open(EEPROM_PATH, O_WRONLY); + if (fd < 0) + return fd; + + rc = write(fd, ee, sizeof(*ee)); + if (rc < sizeof(*ee)) { + close(fd); + return -EIO; + } + + close(fd); + + return 0; +} + +int sysmobts_par_is_int(enum sysmobts_par par) +{ + switch (par) { + case SYSMOBTS_PAR_CLK_FACTORY: + case SYSMOBTS_PAR_TEMP_DIG_MAX: + case SYSMOBTS_PAR_TEMP_RF_MAX: + case SYSMOBTS_PAR_SERNR: + case SYSMOBTS_PAR_HOURS: + case SYSMOBTS_PAR_BOOTS: + case SYSMOBTS_PAR_MODEL_NR: + case SYSMOBTS_PAR_MODEL_FLAGS: + case SYSMOBTS_PAR_TRX_NR: + return 1; + default: + return 0; + } +} + +int sysmobts_par_get_int(enum sysmobts_par par, int *ret) +{ + eeprom_RfClockCal_t rf_clk; + eeprom_Error_t err; + struct sysmobts_eeprom *ee = get_eeprom(0); + + if (!ee) + return -EIO; + + if (par >= _NUM_SYSMOBTS_PAR) + return -ENODEV; + + switch (par) { + case SYSMOBTS_PAR_CLK_FACTORY: + err = eeprom_ReadRfClockCal(&rf_clk); + if (err != EEPROM_SUCCESS) + return -EIO; + *ret = rf_clk.iClkCor; + break; + case SYSMOBTS_PAR_TEMP_DIG_MAX: + *ret = ee->temp1_max; + break; + case SYSMOBTS_PAR_TEMP_RF_MAX: + *ret = ee->temp2_max; + break; + case SYSMOBTS_PAR_SERNR: + *ret = ee->serial_nr; + break; + case SYSMOBTS_PAR_HOURS: + *ret = ee->operational_hours; + break; + case SYSMOBTS_PAR_BOOTS: + *ret = ee->boot_count; + break; + case SYSMOBTS_PAR_MODEL_NR: + *ret = ee->model_nr; + break; + case SYSMOBTS_PAR_MODEL_FLAGS: + *ret = ee->model_flags; + break; + case SYSMOBTS_PAR_TRX_NR: + *ret = ee->trx_nr; + break; + default: + return -EINVAL; + } + + return 0; +} + +int sysmobts_par_set_int(enum sysmobts_par par, int val) +{ + eeprom_RfClockCal_t rf_clk; + eeprom_Error_t err; + struct sysmobts_eeprom *ee = get_eeprom(1); + + if (!ee) + return -EIO; + + if (par >= _NUM_SYSMOBTS_PAR) + return -ENODEV; + + switch (par) { + case SYSMOBTS_PAR_CLK_FACTORY: + err = eeprom_ReadRfClockCal(&rf_clk); + if (err != EEPROM_SUCCESS) + return -EIO; + rf_clk.iClkCor = val; + err = eeprom_WriteRfClockCal(&rf_clk); + if (err != EEPROM_SUCCESS) + return -EIO; + break; + case SYSMOBTS_PAR_TEMP_DIG_MAX: + ee->temp1_max = val; + break; + case SYSMOBTS_PAR_TEMP_RF_MAX: + ee->temp2_max = val; + break; + case SYSMOBTS_PAR_SERNR: + ee->serial_nr = val; + break; + case SYSMOBTS_PAR_HOURS: + ee->operational_hours = val; + break; + case SYSMOBTS_PAR_BOOTS: + ee->boot_count = val; + break; + case SYSMOBTS_PAR_MODEL_NR: + ee->model_nr = val; + break; + case SYSMOBTS_PAR_MODEL_FLAGS: + ee->model_flags = val; + break; + case SYSMOBTS_PAR_TRX_NR: + ee->trx_nr = val; + break; + default: + return -EINVAL; + } + + set_eeprom(ee); + + return 0; +} + +int sysmobts_par_get_buf(enum sysmobts_par par, uint8_t *buf, + unsigned int size) +{ + uint8_t *ptr; + unsigned int len; + struct sysmobts_eeprom *ee = get_eeprom(0); + + if (!ee) + return -EIO; + + if (par >= _NUM_SYSMOBTS_PAR) + return -ENODEV; + + switch (par) { + case SYSMOBTS_PAR_MAC: + ptr = ee->eth_mac; + len = sizeof(ee->eth_mac); + break; + case SYSMOBTS_PAR_KEY: + ptr = ee->gpg_key; + len = sizeof(ee->gpg_key); + break; + default: + return -EINVAL; + } + + if (size < len) + len = size; + memcpy(buf, ptr, len); + + return len; +} + +int sysmobts_par_set_buf(enum sysmobts_par par, const uint8_t *buf, + unsigned int size) +{ + uint8_t *ptr; + unsigned int len; + struct sysmobts_eeprom *ee = get_eeprom(0); + + if (!ee) + return -EIO; + + if (par >= _NUM_SYSMOBTS_PAR) + return -ENODEV; + + switch (par) { + case SYSMOBTS_PAR_MAC: + ptr = ee->eth_mac; + len = sizeof(ee->eth_mac); + break; + case SYSMOBTS_PAR_KEY: + ptr = ee->gpg_key; + len = sizeof(ee->gpg_key); + break; + default: + return -EINVAL; + } + + if (len < size) + size = len; + + memcpy(ptr, buf, size); + + return len; +} + +int sysmobts_par_get_net(struct sysmobts_net_cfg *cfg) +{ + struct sysmobts_eeprom *ee = get_eeprom(0); + ubit_t bits[sizeof(*cfg) * 8]; + uint8_t crc; + int rc; + + if (!ee) + return -EIO; + + /* convert the net_cfg to unpacked bits */ + rc = osmo_pbit2ubit(bits, (uint8_t *) &ee->net_cfg, sizeof(bits)); + if (rc != sizeof(bits)) + return -EFAULT; + /* compute the crc and compare */ + crc = osmo_crc8gen_compute_bits(&crc8_ccit, bits, sizeof(bits)); + if (crc != ee->crc) { + fprintf(stderr, "Computed CRC(%d) wanted CRC(%d)\n", crc, ee->crc); + return -EBADMSG; + } + /* return the actual data */ + *cfg = ee->net_cfg; + return 0; +} + +int sysmobts_get_type(int *bts_type) +{ + return sysmobts_par_get_int(SYSMOBTS_PAR_MODEL_NR, bts_type); +} + +int sysmobts_get_trx(int *trx_number) +{ + return sysmobts_par_get_int(SYSMOBTS_PAR_TRX_NR, trx_number); +} + +char *sysmobts_model(int bts_type, int trx_num) +{ + switch(bts_type) { + case 0: + case 0xffff: + case 1002: + return "sysmoBTS 1002"; + case 2050: + switch(trx_num) { + case 0: + return "sysmoBTS 2050 (master)"; + case 1: + return "sysmoBTS 2050 (slave)"; + default: + return "sysmoBTS 2050 (unknown)"; + } + default: + return "Unknown"; + } +} + +int sysmobts_par_set_net(struct sysmobts_net_cfg *cfg) +{ + struct sysmobts_eeprom *ee = get_eeprom(1); + ubit_t bits[sizeof(*cfg) * 8]; + int rc; + + if (!ee) + return -EIO; + + /* convert the net_cfg to unpacked bits */ + rc = osmo_pbit2ubit(bits, (uint8_t *) cfg, sizeof(bits)); + if (rc != sizeof(bits)) + return -EFAULT; + /* compute and store the result */ + ee->net_cfg = *cfg; + ee->crc = osmo_crc8gen_compute_bits(&crc8_ccit, bits, sizeof(bits)); + return set_eeprom(ee); +} + +osmo_static_assert(offsetof(struct sysmobts_eeprom, trx_nr) == 36, offset_36); +osmo_static_assert(offsetof(struct sysmobts_eeprom, boot_state) == 37, offset_37); +osmo_static_assert(offsetof(struct sysmobts_eeprom, _pad1) == 85, offset_85); +osmo_static_assert(offsetof(struct sysmobts_eeprom, net_cfg.mode) == 103, offset_103); +osmo_static_assert((offsetof(struct sysmobts_eeprom, net_cfg.ip) & 0x3) == 0, ip_32bit_aligned); +osmo_static_assert(offsetof(struct sysmobts_eeprom, gpg_key) == 121, offset_121); diff --git a/src/osmo-bts-sysmo/misc/sysmobts_par.h b/src/osmo-bts-sysmo/misc/sysmobts_par.h new file mode 100644 index 0000000..52bf67d --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_par.h @@ -0,0 +1,38 @@ +#ifndef _SYSMOBTS_PAR_H +#define _SYSMOBTS_PAR_H + +#include + +struct sysmobts_net_cfg; + +enum sysmobts_par { + SYSMOBTS_PAR_MAC, + SYSMOBTS_PAR_CLK_FACTORY, + SYSMOBTS_PAR_TEMP_DIG_MAX, + SYSMOBTS_PAR_TEMP_RF_MAX, + SYSMOBTS_PAR_SERNR, + SYSMOBTS_PAR_HOURS, + SYSMOBTS_PAR_BOOTS, + SYSMOBTS_PAR_KEY, + SYSMOBTS_PAR_MODEL_NR, + SYSMOBTS_PAR_MODEL_FLAGS, + SYSMOBTS_PAR_TRX_NR, + _NUM_SYSMOBTS_PAR +}; + +extern const struct value_string sysmobts_par_names[_NUM_SYSMOBTS_PAR+1]; + +int sysmobts_par_get_int(enum sysmobts_par par, int *ret); +int sysmobts_par_set_int(enum sysmobts_par par, int val); +int sysmobts_par_get_buf(enum sysmobts_par par, uint8_t *buf, + unsigned int size); +int sysmobts_par_set_buf(enum sysmobts_par par, const uint8_t *buf, + unsigned int size); +int sysmobts_par_get_net(struct sysmobts_net_cfg *cfg); +int sysmobts_par_set_net(struct sysmobts_net_cfg *cfg); +int sysmobts_get_type(int *bts_type); +int sysmobts_get_trx(int *trx_number); +char *sysmobts_model(int bts_type, int trx_num); +int sysmobts_par_is_int(enum sysmobts_par par); + +#endif diff --git a/src/osmo-bts-sysmo/misc/sysmobts_util.c b/src/osmo-bts-sysmo/misc/sysmobts_util.c new file mode 100644 index 0000000..c9930d8 --- /dev/null +++ b/src/osmo-bts-sysmo/misc/sysmobts_util.c @@ -0,0 +1,256 @@ +/* sysmobts-util - access to hardware related parameters */ + +/* (C) 2012-2013 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "sysmobts_par.h" +#include "sysmobts_eeprom.h" + +enum act { + ACT_GET, + ACT_SET, + ACT_NET_GET, + ACT_NET_SET, +}; + +static enum act action; +static char *write_arg; +static int void_warranty; + + +static struct in_addr net_ip = { 0, }, net_dns = { 0, }, net_gw = { 0, }, net_mask = { 0, }; +static uint8_t net_mode = 0; + +static void print_help() +{ + const struct value_string *par = sysmobts_par_names; + + printf("sysmobts-util [--void-warranty -r | -w value] param_name\n"); + printf("sysmobts-util --net-read\n"); + printf("sysmobts-util --net-write --mode INT --ip IP_STR --gw IP_STR --dns IP_STR --net-mask IP_STR\n"); + printf("Possible param names:\n"); + + for (; par->str != NULL; par += 1) { + if (!sysmobts_par_is_int(par->value)) + continue; + printf(" %s\n", par->str); + } +} + +static int parse_options(int argc, char **argv) +{ + while (1) { + int option_idx = 0, c; + static const struct option long_options[] = { + { "help", 0, 0, 'h' }, + { "read", 0, 0, 'r' }, + { "void-warranty", 0, 0, 1000}, + { "write", 1, 0, 'w' }, + { "ip", 1, 0, 241 }, + { "gw", 1, 0, 242 }, + { "dns", 1, 0, 243 }, + { "net-mask", 1, 0, 244 }, + { "mode", 1, 0, 245 }, + { "net-read", 0, 0, 246 }, + { "net-write", 0, 0, 247 }, + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "rw:h", + long_options, &option_idx); + if (c == -1) + break; + switch (c) { + case 'r': + action = ACT_GET; + break; + case 'w': + action = ACT_SET; + write_arg = optarg; + break; + case 'h': + print_help(); + return -1; + break; + case 1000: + printf("Will void warranty on write.\n"); + void_warranty = 1; + break; + case 246: + action = ACT_NET_GET; + break; + case 247: + action = ACT_NET_SET; + break; + case 245: + net_mode = atoi(optarg); + break; + case 244: + inet_aton(optarg, &net_mask); + break; + case 243: + inet_aton(optarg, &net_dns); + break; + case 242: + inet_aton(optarg, &net_gw); + break; + case 241: + inet_aton(optarg, &net_ip); + break; + default: + printf("Unknown option %d/%c\n", c, c); + return -1; + } + } + + return 0; +} + +static const char *make_addr(uint32_t saddr) +{ + struct in_addr addr; + addr.s_addr = ntohl(saddr); + return inet_ntoa(addr); +} + +static void dump_net_cfg(struct sysmobts_net_cfg *net_cfg) +{ + if (net_cfg->mode == NET_MODE_DHCP) { + printf("IP=dhcp\n"); + printf("DNS=\n"); + printf("GATEWAY=\n"); + printf("NETMASK=\n"); + } else { + printf("IP=%s\n", make_addr(net_cfg->ip)); + printf("GATEWAY=%s\n", make_addr(net_cfg->gw)); + printf("DNS=%s\n", make_addr(net_cfg->dns)); + printf("NETMASK=%s\n", make_addr(net_cfg->mask)); + } +} + +static int handle_net(void) +{ + struct sysmobts_net_cfg net_cfg; + int rc; + + switch (action) { + case ACT_NET_GET: + rc = sysmobts_par_get_net(&net_cfg); + if (rc != 0) { + fprintf(stderr, "Error %d\n", rc); + exit(rc); + } + dump_net_cfg(&net_cfg); + break; + case ACT_NET_SET: + memset(&net_cfg, 0, sizeof(net_cfg)); + net_cfg.mode = net_mode; + net_cfg.ip = htonl(net_ip.s_addr); + net_cfg.mask = htonl(net_mask.s_addr); + net_cfg.gw = htonl(net_gw.s_addr); + net_cfg.dns = htonl(net_dns.s_addr); + printf("Going to write\n"); + dump_net_cfg(&net_cfg); + + rc = sysmobts_par_set_net(&net_cfg); + if (rc != 0) { + fprintf(stderr, "Error %d\n", rc); + exit(rc); + } + break; + default: + printf("Unhandled action %d\n", action); + } + return 0; +} + +int main(int argc, char **argv) +{ + const char *parname; + enum sysmobts_par par; + int rc, val; + + rc = parse_options(argc, argv); + if (rc < 0) + exit(2); + + if (action > ACT_SET) + return handle_net(); + + if (optind >= argc && action <+ ACT_NET_GET) { + fprintf(stderr, "You must specify the parameter name\n"); + exit(2); + } + parname = argv[optind]; + + rc = get_string_value(sysmobts_par_names, parname); + if (rc < 0) { + fprintf(stderr, "`%s' is not a valid parameter\n", parname); + exit(2); + } else + par = rc; + + switch (action) { + case ACT_GET: + rc = sysmobts_par_get_int(par, &val); + if (rc < 0) { + fprintf(stderr, "Error %d\n", rc); + goto err; + } + printf("%d\n", val); + break; + case ACT_SET: + rc = sysmobts_par_get_int(par, &val); + if (rc < 0) { + fprintf(stderr, "Error %d\n", rc); + goto err; + } + if (val != 0xFFFF && val != 0xFF && val != 0xFFFFFFFF && !void_warranty) { + fprintf(stderr, "Parameter is already set!\r\n"); + goto err; + } + rc = sysmobts_par_set_int(par, atoi(write_arg)); + if (rc < 0) { + fprintf(stderr, "Error %d\n", rc); + goto err; + } + printf("Success setting %s=%d\n", parname, + atoi(write_arg)); + break; + default: + fprintf(stderr, "Unsupported action\n"); + goto err; + } + + exit(0); + +err: + exit(1); +} + diff --git a/src/osmo-bts-sysmo/oml.c b/src/osmo-bts-sysmo/oml.c new file mode 100644 index 0000000..ce85a8b --- /dev/null +++ b/src/osmo-bts-sysmo/oml.c @@ -0,0 +1,1959 @@ +/* (C) 2011 by Harald Welte + * (C) 2013-2014 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "l1_if.h" +#include "femtobts.h" +#include "utils.h" + +static int mph_info_chan_confirm(struct gsm_lchan *lchan, + enum osmo_mph_info_type type, uint8_t cause) +{ + struct osmo_phsap_prim l1sap; + + memset(&l1sap, 0, sizeof(l1sap)); + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_CONFIRM, + NULL); + l1sap.u.info.type = type; + l1sap.u.info.u.act_cnf.chan_nr = gsm_lchan2chan_nr(lchan); + l1sap.u.info.u.act_cnf.cause = cause; + + return l1sap_up(lchan->ts->trx, &l1sap); +} + +enum sapi_cmd_type { + SAPI_CMD_ACTIVATE, + SAPI_CMD_CONFIG_CIPHERING, + SAPI_CMD_CONFIG_LOGCH_PARAM, + SAPI_CMD_SACCH_REL_MARKER, + SAPI_CMD_REL_MARKER, + SAPI_CMD_DEACTIVATE, +}; + +struct sapi_cmd { + struct llist_head entry; + GsmL1_Sapi_t sapi; + GsmL1_Dir_t dir; + enum sapi_cmd_type type; + int (*callback)(struct gsm_lchan *lchan, int status); +}; + +static const enum GsmL1_LogChComb_t pchan_to_logChComb[_GSM_PCHAN_MAX] = { + [GSM_PCHAN_NONE] = GsmL1_LogChComb_0, + [GSM_PCHAN_CCCH] = GsmL1_LogChComb_IV, + [GSM_PCHAN_CCCH_SDCCH4] = GsmL1_LogChComb_V, + [GSM_PCHAN_CCCH_SDCCH4_CBCH] = GsmL1_LogChComb_V, + [GSM_PCHAN_TCH_F] = GsmL1_LogChComb_I, + [GSM_PCHAN_TCH_H] = GsmL1_LogChComb_II, + [GSM_PCHAN_SDCCH8_SACCH8C] = GsmL1_LogChComb_VII, + [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = GsmL1_LogChComb_VII, + [GSM_PCHAN_PDCH] = GsmL1_LogChComb_XIII, + [GSM_PCHAN_UNKNOWN] = GsmL1_LogChComb_0, + /* + * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_TCH_F_TCH_H_PDCH should not be + * part of this, only "real" pchan values will be looked up here. + * See the callers of ts_connect_as(). + */ +}; + +static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb); + +static void *prim_init(GsmL1_Prim_t *prim, GsmL1_PrimId_t id, struct femtol1_hdl *gl1, + HANDLE hLayer3) +{ + prim->id = id; + + /* for some reason the hLayer1 and hlayer3 fields are not always at the + * same position in the GsmL1_Prim_t, so we have to have this ugly case + * statement here... */ + switch (id) { + case GsmL1_PrimId_MphInitReq: + //prim->u.mphInitReq.hLayer1 = gl1->hLayer1; + prim->u.mphInitReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphCloseReq: + prim->u.mphCloseReq.hLayer1 = gl1->hLayer1; + prim->u.mphCloseReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConnectReq: + prim->u.mphConnectReq.hLayer1 = gl1->hLayer1; + prim->u.mphConnectReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDisconnectReq: + prim->u.mphDisconnectReq.hLayer1 = gl1->hLayer1; + prim->u.mphDisconnectReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphActivateReq: + prim->u.mphActivateReq.hLayer1 = gl1->hLayer1; + prim->u.mphActivateReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDeactivateReq: + prim->u.mphDeactivateReq.hLayer1 = gl1->hLayer1; + prim->u.mphDeactivateReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConfigReq: + prim->u.mphConfigReq.hLayer1 = gl1->hLayer1; + prim->u.mphConfigReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphMeasureReq: + prim->u.mphMeasureReq.hLayer1 = gl1->hLayer1; + prim->u.mphMeasureReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphInitCnf: + prim->u.mphInitCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphCloseCnf: + prim->u.mphCloseCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConnectCnf: + prim->u.mphConnectCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDisconnectCnf: + prim->u.mphDisconnectCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphActivateCnf: + prim->u.mphActivateCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDeactivateCnf: + prim->u.mphDeactivateCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConfigCnf: + prim->u.mphConfigCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphMeasureCnf: + prim->u.mphMeasureCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphTimeInd: + break; + case GsmL1_PrimId_MphSyncInd: + break; + case GsmL1_PrimId_PhEmptyFrameReq: + prim->u.phEmptyFrameReq.hLayer1 = gl1->hLayer1; + break; + case GsmL1_PrimId_PhDataReq: + prim->u.phDataReq.hLayer1 = gl1->hLayer1; + break; + case GsmL1_PrimId_PhConnectInd: + break; + case GsmL1_PrimId_PhReadyToSendInd: + break; + case GsmL1_PrimId_PhDataInd: + break; + case GsmL1_PrimId_PhRaInd: + break; + default: + LOGP(DL1C, LOGL_ERROR, "unknown L1 primitive %u\n", id); + break; + } + return &prim->u; +} + +static HANDLE l1p_handle_for_trx(struct gsm_bts_trx *trx) +{ + struct gsm_bts *bts = trx->bts; + + osmo_static_assert(sizeof(HANDLE) >= 4, l1p_handle_is_at_least_32bit); + osmo_static_assert(sizeof(trx->nr) == 1, trx_nr_is_8bit); + osmo_static_assert(sizeof(bts->nr) == 1, bts_nr_is_8bit); + + return bts->nr << 24 + | trx->nr << 16; +} + +static HANDLE l1p_handle_for_ts(struct gsm_bts_trx_ts *ts) +{ + osmo_static_assert(sizeof(ts->nr) == 1, ts_nr_is_8bit); + + return l1p_handle_for_trx(ts->trx) + | ts->nr << 8; +} + + +static HANDLE l1p_handle_for_lchan(struct gsm_lchan *lchan) +{ + osmo_static_assert(sizeof(lchan->nr) == 1, lchan_nr_is_8bit); + + return l1p_handle_for_ts(lchan->ts) + | lchan->nr; +} + +GsmL1_Status_t prim_status(GsmL1_Prim_t *prim) +{ + /* for some reason the Status field is not always at the same position + * in the GsmL1_Prim_t, so we have to have this ugly case statement here... */ + switch (prim->id) { + case GsmL1_PrimId_MphInitCnf: + return prim->u.mphInitCnf.status; + case GsmL1_PrimId_MphCloseCnf: + return prim->u.mphCloseCnf.status; + case GsmL1_PrimId_MphConnectCnf: + return prim->u.mphConnectCnf.status; + case GsmL1_PrimId_MphDisconnectCnf: + return prim->u.mphDisconnectCnf.status; + case GsmL1_PrimId_MphActivateCnf: + return prim->u.mphActivateCnf.status; + case GsmL1_PrimId_MphDeactivateCnf: + return prim->u.mphDeactivateCnf.status; + case GsmL1_PrimId_MphConfigCnf: + return prim->u.mphConfigCnf.status; + case GsmL1_PrimId_MphMeasureCnf: + return prim->u.mphMeasureCnf.status; + default: + break; + } + return GsmL1_Status_Success; +} + +#if 0 +static int compl_cb_send_oml_msg(struct msgb *l1_msg, void *data) +{ + struct msgb *resp_msg = data; + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + + if (prim_status(l1p) != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_ERROR, "Rx %s, status: %s\n", + get_value_string(femtobts_l1prim_names, l1p->id), + get_value_string(femtobts_l1status_names, cc->status)); + return 0; + } + + msgb_free(l1_msg); + + return abis_nm_sendmsg(msg); +} +#endif + +int lchan_activate(struct gsm_lchan *lchan); + +static int opstart_compl(struct gsm_abis_mo *mo, struct msgb *l1_msg) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_Status_t status = prim_status(l1p); + + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_ERROR, "Rx %s, status: %s\n", + get_value_string(femtobts_l1prim_names, l1p->id), + get_value_string(femtobts_l1status_names, status)); + msgb_free(l1_msg); + return oml_mo_opstart_nack(mo, NM_NACK_CANT_PERFORM); + } + + msgb_free(l1_msg); + + /* Set to Operational State: Enabled */ + oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); + + /* ugly hack to auto-activate all SAPIs for the BCCH/CCCH on TS0 */ + if (mo->obj_class == NM_OC_CHANNEL && mo->obj_inst.trx_nr == 0 && + mo->obj_inst.ts_nr == 0) { + struct gsm_lchan *cbch = gsm_bts_get_cbch(mo->bts); + DEBUGP(DL1C, "====> trying to activate lchans of BCCH\n"); + mo->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind = + LCHAN_REL_ACT_OML; + lchan_activate(&mo->bts->c0->ts[0].lchan[CCCH_LCHAN]); + if (cbch) { + cbch->rel_act_kind = LCHAN_REL_ACT_OML; + lchan_activate(cbch); + } + } + + /* Send OPSTART ack */ + return oml_mo_opstart_ack(mo); +} + +static int opstart_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + struct gsm_abis_mo *mo; + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphConnectCnf_t *cnf = &l1p->u.mphConnectCnf; + + mo = &trx->ts[cnf->u8Tn].mo; + return opstart_compl(mo, l1_msg); +} + +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0) +static int trx_mute_on_init_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + SuperFemto_Prim_t *sysp = msgb_sysprim(resp); + GsmL1_Status_t status; + + status = sysp->u.muteRfCnf.status; + + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_FATAL, "Rx RF-MUTE.conf status=%s\n", + get_value_string(femtobts_l1status_names, status)); + bts_shutdown(trx->bts, "RF-MUTE failure"); + } + + msgb_free(resp); + + return 0; +} +#endif + +static int trx_init_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); + + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphInitCnf_t *ic = &l1p->u.mphInitCnf; + + LOGP(DL1C, LOGL_INFO, "Rx MPH-INIT.conf (status=%s)\n", + get_value_string(femtobts_l1status_names, ic->status)); + + /* store layer1 handle */ + if (ic->status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_FATAL, "Rx MPH-INIT.conf status=%s\n", + get_value_string(femtobts_l1status_names, ic->status)); + bts_shutdown(trx->bts, "MPH-INIT failure"); + } + + fl1h->hLayer1 = ic->hLayer1; + +#if SUPERFEMTO_API_VERSION >= SUPERFEMTO_API(3,6,0) + /* If the TRX was already locked the MphInit would have undone it */ + if (trx->mo.nm_state.administrative == NM_STATE_LOCKED) + trx_rf_lock(trx, 1, trx_mute_on_init_cb); +#endif + + /* Begin to ramp up the power */ + power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0); + + return opstart_compl(&trx->mo, l1_msg); +} + +int gsm_abis_mo_check_attr(const struct gsm_abis_mo *mo, const uint8_t *attr_ids, + unsigned int num_attr_ids) +{ + unsigned int i; + + if (!mo->nm_attr) + return 0; + + for (i = 0; i < num_attr_ids; i++) { + if (!TLVP_PRESENT(mo->nm_attr, attr_ids[i])) + return 0; + } + return 1; +} + +static const uint8_t trx_rqd_attr[] = { NM_ATT_RF_MAXPOWR_R }; + +/* initialize the layer1 */ +static int trx_init(struct gsm_bts_trx *trx) +{ + struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); + struct msgb *msg; + GsmL1_MphInitReq_t *mi_req; + GsmL1_DeviceParam_t *dev_par; + int femto_band; + int initial_mdBm = power_ramp_initial_power_mdBm(trx); + + if (!gsm_abis_mo_check_attr(&trx->mo, trx_rqd_attr, + ARRAY_SIZE(trx_rqd_attr))) { + /* HACK: spec says we need to decline, but openbsc + * doesn't deal with this very well */ + return oml_mo_opstart_ack(&trx->mo); + //return oml_mo_opstart_nack(&trx->mo, NM_NACK_CANT_PERFORM); + } + + femto_band = sysmobts_select_femto_band(trx, trx->arfcn); + if (femto_band < 0) { + LOGP(DL1C, LOGL_ERROR, "Unsupported GSM band %s\n", + gsm_band_name(trx->bts->band)); + } + + msg = l1p_msgb_alloc(); + mi_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphInitReq, fl1h, + l1p_handle_for_trx(trx)); + dev_par = &mi_req->deviceParam; + dev_par->devType = GsmL1_DevType_TxdRxu; + dev_par->freqBand = femto_band; + dev_par->u16Arfcn = trx->arfcn; + dev_par->u16BcchArfcn = trx->bts->c0->arfcn; + dev_par->u8NbTsc = trx->bts->bsic & 7; + dev_par->fRxPowerLevel = trx_ms_pwr_ctrl_is_osmo(trx) + ? 0.0 : trx->bts->ul_power_target; + + dev_par->fTxPowerLevel = ((float) initial_mdBm) / 1000; + LOGP(DL1C, LOGL_NOTICE, "Init TRX (ARFCN %u, TSC %u, RxPower % 2f dBm, " + "TxPower % 2.2f dBm\n", dev_par->u16Arfcn, dev_par->u8NbTsc, + dev_par->fRxPowerLevel, dev_par->fTxPowerLevel); + trx->power_params.p_total_cur_mdBm = trx->power_params.ramp.max_initial_pout_mdBm; + + /* send MPH-INIT-REQ, wait for MPH-INIT-CNF */ + return l1if_gsm_req_compl(fl1h, msg, trx_init_compl_cb, NULL); +} + +uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx) +{ + struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); + + return fl1h->hLayer1; +} + +static int trx_close_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + msgb_free(l1_msg); + return 0; +} + +int bts_model_trx_close(struct gsm_bts_trx *trx) +{ + struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); + struct msgb *msg; + + msg = l1p_msgb_alloc(); + prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphCloseReq, fl1h, + l1p_handle_for_trx(trx)); + LOGP(DL1C, LOGL_NOTICE, "Close TRX %u\n", trx->nr); + + return l1if_gsm_req_compl(fl1h, msg, trx_close_compl_cb, NULL); +} + +static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb) +{ + struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); + uint8_t mute[8]; + int i; + + for (i = 0; i < ARRAY_SIZE(mute); ++i) + mute[i] = locked ? 1 : 0; + + return l1if_mute_rf(fl1h, mute, cb); +} + +int oml_mo_rf_lock_chg(struct gsm_abis_mo *mo, uint8_t mute_state[8], + int success) +{ + if (success) { + int i; + int is_locked = 1; + + for (i = 0; i < 8; ++i) + if (!mute_state[i]) + is_locked = 0; + + mo->nm_state.administrative = + is_locked ? NM_STATE_LOCKED : NM_STATE_UNLOCKED; + mo->procedure_pending = 0; + return oml_mo_statechg_ack(mo); + } else { + mo->procedure_pending = 0; + return oml_mo_statechg_nack(mo, NM_NACK_REQ_NOT_GRANT); + } +} + +static int ts_connect_as(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config pchan, + l1if_compl_cb *cb, void *data) +{ + struct msgb *msg = l1p_msgb_alloc(); + struct femtol1_hdl *fl1h = trx_femtol1_hdl(ts->trx); + GsmL1_MphConnectReq_t *cr; + + if (pchan == GSM_PCHAN_TCH_F_PDCH + || pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + LOGP(DL1C, LOGL_ERROR, + "%s Requested TS connect as %s," + " expected a specific pchan instead\n", + gsm_ts_and_pchan_name(ts), gsm_pchan_name(pchan)); + return -EINVAL; + } + + cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConnectReq, fl1h, + l1p_handle_for_ts(ts)); + cr->u8Tn = ts->nr; + cr->logChComb = pchan_to_logChComb[pchan]; + + DEBUGP(DL1C, "%s pchan=%s ts_connect_as(%s) logChComb=%s\n", + gsm_lchan_name(ts->lchan), gsm_pchan_name(ts->pchan), + gsm_pchan_name(pchan), get_value_string(femtobts_chcomb_names, + cr->logChComb)); + + return l1if_gsm_req_compl(fl1h, msg, cb, NULL); +} + +static int ts_opstart(struct gsm_bts_trx_ts *ts) +{ + enum gsm_phys_chan_config pchan = ts->pchan; + switch (pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + ts->dyn.pchan_is = ts->dyn.pchan_want = GSM_PCHAN_NONE; + /* First connect as NONE, until first RSL CHAN ACT. */ + pchan = GSM_PCHAN_NONE; + break; + case GSM_PCHAN_TCH_F_PDCH: + /* First connect as TCH/F, expecting PDCH ACT. */ + pchan = GSM_PCHAN_TCH_F; + break; + default: + /* simply use ts->pchan */ + break; + } + return ts_connect_as(ts, pchan, opstart_compl_cb, NULL); +} + +GsmL1_Sapi_t lchan_to_GsmL1_Sapi_t(const struct gsm_lchan *lchan) +{ + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + return GsmL1_Sapi_TchF; + case GSM_LCHAN_TCH_H: + return GsmL1_Sapi_TchH; + default: + LOGP(DL1C, LOGL_NOTICE, "%s cannot determine L1 SAPI\n", + gsm_lchan_name(lchan)); + break; + } + return GsmL1_Sapi_Idle; +} + +GsmL1_SubCh_t lchan_to_GsmL1_SubCh_t(const struct gsm_lchan *lchan) +{ + enum gsm_phys_chan_config pchan = lchan->ts->pchan; + + if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) + pchan = lchan->ts->dyn.pchan_want; + + switch (pchan) { + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + if (lchan->type == GSM_LCHAN_CCCH) + return GsmL1_SubCh_NA; + /* fall-through */ + case GSM_PCHAN_TCH_H: + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + return lchan->nr; + case GSM_PCHAN_NONE: + case GSM_PCHAN_CCCH: + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_PDCH: + case GSM_PCHAN_TCH_F_PDCH: + case GSM_PCHAN_UNKNOWN: + default: + /* case GSM_PCHAN_TCH_F_TCH_H_PDCH: is caught above */ + return GsmL1_SubCh_NA; + } + + return GsmL1_SubCh_NA; +} + +struct sapi_dir { + GsmL1_Sapi_t sapi; + GsmL1_Dir_t dir; +}; + +static const struct sapi_dir ccch_sapis[] = { + { GsmL1_Sapi_Fcch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Sch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Bcch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Agch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Pch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink }, +}; + +static const struct sapi_dir tchf_sapis[] = { + { GsmL1_Sapi_TchF, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_TchF, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_FacchF, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_FacchF, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink }, +}; + +static const struct sapi_dir tchh_sapis[] = { + { GsmL1_Sapi_TchH, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_TchH, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_FacchH, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_FacchH, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink }, +}; + +static const struct sapi_dir sdcch_sapis[] = { + { GsmL1_Sapi_Sdcch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Sdcch, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink }, +}; + +static const struct sapi_dir cbch_sapis[] = { + { GsmL1_Sapi_Cbch, GsmL1_Dir_TxDownlink }, + /* Does the CBCH really have a SACCH in Downlink? */ + { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink }, +}; + +static const struct sapi_dir pdtch_sapis[] = { + { GsmL1_Sapi_Pdtch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Pdtch, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_Ptcch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Prach, GsmL1_Dir_RxUplink }, +#if 0 + { GsmL1_Sapi_Ptcch, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_Pacch, GsmL1_Dir_TxDownlink }, +#endif +}; + +static const struct sapi_dir ho_sapis[] = { + { GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink }, +}; + +struct lchan_sapis { + const struct sapi_dir *sapis; + unsigned int num_sapis; +}; + +static const struct lchan_sapis sapis_for_lchan[_GSM_LCHAN_MAX] = { + [GSM_LCHAN_SDCCH] = { + .sapis = sdcch_sapis, + .num_sapis = ARRAY_SIZE(sdcch_sapis), + }, + [GSM_LCHAN_TCH_F] = { + .sapis = tchf_sapis, + .num_sapis = ARRAY_SIZE(tchf_sapis), + }, + [GSM_LCHAN_TCH_H] = { + .sapis = tchh_sapis, + .num_sapis = ARRAY_SIZE(tchh_sapis), + }, + [GSM_LCHAN_CCCH] = { + .sapis = ccch_sapis, + .num_sapis = ARRAY_SIZE(ccch_sapis), + }, + [GSM_LCHAN_PDTCH] = { + .sapis = pdtch_sapis, + .num_sapis = ARRAY_SIZE(pdtch_sapis), + }, + [GSM_LCHAN_CBCH] = { + .sapis = cbch_sapis, + .num_sapis = ARRAY_SIZE(cbch_sapis), + }, +}; + +static const struct lchan_sapis sapis_for_ho = { + .sapis = ho_sapis, + .num_sapis = ARRAY_SIZE(ho_sapis), +}; + +static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd); +static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd); +static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd); +static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cmd); + +static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir); +static int lchan_deactivate_sapis(struct gsm_lchan *lchan); + +/** + * Execute the first SAPI command of the queue. In case of the markers + * this method is re-entrant so we need to make sure to remove a command + * from the list before calling a function that will queue a command. + * + * \return 0 in case no Gsm Request was sent, 1 otherwise + */ +static int sapi_queue_exeute(struct gsm_lchan *lchan) +{ + int res; + struct sapi_cmd *cmd; + + cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry); + + switch (cmd->type) { + case SAPI_CMD_ACTIVATE: + mph_send_activate_req(lchan, cmd); + res = 1; + break; + case SAPI_CMD_CONFIG_CIPHERING: + mph_send_config_ciphering(lchan, cmd); + res = 1; + break; + case SAPI_CMD_CONFIG_LOGCH_PARAM: + mph_send_config_logchpar(lchan, cmd); + res = 1; + break; + case SAPI_CMD_SACCH_REL_MARKER: + llist_del(&cmd->entry); + talloc_free(cmd); + res = check_sapi_release(lchan, GsmL1_Sapi_Sacch, + GsmL1_Dir_TxDownlink); + res |= check_sapi_release(lchan, GsmL1_Sapi_Sacch, + GsmL1_Dir_RxUplink); + break; + case SAPI_CMD_REL_MARKER: + llist_del(&cmd->entry); + talloc_free(cmd); + res = lchan_deactivate_sapis(lchan); + break; + case SAPI_CMD_DEACTIVATE: + mph_send_deactivate_req(lchan, cmd); + res = 1; + break; + default: + LOGP(DL1C, LOGL_NOTICE, + "Unimplemented command type %d\n", cmd->type); + llist_del(&cmd->entry); + talloc_free(cmd); + res = 0; + abort(); + break; + } + + return res; +} + +static void sapi_queue_send(struct gsm_lchan *lchan) +{ + int res; + + do { + res = sapi_queue_exeute(lchan); + } while (res == 0 && !llist_empty(&lchan->sapi_cmds)); +} + +static void sapi_queue_dispatch(struct gsm_lchan *lchan, int status) +{ + int end; + struct sapi_cmd *cmd = llist_entry(lchan->sapi_cmds.next, + struct sapi_cmd, entry); + llist_del(&cmd->entry); + end = llist_empty(&lchan->sapi_cmds); + + if (cmd->callback) + cmd->callback(lchan, status); + talloc_free(cmd); + + if (end || llist_empty(&lchan->sapi_cmds)) { + LOGP(DL1C, LOGL_DEBUG, + "%s End of SAPI cmd queue encountered.%s\n", + gsm_lchan_name(lchan), + llist_empty(&lchan->sapi_cmds) + ? " Queue is now empty." + : " More pending."); + return; + } + + sapi_queue_send(lchan); +} + +/** + * Queue and possible execute a SAPI command. Return 1 in case the command was + * already executed and 0 in case if it was only put into the queue + */ +static int queue_sapi_command(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + int start = llist_empty(&lchan->sapi_cmds); + llist_add_tail(&cmd->entry, &lchan->sapi_cmds); + + if (!start) + return 0; + + sapi_queue_send(lchan); + return 1; +} + +static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + enum lchan_sapi_state status; + struct sapi_cmd *cmd; + struct gsm_lchan *lchan; + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphActivateCnf_t *ic = &l1p->u.mphActivateCnf; + + /* get the lchan from the information we supplied */ + lchan = l1if_hLayer_to_lchan(trx, ic->hLayer3); + if (!lchan) { + LOGP(DL1C, LOGL_ERROR, + "Failed to find lchan for hLayer3=0x%x\n", ic->hLayer3); + goto err; + } + + LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.conf (%s ", + gsm_lchan_name(lchan), + get_value_string(femtobts_l1sapi_names, ic->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(femtobts_dir_names, ic->dir)); + + if (ic->status == GsmL1_Status_Success) { + DEBUGP(DL1C, "Successful activation of L1 SAPI %s on TS %u\n", + get_value_string(femtobts_l1sapi_names, ic->sapi), ic->u8Tn); + status = LCHAN_SAPI_S_ASSIGNED; + } else { + LOGP(DL1C, LOGL_ERROR, "Error activating L1 SAPI %s on TS %u: %s\n", + get_value_string(femtobts_l1sapi_names, ic->sapi), ic->u8Tn, + get_value_string(femtobts_l1status_names, ic->status)); + status = LCHAN_SAPI_S_ERROR; + } + + if (ic->dir & GsmL1_Dir_TxDownlink) + lchan->sapis_dl[ic->sapi] = status; + if (ic->dir & GsmL1_Dir_RxUplink) + lchan->sapis_ul[ic->sapi] = status; + + if (llist_empty(&lchan->sapi_cmds)) { + LOGP(DL1C, LOGL_ERROR, + "%s Got activation confirmation with empty queue\n", + gsm_lchan_name(lchan)); + goto err; + } + + cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry); + if (cmd->sapi != ic->sapi || cmd->dir != ic->dir || + cmd->type != SAPI_CMD_ACTIVATE) { + LOGP(DL1C, LOGL_ERROR, + "%s Confirmation mismatch (%d, %d) (%d, %d)\n", + gsm_lchan_name(lchan), cmd->sapi, cmd->dir, + ic->sapi, ic->dir); + goto err; + } + + sapi_queue_dispatch(lchan, ic->status); + +err: + msgb_free(l1_msg); + + return 0; +} + +uint32_t l1if_lchan_to_hLayer(struct gsm_lchan *lchan) +{ + return 0xBB + | (lchan->nr << 8) + | (lchan->ts->nr << 16) + | (lchan->ts->trx->nr << 24); +} + +/* obtain a ptr to the lapdm_channel for a given hLayer */ +struct gsm_lchan * +l1if_hLayer_to_lchan(struct gsm_bts_trx *trx, uint32_t hLayer2) +{ + uint8_t magic = hLayer2 & 0xff; + uint8_t ts_nr = (hLayer2 >> 16) & 0xff; + uint8_t lchan_nr = (hLayer2 >> 8)& 0xff; + struct gsm_bts_trx_ts *ts; + + if (magic != 0xBB) + return NULL; + + /* FIXME: if we actually run on the BTS, the 32bit field is large + * enough to simply put a pointer inside. */ + if (ts_nr >= ARRAY_SIZE(trx->ts)) + return NULL; + + ts = &trx->ts[ts_nr]; + + if (lchan_nr >= ARRAY_SIZE(ts->lchan)) + return NULL; + + return &ts->lchan[lchan_nr]; +} + +/* we regularly check if the DSP L1 is still sending us primitives. + * if not, we simply stop the BTS program (and be re-spawned) */ +static void alive_timer_cb(void *data) +{ + struct femtol1_hdl *fl1h = data; + + if (fl1h->alive_prim_cnt == 0) { + LOGP(DL1C, LOGL_FATAL, "DSP L1 is no longer sending primitives!\n"); + exit(23); + } + fl1h->alive_prim_cnt = 0; + osmo_timer_schedule(&fl1h->alive_timer, 5, 0); +} + +static void clear_amr_params(GsmL1_LogChParam_t *lch_par) +{ + int j; + /* common for the SIGN, V1 and EFR: */ + lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_NA; + lch_par->tch.amrInitCodecMode = GsmL1_AmrCodecMode_Unset; + for (j = 0; j < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); j++) + lch_par->tch.amrActiveCodecSet[j] = GsmL1_AmrCodec_Unset; +} + +static void set_payload_format(GsmL1_LogChParam_t *lch_par) +{ +#ifdef L1_HAS_RTP_MODE +#ifdef USE_L1_RTP_MODE + lch_par->tch.tchPlFmt = GsmL1_TchPlFmt_Rtp; +#else + lch_par->tch.tchPlFmt = GsmL1_TchPlFmt_If2; +#endif /* USE_L1_RTP_MODE */ +#endif /* L1_HAS_RTP_MODE */ +} + +static void lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan) +{ + struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr; + struct gsm48_multi_rate_conf *mr_conf = + (struct gsm48_multi_rate_conf *) amr_mrc->gsm48_ie; + int j; + + LOGP(DL1C, LOGL_INFO, "%s: %s tch_mode=0x%02x\n", + gsm_lchan_name(lchan), __FUNCTION__, lchan->tch_mode); + + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: + /* we have to set some TCH payload type even if we don't + * know yet what codec we will use later on */ + if (lchan->type == GSM_LCHAN_TCH_F) + lch_par->tch.tchPlType = GsmL1_TchPlType_Fr; + else + lch_par->tch.tchPlType = GsmL1_TchPlType_Hr; + clear_amr_params(lch_par); + break; + case GSM48_CMODE_SPEECH_V1: + if (lchan->type == GSM_LCHAN_TCH_F) + lch_par->tch.tchPlType = GsmL1_TchPlType_Fr; + else + lch_par->tch.tchPlType = GsmL1_TchPlType_Hr; + set_payload_format(lch_par); + clear_amr_params(lch_par); + break; + case GSM48_CMODE_SPEECH_EFR: + lch_par->tch.tchPlType = GsmL1_TchPlType_Efr; + set_payload_format(lch_par); + clear_amr_params(lch_par); + break; + case GSM48_CMODE_SPEECH_AMR: + lch_par->tch.tchPlType = GsmL1_TchPlType_Amr; + set_payload_format(lch_par); + lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_Odd; /* FIXME? */ + lch_par->tch.amrInitCodecMode = amr_get_initial_mode(lchan); + + /* initialize to clean state */ + for (j = 0; j < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); j++) + lch_par->tch.amrActiveCodecSet[j] = GsmL1_AmrCodec_Unset; + + j = 0; + if (mr_conf->m4_75) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_4_75; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + + if (mr_conf->m5_15) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_5_15; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + + if (mr_conf->m5_90) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_5_9; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + + if (mr_conf->m6_70) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_6_7; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + + if (mr_conf->m7_40) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_7_4; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + + if (mr_conf->m7_95) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_7_95; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + + if (mr_conf->m10_2) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_10_2; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + if (mr_conf->m12_2) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_12_2; + break; + case GSM48_CMODE_DATA_14k5: + case GSM48_CMODE_DATA_12k0: + case GSM48_CMODE_DATA_6k0: + case GSM48_CMODE_DATA_3k6: + LOGP(DL1C, LOGL_ERROR, "%s: CSD not supported!\n", + gsm_lchan_name(lchan)); + break; + } +} + +static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx); + struct msgb *msg = l1p_msgb_alloc(); + int sapi = cmd->sapi; + int dir = cmd->dir; + GsmL1_MphActivateReq_t *act_req; + GsmL1_LogChParam_t *lch_par; + + act_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphActivateReq, + fl1h, l1p_handle_for_lchan(lchan)); + lch_par = &act_req->logChPrm; + act_req->u8Tn = lchan->ts->nr; + act_req->subCh = lchan_to_GsmL1_SubCh_t(lchan); + act_req->dir = dir; + act_req->sapi = sapi; + act_req->hLayer2 = l1if_lchan_to_hLayer(lchan); + act_req->hLayer3 = act_req->hLayer2; + + switch (act_req->sapi) { + case GsmL1_Sapi_Rach: + lch_par->rach.u8Bsic = lchan->ts->trx->bts->bsic; + break; + case GsmL1_Sapi_Agch: + lch_par->agch.u8NbrOfAgch = num_agch(lchan->ts->trx, lchan->name); + break; + case GsmL1_Sapi_TchH: + case GsmL1_Sapi_TchF: + lchan2lch_par(lch_par, lchan); + /* + * Be sure that every packet is received, even if it + * fails. In this case the length might be lower or 0. + */ + act_req->fBFILevel = -200.0f; + break; + case GsmL1_Sapi_Ptcch: + lch_par->ptcch.u8Bsic = lchan->ts->trx->bts->bsic; + break; + case GsmL1_Sapi_Prach: + lch_par->prach.u8Bsic = lchan->ts->trx->bts->bsic; + break; + case GsmL1_Sapi_Sacch: + /* + * For the SACCH we need to set the u8MsPowerLevel when + * doing manual MS power control. + */ + if (trx_ms_pwr_ctrl_is_osmo(lchan->ts->trx)) + lch_par->sacch.u8MsPowerLevel = lchan->ms_power_ctrl.current; + /* fall through */ + case GsmL1_Sapi_Pdtch: + case GsmL1_Sapi_Pacch: + /* + * Be sure that every packet is received, even if it + * fails. In this case the length might be lower or 0. + */ + act_req->fBFILevel = -200.0f; + break; + default: + break; + } + + LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.req (hL2=0x%08x, %s ", + gsm_lchan_name(lchan), act_req->hLayer2, + get_value_string(femtobts_l1sapi_names, act_req->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(femtobts_dir_names, act_req->dir)); + + /* send the primitive for all GsmL1_Sapi_* that match the LCHAN */ + return l1if_gsm_req_compl(fl1h, msg, lchan_act_compl_cb, NULL); +} + +static void sapi_clear_queue(struct llist_head *queue) +{ + struct sapi_cmd *next, *tmp; + + llist_for_each_entry_safe(next, tmp, queue, entry) { + llist_del(&next->entry); + talloc_free(next); + } +} + +static int sapi_activate_cb(struct gsm_lchan *lchan, int status) +{ + struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx); + + /* FIXME: Error handling */ + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_ERROR, + "%s act failed mark broken due status: %d\n", + gsm_lchan_name(lchan), status); + lchan_set_state(lchan, LCHAN_S_BROKEN); + sapi_clear_queue(&lchan->sapi_cmds); + mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, RSL_ERR_PROCESSOR_OVERLOAD); + return -1; + } + + if (!llist_empty(&lchan->sapi_cmds)) + return 0; + + if (lchan->state != LCHAN_S_ACT_REQ) + return 0; + + lchan_set_state(lchan, LCHAN_S_ACTIVE); + mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, 0); + + /* set the initial ciphering parameters for both directions */ + l1if_set_ciphering(fl1h, lchan, 1); + l1if_set_ciphering(fl1h, lchan, 0); + if (lchan->encr.alg_id) + lchan->ciph_state = LCHAN_CIPH_RXTX_REQ; + else + lchan->ciph_state = LCHAN_CIPH_NONE; + + return 0; +} + +static void enqueue_sapi_act_cmd(struct gsm_lchan *lchan, int sapi, int dir) +{ + struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + + cmd->sapi = sapi; + cmd->dir = dir; + cmd->type = SAPI_CMD_ACTIVATE; + cmd->callback = sapi_activate_cb; + queue_sapi_command(lchan, cmd); +} + +int lchan_activate(struct gsm_lchan *lchan) +{ + struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx); + const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type]; + unsigned int i; + + lchan_set_state(lchan, LCHAN_S_ACT_REQ); + + if (!llist_empty(&lchan->sapi_cmds)) + LOGP(DL1C, LOGL_ERROR, + "%s Trying to activate lchan, but commands in queue\n", + gsm_lchan_name(lchan)); + + /* override the regular SAPIs if this is the first hand-over + * related activation of the LCHAN */ + if (lchan->ho.active == HANDOVER_ENABLED) + s4l = &sapis_for_ho; + + for (i = 0; i < s4l->num_sapis; i++) { + int sapi = s4l->sapis[i].sapi; + int dir = s4l->sapis[i].dir; + + if (sapi == GsmL1_Sapi_Sch) { + /* once we activate the SCH, we should get MPH-TIME.ind */ + fl1h->alive_timer.cb = alive_timer_cb; + fl1h->alive_timer.data = fl1h; + fl1h->alive_prim_cnt = 0; + osmo_timer_schedule(&fl1h->alive_timer, 5, 0); + } + enqueue_sapi_act_cmd(lchan, sapi, dir); + } + +#warning "FIXME: Should this be in sapi_activate_cb?" + lchan_init_lapdm(lchan); + + return 0; +} + +const struct value_string femtobts_l1cfgt_names[] = { + { GsmL1_ConfigParamId_SetNbTsc, "Set NB TSC" }, + { GsmL1_ConfigParamId_SetTxPowerLevel, "Set Tx power level" }, + { GsmL1_ConfigParamId_SetLogChParams, "Set logical channel params" }, + { GsmL1_ConfigParamId_SetCipheringParams,"Configure ciphering params" }, + { 0, NULL } +}; + +static void dump_lch_par(int logl, GsmL1_LogChParam_t *lch_par, GsmL1_Sapi_t sapi) +{ + int i; + + switch (sapi) { + case GsmL1_Sapi_Rach: + LOGPC(DL1C, logl, "BSIC=0x%08x", lch_par->rach.u8Bsic); + break; + case GsmL1_Sapi_Agch: + LOGPC(DL1C, logl, "BS_AG_BLKS_RES=%u ", + lch_par->agch.u8NbrOfAgch); + break; + case GsmL1_Sapi_Sacch: + LOGPC(DL1C, logl, "MS Power Level 0x%02x", + lch_par->sacch.u8MsPowerLevel); + break; + case GsmL1_Sapi_TchF: + case GsmL1_Sapi_TchH: + LOGPC(DL1C, logl, "amrCmiPhase=0x%02x amrInitCodec=0x%02x (", + lch_par->tch.amrCmiPhase, + lch_par->tch.amrInitCodecMode); + for (i = 0; i < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); i++) { + LOGPC(DL1C, logl, "%x ", + lch_par->tch.amrActiveCodecSet[i]); + } + break; + case GsmL1_Sapi_Ptcch: + LOGPC(DL1C, logl, "BSIC=0x%08x", lch_par->ptcch.u8Bsic); + break; + case GsmL1_Sapi_Prach: + LOGPC(DL1C, logl, "BSIC=0x%08x", lch_par->prach.u8Bsic); + break; + default: + break; + } + LOGPC(DL1C, logl, ")\n"); +} + +static int chmod_txpower_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf; + + LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ", + gsm_trx_name(trx), + get_value_string(femtobts_l1cfgt_names, cc->cfgParamId)); + + LOGPC(DL1C, LOGL_INFO, "setTxPower %f dBm\n", + cc->cfgParams.setTxPowerLevel.fTxPowerLevel); + + power_trx_change_compl(trx, + (int) (cc->cfgParams.setTxPowerLevel.fTxPowerLevel * 1000)); + + msgb_free(l1_msg); + + return 0; +} + +static int chmod_modif_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + struct gsm_lchan *lchan; + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf; + + /* get the lchan from the information we supplied */ + lchan = l1if_hLayer_to_lchan(trx, cc->hLayer3); + if (!lchan) { + LOGP(DL1C, LOGL_ERROR, + "Failed to find lchan for hLayer3=0x%x\n", cc->hLayer3); + goto err; + } + + LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ", + gsm_lchan_name(lchan), + get_value_string(femtobts_l1cfgt_names, cc->cfgParamId)); + + switch (cc->cfgParamId) { + case GsmL1_ConfigParamId_SetLogChParams: + dump_lch_par(LOGL_INFO, + &cc->cfgParams.setLogChParams.logChParams, + cc->cfgParams.setLogChParams.sapi); + + sapi_queue_dispatch(lchan, cc->status); + break; + case GsmL1_ConfigParamId_SetCipheringParams: + switch (lchan->ciph_state) { + case LCHAN_CIPH_RX_REQ: + LOGPC(DL1C, LOGL_INFO, "RX_REQ -> RX_CONF\n"); + lchan->ciph_state = LCHAN_CIPH_RX_CONF; + break; + case LCHAN_CIPH_RX_CONF_TX_REQ: + LOGPC(DL1C, LOGL_INFO, "RX_CONF_TX_REQ -> RXTX_CONF\n"); + lchan->ciph_state = LCHAN_CIPH_RXTX_CONF; + break; + case LCHAN_CIPH_RXTX_REQ: + LOGPC(DL1C, LOGL_INFO, "RXTX_REQ -> RX_CONF_TX_REQ\n"); + lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ; + break; + case LCHAN_CIPH_NONE: + LOGPC(DL1C, LOGL_INFO, "\n"); + break; + default: + LOGPC(DL1C, LOGL_INFO, "unhandled state %u\n", lchan->ciph_state); + break; + } + if (llist_empty(&lchan->sapi_cmds)) { + LOGP(DL1C, LOGL_ERROR, + "%s Got ciphering conf with empty queue\n", + gsm_lchan_name(lchan)); + goto err; + } + + sapi_queue_dispatch(lchan, cc->status); + break; + case GsmL1_ConfigParamId_SetNbTsc: + default: + LOGPC(DL1C, LOGL_INFO, "\n"); + break; + } + +err: + msgb_free(l1_msg); + + return 0; +} + +static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + struct gsm_bts_trx *trx = lchan->ts->trx; + struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); + struct msgb *msg = l1p_msgb_alloc(); + GsmL1_MphConfigReq_t *conf_req; + GsmL1_LogChParam_t *lch_par; + + /* channel mode, encryption and/or multirate have changed */ + + /* update multi-rate config */ + conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h, + l1p_handle_for_lchan(lchan)); + conf_req->cfgParamId = GsmL1_ConfigParamId_SetLogChParams; + conf_req->cfgParams.setLogChParams.sapi = cmd->sapi; + conf_req->cfgParams.setLogChParams.u8Tn = lchan->ts->nr; + conf_req->cfgParams.setLogChParams.subCh = lchan_to_GsmL1_SubCh_t(lchan); + conf_req->cfgParams.setLogChParams.dir = cmd->dir; + conf_req->hLayer3 = l1if_lchan_to_hLayer(lchan); + + lch_par = &conf_req->cfgParams.setLogChParams.logChParams; + lchan2lch_par(lch_par, lchan); + + /* Update the MS Power Level */ + if (cmd->sapi == GsmL1_Sapi_Sacch && trx_ms_pwr_ctrl_is_osmo(trx)) + lch_par->sacch.u8MsPowerLevel = lchan->ms_power_ctrl.current; + + /* FIXME: update encryption */ + + LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.req (%s) ", + gsm_lchan_name(lchan), + get_value_string(femtobts_l1sapi_names, + conf_req->cfgParams.setLogChParams.sapi)); + LOGPC(DL1C, LOGL_INFO, "cfgParams Tn=%u, subCh=%u, dir=0x%x ", + conf_req->cfgParams.setLogChParams.u8Tn, + conf_req->cfgParams.setLogChParams.subCh, + conf_req->cfgParams.setLogChParams.dir); + dump_lch_par(LOGL_INFO, + &conf_req->cfgParams.setLogChParams.logChParams, + conf_req->cfgParams.setLogChParams.sapi); + + return l1if_gsm_req_compl(fl1h, msg, chmod_modif_compl_cb, NULL); +} + +static void enqueue_sapi_logchpar_cmd(struct gsm_lchan *lchan, int dir, GsmL1_Sapi_t sapi) +{ + struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + + cmd->dir = dir; + cmd->sapi = sapi; + cmd->type = SAPI_CMD_CONFIG_LOGCH_PARAM; + queue_sapi_command(lchan, cmd); +} + +static int tx_confreq_logchpar(struct gsm_lchan *lchan, uint8_t direction) +{ + enqueue_sapi_logchpar_cmd(lchan, direction, lchan_to_GsmL1_Sapi_t(lchan)); + return 0; +} + +int l1if_set_txpower(struct femtol1_hdl *fl1h, float tx_power) +{ + struct msgb *msg = l1p_msgb_alloc(); + GsmL1_MphConfigReq_t *conf_req; + + conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h, 0); + conf_req->cfgParamId = GsmL1_ConfigParamId_SetTxPowerLevel; + conf_req->cfgParams.setTxPowerLevel.fTxPowerLevel = tx_power; + + return l1if_gsm_req_compl(fl1h, msg, chmod_txpower_compl_cb, NULL); +} + +const enum GsmL1_CipherId_t rsl2l1_ciph[] = { + [0] = GsmL1_CipherId_A50, + [1] = GsmL1_CipherId_A50, + [2] = GsmL1_CipherId_A51, + [3] = GsmL1_CipherId_A52, + [4] = GsmL1_CipherId_A53, +}; + +static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx); + struct msgb *msg = l1p_msgb_alloc(); + struct GsmL1_MphConfigReq_t *cfgr; + + cfgr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h, + l1p_handle_for_lchan(lchan)); + + cfgr->cfgParamId = GsmL1_ConfigParamId_SetCipheringParams; + cfgr->cfgParams.setCipheringParams.u8Tn = lchan->ts->nr; + cfgr->cfgParams.setCipheringParams.subCh = lchan_to_GsmL1_SubCh_t(lchan); + cfgr->cfgParams.setCipheringParams.dir = cmd->dir; + cfgr->hLayer3 = l1if_lchan_to_hLayer(lchan); + + if (lchan->encr.alg_id >= ARRAY_SIZE(rsl2l1_ciph)) + return -EINVAL; + cfgr->cfgParams.setCipheringParams.cipherId = rsl2l1_ciph[lchan->encr.alg_id]; + + LOGP(DL1C, LOGL_NOTICE, "%s SET_CIPHERING (ALG=%u %s)\n", + gsm_lchan_name(lchan), + cfgr->cfgParams.setCipheringParams.cipherId, + get_value_string(femtobts_dir_names, + cfgr->cfgParams.setCipheringParams.dir)); + + memcpy(cfgr->cfgParams.setCipheringParams.u8Kc, + lchan->encr.key, lchan->encr.key_len); + + return l1if_gsm_req_compl(fl1h, msg, chmod_modif_compl_cb, NULL); +} + +static void enqueue_sapi_ciphering_cmd(struct gsm_lchan *lchan, int dir) +{ + struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + + cmd->dir = dir; + cmd->type = SAPI_CMD_CONFIG_CIPHERING; + queue_sapi_command(lchan, cmd); +} + +int l1if_set_ciphering(struct femtol1_hdl *fl1h, + struct gsm_lchan *lchan, + int dir_downlink) +{ + int dir; + + /* ignore the request when the channel is not active */ + if (lchan->state != LCHAN_S_ACTIVE) + return -1; + + if (dir_downlink) + dir = GsmL1_Dir_TxDownlink; + else + dir = GsmL1_Dir_RxUplink; + + enqueue_sapi_ciphering_cmd(lchan, dir); + + return 0; +} + +int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) +{ + if (lchan->state != LCHAN_S_ACTIVE) + return -1; + + enqueue_sapi_logchpar_cmd(lchan, GsmL1_Dir_RxUplink, GsmL1_Sapi_Sacch); + return 0; +} + +int l1if_rsl_mode_modify(struct gsm_lchan *lchan) +{ + if (lchan->state != LCHAN_S_ACTIVE) + return -1; + + /* channel mode, encryption and/or multirate have changed */ + + /* update multi-rate config */ + tx_confreq_logchpar(lchan, GsmL1_Dir_RxUplink); + tx_confreq_logchpar(lchan, GsmL1_Dir_TxDownlink); + + /* FIXME: update encryption */ + + return 0; +} + +static int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + enum lchan_sapi_state status; + struct sapi_cmd *cmd; + struct gsm_lchan *lchan; + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphDeactivateCnf_t *ic = &l1p->u.mphDeactivateCnf; + + lchan = l1if_hLayer_to_lchan(trx, ic->hLayer3); + if (!lchan) { + LOGP(DL1C, LOGL_ERROR, + "Failed to find lchan for hLayer3=0x%x\n", ic->hLayer3); + goto err; + } + + LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.conf (%s ", + gsm_lchan_name(lchan), + get_value_string(femtobts_l1sapi_names, ic->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(femtobts_dir_names, ic->dir)); + + if (ic->status == GsmL1_Status_Success) { + DEBUGP(DL1C, "Successful deactivation of L1 SAPI %s on TS %u\n", + get_value_string(femtobts_l1sapi_names, ic->sapi), ic->u8Tn); + status = LCHAN_SAPI_S_NONE; + } else { + LOGP(DL1C, LOGL_ERROR, "Error deactivating L1 SAPI %s on TS %u: %s\n", + get_value_string(femtobts_l1sapi_names, ic->sapi), ic->u8Tn, + get_value_string(femtobts_l1status_names, ic->status)); + status = LCHAN_SAPI_S_ERROR; + } + + if (ic->dir & GsmL1_Dir_TxDownlink) + lchan->sapis_dl[ic->sapi] = status; + if (ic->dir & GsmL1_Dir_RxUplink) + lchan->sapis_ul[ic->sapi] = status; + + + if (llist_empty(&lchan->sapi_cmds)) { + LOGP(DL1C, LOGL_ERROR, + "%s Got de-activation confirmation with empty queue\n", + gsm_lchan_name(lchan)); + goto err; + } + + cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry); + if (cmd->sapi != ic->sapi || cmd->dir != ic->dir || + cmd->type != SAPI_CMD_DEACTIVATE) { + LOGP(DL1C, LOGL_ERROR, + "%s Confirmation mismatch (%d, %d) (%d, %d)\n", + gsm_lchan_name(lchan), cmd->sapi, cmd->dir, + ic->sapi, ic->dir); + goto err; + } + + sapi_queue_dispatch(lchan, ic->status); + +err: + msgb_free(l1_msg); + return 0; +} + +static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx); + struct msgb *msg = l1p_msgb_alloc(); + GsmL1_MphDeactivateReq_t *deact_req; + + deact_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDeactivateReq, + fl1h, l1p_handle_for_lchan(lchan)); + deact_req->u8Tn = lchan->ts->nr; + deact_req->subCh = lchan_to_GsmL1_SubCh_t(lchan); + deact_req->dir = cmd->dir; + deact_req->sapi = cmd->sapi; + deact_req->hLayer3 = l1if_lchan_to_hLayer(lchan); + + LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.req (%s ", + gsm_lchan_name(lchan), + get_value_string(femtobts_l1sapi_names, deact_req->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(femtobts_dir_names, deact_req->dir)); + + /* send the primitive for all GsmL1_Sapi_* that match the LCHAN */ + return l1if_gsm_req_compl(fl1h, msg, lchan_deact_compl_cb, NULL); +} + +static int sapi_deactivate_cb(struct gsm_lchan *lchan, int status) +{ + /* FIXME: Error handling. There is no NACK... */ + if (status != GsmL1_Status_Success && lchan->state == LCHAN_S_REL_REQ) { + LOGP(DL1C, LOGL_ERROR, "%s is now broken. Stopping the release.\n", + gsm_lchan_name(lchan)); + lchan_set_state(lchan, LCHAN_S_BROKEN); + sapi_clear_queue(&lchan->sapi_cmds); + mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0); + return -1; + } + + if (!llist_empty(&lchan->sapi_cmds)) + return 0; + + /* Don't send an REL ACK on SACCH deactivate */ + if (lchan->state != LCHAN_S_REL_REQ) + return 0; + + lchan_set_state(lchan, LCHAN_S_NONE); + mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0); + + /* Reactivate CCCH due to SI3 update in RSL */ + if (lchan->rel_act_kind == LCHAN_REL_ACT_REACT) { + lchan->rel_act_kind = LCHAN_REL_ACT_RSL; + lchan_activate(lchan); + } + return 0; +} + +static int enqueue_sapi_deact_cmd(struct gsm_lchan *lchan, int sapi, int dir) +{ + struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + + cmd->sapi = sapi; + cmd->dir = dir; + cmd->type = SAPI_CMD_DEACTIVATE; + cmd->callback = sapi_deactivate_cb; + return queue_sapi_command(lchan, cmd); +} + +/* + * Release the SAPI if it was allocated. E.g. the SACCH might be already + * deactivated or during a hand-over the TCH was not allocated yet. + */ +static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir) +{ + /* check if we should schedule a release */ + if (dir & GsmL1_Dir_TxDownlink) { + if (lchan->sapis_dl[sapi] != LCHAN_SAPI_S_ASSIGNED) + return 0; + lchan->sapis_dl[sapi] = LCHAN_SAPI_S_REL; + } else if (dir & GsmL1_Dir_RxUplink) { + if (lchan->sapis_ul[sapi] != LCHAN_SAPI_S_ASSIGNED) + return 0; + lchan->sapis_ul[sapi] = LCHAN_SAPI_S_REL; + } + + /* now schedule the command and maybe dispatch it */ + return enqueue_sapi_deact_cmd(lchan, sapi, dir); +} + +static int release_sapis_for_ho(struct gsm_lchan *lchan) +{ + int res = 0; + int i; + + const struct lchan_sapis *s4l = &sapis_for_ho; + + for (i = s4l->num_sapis-1; i >= 0; i--) + res |= check_sapi_release(lchan, + s4l->sapis[i].sapi, s4l->sapis[i].dir); + return res; +} + +static int lchan_deactivate_sapis(struct gsm_lchan *lchan) +{ + struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx); + const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type]; + int i, res; + + res = 0; + + /* The order matters.. the Facch needs to be released first */ + for (i = s4l->num_sapis-1; i >= 0; i--) { + /* Stop the alive timer once we deactivate the SCH */ + if (s4l->sapis[i].sapi == GsmL1_Sapi_Sch) + osmo_timer_del(&fl1h->alive_timer); + + /* Release if it was allocated */ + res |= check_sapi_release(lchan, s4l->sapis[i].sapi, s4l->sapis[i].dir); + } + + /* always attempt to disable the RACH burst */ + res |= release_sapis_for_ho(lchan); + + /* nothing was queued */ + if (res == 0) { + LOGP(DL1C, LOGL_ERROR, "%s all SAPIs already released?\n", + gsm_lchan_name(lchan)); + lchan_set_state(lchan, LCHAN_S_BROKEN); + mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0); + } + + return res; +} + +static void enqueue_rel_marker(struct gsm_lchan *lchan) +{ + struct sapi_cmd *cmd; + + /* remember we need to release all active SAPIs */ + cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + cmd->type = SAPI_CMD_REL_MARKER; + queue_sapi_command(lchan, cmd); +} + +int bts_model_lchan_deactivate(struct gsm_lchan *lchan) +{ + lchan_set_state(lchan, LCHAN_S_REL_REQ); + enqueue_rel_marker(lchan); + return 0; +} + +static void enqueue_sacch_rel_marker(struct gsm_lchan *lchan) +{ + struct sapi_cmd *cmd; + + /* remember we need to check if the SACCH is allocated */ + cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + cmd->type = SAPI_CMD_SACCH_REL_MARKER; + queue_sapi_command(lchan, cmd); +} + +int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan) +{ + enqueue_sacch_rel_marker(lchan); + return 0; +} + +/* callback from OML */ +int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, + struct tlv_parsed *old_attr, struct tlv_parsed *new_attr, + void *obj) +{ + /* FIXME: more checks if the attributes are valid */ + + switch (msg_type) { + case NM_MT_SET_CHAN_ATTR: + /* our L1 only supports one global TSC for all channels + * one one TRX, so we need to make sure not to activate + * channels with a different TSC!! */ + if (TLVP_PRES_LEN(new_attr, NM_ATT_TSC, 1) && + *TLVP_VAL(new_attr, NM_ATT_TSC) != (bts->bsic & 7)) { + LOGP(DOML, LOGL_ERROR, "Channel TSC %u != BSIC-TSC %u\n", + *TLVP_VAL(new_attr, NM_ATT_TSC), bts->bsic & 7); + return -NM_NACK_PARAM_RANGE; + } + break; + } + return 0; +} + +/* callback from OML */ +int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, + struct tlv_parsed *new_attr, int kind, void *obj) +{ + if (kind == NM_OC_RADIO_CARRIER) { + struct gsm_bts_trx *trx = obj; + struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); + + /* Did we go through MphInit yet? If yes fire and forget */ + if (fl1h->hLayer1) + power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0); + } + + /* FIXME: we actaully need to send a ACK or NACK for the OML message */ + return oml_fom_ack_nack(msg, 0); +} + +/* callback from OML */ +int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, + void *obj) +{ + int rc; + + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + rc = trx_init(obj); + break; + case NM_OC_CHANNEL: + rc = ts_opstart(obj); + break; + case NM_OC_BTS: + case NM_OC_SITE_MANAGER: + case NM_OC_BASEB_TRANSC: + case NM_OC_GPRS_NSE: + case NM_OC_GPRS_CELL: + case NM_OC_GPRS_NSVC: + oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, -1); + rc = oml_mo_opstart_ack(mo); + if (mo->obj_class == NM_OC_BTS) { + oml_mo_state_chg(&bts->mo, -1, NM_AVSTATE_OK); + oml_mo_state_chg(&bts->gprs.nse.mo, -1, NM_AVSTATE_OK); + oml_mo_state_chg(&bts->gprs.cell.mo, -1, NM_AVSTATE_OK); + oml_mo_state_chg(&bts->gprs.nsvc[0].mo, -1, NM_AVSTATE_OK); + } + break; + default: + rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP); + } + return rc; +} + +int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo, + void *obj, uint8_t adm_state) +{ + int rc = -EINVAL; + int granted = 0; + + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + + if (mo->procedure_pending) { + LOGP(DL1C, LOGL_ERROR, "Discarding adm change command: " + "pending procedure on RC %d\n", + ((struct gsm_bts_trx *)obj)->nr); + return 0; + } + mo->procedure_pending = 1; + switch (adm_state) { + case NM_STATE_LOCKED: + rc = trx_rf_lock(obj, 1, NULL); + break; + case NM_STATE_UNLOCKED: + rc = trx_rf_lock(obj, 0, NULL); + break; + default: + granted = 1; + break; + } + + if (!granted && rc == 0) + /* in progress, will send ack/nack after completion */ + return 0; + + mo->procedure_pending = 0; + + break; + default: + /* blindly accept all state changes */ + granted = 1; + break; + } + + if (granted) { + mo->nm_state.administrative = adm_state; + return oml_mo_statechg_ack(mo); + } else + return oml_mo_statechg_nack(mo, NM_NACK_REQ_NOT_GRANT); + +} + +int l1if_rsl_chan_act(struct gsm_lchan *lchan) +{ + //uint8_t mode = *TLVP_VAL(tp, RSL_IE_CHAN_MODE); + //uint8_t type = *TLVP_VAL(tp, RSL_IE_ACT_TYPE); + lchan_activate(lchan); + return 0; +} + +/** + * Modify the given lchan in the handover scenario. This is a lot like + * second channel activation but with some additional activation. + */ +int l1if_rsl_chan_mod(struct gsm_lchan *lchan) +{ + const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type]; + unsigned int i; + + if (lchan->ho.active == HANDOVER_NONE) + return -1; + + LOGP(DHO, LOGL_ERROR, "%s modifying channel for handover\n", + gsm_lchan_name(lchan)); + + /* Give up listening to RACH bursts */ + release_sapis_for_ho(lchan); + + /* Activate the normal SAPIs */ + for (i = 0; i < s4l->num_sapis; i++) { + int sapi = s4l->sapis[i].sapi; + int dir = s4l->sapis[i].dir; + enqueue_sapi_act_cmd(lchan, sapi, dir); + } + + return 0; +} + +int l1if_rsl_chan_rel(struct gsm_lchan *lchan) +{ + /* A duplicate RF Release Request, ignore it */ + if (lchan->state == LCHAN_S_REL_REQ) { + LOGP(DL1C, LOGL_ERROR, "%s already in release request state.\n", + gsm_lchan_name(lchan)); + return 0; + } + + lchan_deactivate(lchan); + return 0; +} + +int l1if_rsl_deact_sacch(struct gsm_lchan *lchan) +{ + /* Only de-activate the SACCH if the lchan is active */ + if (lchan->state != LCHAN_S_ACTIVE) + return 0; + return bts_model_lchan_deactivate_sacch(lchan); +} + +int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) +{ + struct femtol1_hdl *fl1 = trx_femtol1_hdl(trx); + + return l1if_activate_rf(fl1, 0); +} + +int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm) +{ + return l1if_set_txpower(trx_femtol1_hdl(trx), ((float) p_trxout_mdBm)/1000.0); +} + +static int ts_disconnect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphDisconnectCnf_t *cnf = &l1p->u.mphDisconnectCnf; + struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn]; + OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS); + + LOGP(DL1C, LOGL_DEBUG, "%s Rx mphDisconnectCnf\n", + gsm_lchan_name(ts->lchan)); + + cb_ts_disconnected(ts); + + return 0; +} + +int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) +{ + struct msgb *msg = l1p_msgb_alloc(); + struct femtol1_hdl *fl1h = trx_femtol1_hdl(ts->trx); + GsmL1_MphDisconnectReq_t *cr; + + DEBUGP(DRSL, "%s TS disconnect\n", gsm_lchan_name(ts->lchan)); + cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDisconnectReq, fl1h, + l1p_handle_for_ts(ts)); + cr->u8Tn = ts->nr; + + return l1if_gsm_req_compl(fl1h, msg, ts_disconnect_cb, NULL); +} + +static int ts_connect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphConnectCnf_t *cnf = &l1p->u.mphConnectCnf; + struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn]; + OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS); + + DEBUGP(DL1C, "%s %s Rx mphConnectCnf flags=%s%s%s\n", + gsm_lchan_name(ts->lchan), + gsm_pchan_name(ts->pchan), + ts->flags & TS_F_PDCH_ACTIVE ? "ACTIVE " : "", + ts->flags & TS_F_PDCH_ACT_PENDING ? "ACT_PENDING " : "", + ts->flags & TS_F_PDCH_DEACT_PENDING ? "DEACT_PENDING " : ""); + + cb_ts_connected(ts); + + return 0; +} + +int bts_model_ts_connect(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config as_pchan) +{ + return ts_connect_as(ts, as_pchan, ts_connect_cb, NULL); +} diff --git a/src/osmo-bts-sysmo/oml_router.c b/src/osmo-bts-sysmo/oml_router.c new file mode 100644 index 0000000..f3d0837 --- /dev/null +++ b/src/osmo-bts-sysmo/oml_router.c @@ -0,0 +1,129 @@ +/* Beginnings of an OML router */ + +/* (C) 2014 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include "oml_router.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +static int oml_router_read_cb(struct osmo_fd *fd, unsigned int what) +{ + struct msgb *msg; + int rc; + + msg = oml_msgb_alloc(); + if (!msg) { + LOGP(DL1C, LOGL_ERROR, "Failed to allocate oml msgb.\n"); + return -1; + } + + rc = recv(fd->fd, msg->tail, msg->data_len, 0); + if (rc <= 0) { + close(fd->fd); + osmo_fd_unregister(fd); + fd->fd = -1; + goto err; + } + + msg->l1h = msgb_put(msg, rc); + rc = msg_verify_ipa_structure(msg); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, + "OML Router: Invalid IPA message rc(%d)\n", rc); + goto err; + } + + rc = msg_verify_oml_structure(msg); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, + "OML Router: Invalid OML message rc(%d)\n", rc); + goto err; + } + + /* todo dispatch message */ + +err: + msgb_free(msg); + return -1; +} + +static int oml_router_accept_cb(struct osmo_fd *accept_fd, unsigned int what) +{ + int fd; + struct osmo_fd *read_fd = (struct osmo_fd *) accept_fd->data; + + /* Accept only one connection at a time. De-register it */ + if (read_fd->fd > -1) { + LOGP(DL1C, LOGL_NOTICE, + "New OML router connection. Closing old one.\n"); + close(read_fd->fd); + osmo_fd_unregister(read_fd); + read_fd->fd = -1; + } + + fd = accept(accept_fd->fd, NULL, NULL); + if (fd < 0) { + LOGP(DL1C, LOGL_ERROR, "Failed to accept. errno: %s.\n", + strerror(errno)); + return -1; + } + + read_fd->fd = fd; + if (osmo_fd_register(read_fd) != 0) { + LOGP(DL1C, LOGL_ERROR, "Registering the read fd failed.\n"); + close(fd); + read_fd->fd = -1; + return -1; + } + + return 0; +} + +int oml_router_init(struct gsm_bts *bts, const char *path, + struct osmo_fd *accept_fd, struct osmo_fd *read_fd) +{ + int rc; + + memset(accept_fd, 0, sizeof(*accept_fd)); + memset(read_fd, 0, sizeof(*read_fd)); + + accept_fd->cb = oml_router_accept_cb; + accept_fd->data = read_fd; + + read_fd->cb = oml_router_read_cb; + read_fd->data = bts; + read_fd->when = BSC_FD_READ; + read_fd->fd = -1; + + rc = osmo_sock_unix_init_ofd(accept_fd, SOCK_SEQPACKET, 0, + path, + OSMO_SOCK_F_BIND | OSMO_SOCK_F_NONBLOCK); + return rc; +} diff --git a/src/osmo-bts-sysmo/oml_router.h b/src/osmo-bts-sysmo/oml_router.h new file mode 100644 index 0000000..55f0681 --- /dev/null +++ b/src/osmo-bts-sysmo/oml_router.h @@ -0,0 +1,13 @@ +#pragma once + +struct gsm_bts; +struct osmo_fd; + +/** + * The default path sysmobts will listen for incoming + * registrations for OML routing and sending. + */ +#define OML_ROUTER_PATH "/var/run/sysmobts_oml_router" + + +int oml_router_init(struct gsm_bts *bts, const char *path, struct osmo_fd *accept, struct osmo_fd *read); diff --git a/src/osmo-bts-sysmo/sysmobts_ctrl.c b/src/osmo-bts-sysmo/sysmobts_ctrl.c new file mode 100644 index 0000000..21df88e --- /dev/null +++ b/src/osmo-bts-sysmo/sysmobts_ctrl.c @@ -0,0 +1,274 @@ +/* Control Interface for sysmoBTS */ + +/* (C) 2014 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "femtobts.h" +#include "l1_if.h" + + +/* for control interface */ + +#ifndef HW_SYSMOBTS_V1 +CTRL_CMD_DEFINE(clock_info, "clock-info"); +static int ctrl_clkinfo_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + SuperFemto_Prim_t *sysp = msgb_sysprim(resp); + struct ctrl_cmd_def *cd = data; + struct ctrl_cmd *cmd = cd->cmd; + + LOGP(DL1C, LOGL_NOTICE, + "RfClockInfo iClkCor=%d/clkSrc=%s Err=%d/ErrRes=%d/clkSrc=%s\n", + sysp->u.rfClockInfoCnf.rfTrx.iClkCor, + get_value_string(femtobts_clksrc_names, + sysp->u.rfClockInfoCnf.rfTrx.clkSrc), + sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErr, + sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes, + get_value_string(femtobts_clksrc_names, + sysp->u.rfClockInfoCnf.rfTrxClkCal.clkSrc)); + + if (ctrl_cmd_def_is_zombie(cd)) { + msgb_free(resp); + return 0; + } + + cmd->reply = talloc_asprintf(cmd, "%d,%s,%d,%d,%s", + sysp->u.rfClockInfoCnf.rfTrx.iClkCor, + get_value_string(femtobts_clksrc_names, + sysp->u.rfClockInfoCnf.rfTrx.clkSrc), + sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErr, + sysp->u.rfClockInfoCnf.rfTrxClkCal.iClkErrRes, + get_value_string(femtobts_clksrc_names, + sysp->u.rfClockInfoCnf.rfTrxClkCal.clkSrc)); + + ctrl_cmd_def_send(cd); + + msgb_free(resp); + + return 0; +} +static int get_clock_info(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts_trx *trx = cmd->node; + struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); + struct msgb *msg = sysp_msgb_alloc(); + SuperFemto_Prim_t *sysp = msgb_sysprim(msg); + struct ctrl_cmd_def *cd; + + /* geneate a deferred control command */ + cd = ctrl_cmd_def_make(fl1h, cmd, NULL, 10); + + sysp->id = SuperFemto_PrimId_RfClockInfoReq; + sysp->u.rfClockInfoReq.u8RstClkCal = 0; + + return l1if_req_compl(fl1h, msg, ctrl_clkinfo_cb, cd); +} +static int ctrl_set_clkinfo_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + struct ctrl_cmd_def *cd = data; + struct ctrl_cmd *cmd = cd->cmd; + + if (ctrl_cmd_def_is_zombie(cd)) { + msgb_free(resp); + return 0; + } + + cmd->reply = "success"; + + ctrl_cmd_def_send(cd); + + msgb_free(resp); + + return 0; +} +static int clock_setup_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + msgb_free(resp); + return 0; +} + +static int set_clock_info(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts_trx *trx = cmd->node; + struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); + struct msgb *msg = sysp_msgb_alloc(); + SuperFemto_Prim_t *sysp = msgb_sysprim(msg); + struct ctrl_cmd_def *cd; + + /* geneate a deferred control command */ + cd = ctrl_cmd_def_make(fl1h, cmd, NULL, 10); + + /* Set GPS/PPS as reference */ + sysp->id = SuperFemto_PrimId_RfClockSetupReq; + sysp->u.rfClockSetupReq.rfTrx.iClkCor = get_clk_cal(fl1h); + sysp->u.rfClockSetupReq.rfTrx.clkSrc = fl1h->clk_src; + sysp->u.rfClockSetupReq.rfTrxClkCal.clkSrc = SuperFemto_ClkSrcId_GpsPps; + l1if_req_compl(fl1h, msg, clock_setup_cb, NULL); + + /* Reset the error counters */ + msg = sysp_msgb_alloc(); + sysp = msgb_sysprim(msg); + sysp->id = SuperFemto_PrimId_RfClockInfoReq; + sysp->u.rfClockInfoReq.u8RstClkCal = 1; + + l1if_req_compl(fl1h, msg, ctrl_set_clkinfo_cb, cd); + + return CTRL_CMD_HANDLED; +} + +static int verify_clock_info(struct ctrl_cmd *cmd, const char *value, void *data) +{ + return 0; +} + + +CTRL_CMD_DEFINE(clock_corr, "clock-correction"); +static int ctrl_get_clkcorr_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + SuperFemto_Prim_t *sysp = msgb_sysprim(resp); + struct ctrl_cmd_def *cd = data; + struct ctrl_cmd *cmd = cd->cmd; + + if (ctrl_cmd_def_is_zombie(cd)) { + msgb_free(resp); + return 0; + } + + cmd->reply = talloc_asprintf(cmd, "%d", + sysp->u.rfClockInfoCnf.rfTrx.iClkCor); + + ctrl_cmd_def_send(cd); + + msgb_free(resp); + + return 0; +} +static int get_clock_corr(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts_trx *trx = cmd->node; + struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); + struct msgb *msg = sysp_msgb_alloc(); + SuperFemto_Prim_t *sysp = msgb_sysprim(msg); + struct ctrl_cmd_def *cd; + + /* we could theoretically simply respond with a cached value, but I + * prefer to to ask the actual L1 about the currently used value to + * avoid any mistakes */ + + /* geneate a deferred control command */ + cd = ctrl_cmd_def_make(fl1h, cmd, NULL, 10); + + sysp->id = SuperFemto_PrimId_RfClockInfoReq; + sysp->u.rfClockInfoReq.u8RstClkCal = 0; + + l1if_req_compl(fl1h, msg, ctrl_get_clkcorr_cb, cd); + + return CTRL_CMD_HANDLED; +} +static int ctrl_set_clkcorr_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + SuperFemto_Prim_t *sysp = msgb_sysprim(resp); + struct ctrl_cmd_def *cd = data; + struct ctrl_cmd *cmd = cd->cmd; + + if (ctrl_cmd_def_is_zombie(cd)) { + msgb_free(resp); + return 0; + } + + if (sysp->u.rfClockSetupCnf.status != GsmL1_Status_Success) { + cmd->type = CTRL_CMD_ERROR; + cmd->reply = "Error setting new correction value."; + } else + cmd->reply = "success"; + + ctrl_cmd_def_send(cd); + + msgb_free(resp); + + return 0; +} +static int set_clock_corr(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts_trx *trx = cmd->node; + struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); + struct msgb *msg = sysp_msgb_alloc(); + SuperFemto_Prim_t *sysp = msgb_sysprim(msg); + struct ctrl_cmd_def *cd; + + fl1h->clk_cal = atoi(cmd->value); + + /* geneate a deferred control command */ + cd = ctrl_cmd_def_make(fl1h, cmd, NULL, 10); + + sysp->id = SuperFemto_PrimId_RfClockSetupReq; + sysp->u.rfClockSetupReq.rfTrx.iClkCor = fl1h->clk_cal; + sysp->u.rfClockSetupReq.rfTrx.clkSrc = fl1h->clk_src; + sysp->u.rfClockSetupReq.rfTrxClkCal.clkSrc = SuperFemto_ClkSrcId_GpsPps; + + l1if_req_compl(fl1h, msg, ctrl_set_clkcorr_cb, cd); + + return CTRL_CMD_HANDLED; +} + +static int verify_clock_corr(struct ctrl_cmd *cmd, const char *value, void *data) +{ + /* FIXME: check the range */ + return 0; +} +#endif /* HW_SYSMOBTS_V1 */ + +int bts_model_ctrl_cmds_install(struct gsm_bts *bts) +{ + int rc = 0; + +#ifndef HW_SYSMOBTS_V1 + rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_clock_info); + rc |= ctrl_cmd_install(CTRL_NODE_TRX, &cmd_clock_corr); +#endif /* HW_SYSMOBTS_V1 */ + + return rc; +} diff --git a/src/osmo-bts-sysmo/sysmobts_vty.c b/src/osmo-bts-sysmo/sysmobts_vty.c new file mode 100644 index 0000000..039236f --- /dev/null +++ b/src/osmo-bts-sysmo/sysmobts_vty.c @@ -0,0 +1,541 @@ +/* VTY interface for sysmoBTS */ + +/* (C) 2011 by Harald Welte + * (C) 2012,2013 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "femtobts.h" +#include "l1_if.h" +#include "utils.h" + +extern int lchan_activate(struct gsm_lchan *lchan); + +#define TRX_STR "Transceiver related commands\n" "TRX number\n" + +#define SHOW_TRX_STR \ + SHOW_STR \ + TRX_STR +#define DSP_TRACE_F_STR "DSP Trace Flag\n" + +static struct gsm_bts *vty_bts; + +/* configuration */ + +DEFUN(cfg_phy_clkcal_eeprom, cfg_phy_clkcal_eeprom_cmd, + "clock-calibration eeprom", + "Use the eeprom clock calibration value\n") +{ + struct phy_instance *pinst = vty->index; + + pinst->u.sysmobts.clk_use_eeprom = 1; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_clkcal_def, cfg_phy_clkcal_def_cmd, + "clock-calibration default", + "Set the clock calibration value\n" "Default Clock DAC value\n") +{ + struct phy_instance *pinst = vty->index; + + pinst->u.sysmobts.clk_use_eeprom = 0; + pinst->u.sysmobts.clk_cal = 0xffff; + + return CMD_SUCCESS; +} + +#ifdef HW_SYSMOBTS_V1 +DEFUN(cfg_phy_clkcal, cfg_phy_clkcal_cmd, + "clock-calibration <0-4095>", + "Set the clock calibration value\n" "Clock DAC value\n") +{ + unsigned int clkcal = atoi(argv[0]); + struct phy_instance *pinst = vty->index; + + pinst->u.sysmobts.clk_use_eeprom = 0; + pinst->u.sysmobts.clk_cal = clkcal & 0xfff; + + return CMD_SUCCESS; +} +#else +DEFUN(cfg_phy_clkcal, cfg_phy_clkcal_cmd, + "clock-calibration <-4095-4095>", + "Set the clock calibration value\n" "Offset in PPB\n") +{ + int clkcal = atoi(argv[0]); + struct phy_instance *pinst = vty->index; + + pinst->u.sysmobts.clk_use_eeprom = 0; + pinst->u.sysmobts.clk_cal = clkcal; + + return CMD_SUCCESS; +} +#endif + +DEFUN(cfg_phy_clksrc, cfg_phy_clksrc_cmd, + "clock-source (tcxo|ocxo|ext|gps)", + "Set the clock source value\n" + "Use the TCXO\n" + "Use the OCXO\n" + "Use an external clock\n" + "Use the GPS pps\n") +{ + struct phy_instance *pinst = vty->index; + int rc; + + rc = get_string_value(femtobts_clksrc_names, argv[0]); + if (rc < 0) + return CMD_WARNING; + + pinst->u.sysmobts.clk_src = rc; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_cal_path, cfg_phy_cal_path_cmd, + "trx-calibration-path PATH", + "Set the path name to TRX calibration data\n" "Path name\n") +{ + struct phy_instance *pinst = vty->index; + + if (pinst->u.sysmobts.calib_path) + talloc_free(pinst->u.sysmobts.calib_path); + + pinst->u.sysmobts.calib_path = talloc_strdup(pinst, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN_DEPRECATED(cfg_trx_ul_power_target, cfg_trx_ul_power_target_cmd, + "uplink-power-target <-110-0>", + "Obsolete alias for bts uplink-power-target\n" + "Target uplink Rx level in dBm\n") +{ + struct gsm_bts_trx *trx = vty->index; + + trx->bts->ul_power_target = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_nominal_power, cfg_trx_nominal_power_cmd, + "nominal-tx-power <0-100>", + "Set the nominal transmit output power in dBm\n" + "Nominal transmit output power level in dBm\n") +{ + struct gsm_bts_trx *trx = vty->index; + + trx->nominal_power = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_dsp_trace_f, cfg_phy_dsp_trace_f_cmd, + "HIDDEN", TRX_STR) +{ + struct phy_instance *pinst = vty->index; + unsigned int flag; + + flag = get_string_value(femtobts_tracef_names, argv[1]); + pinst->u.sysmobts.dsp_trace_f |= ~flag; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_no_dsp_trace_f, cfg_phy_no_dsp_trace_f_cmd, + "HIDDEN", NO_STR TRX_STR) +{ + struct phy_instance *pinst = vty->index; + unsigned int flag; + + flag = get_string_value(femtobts_tracef_names, argv[1]); + pinst->u.sysmobts.dsp_trace_f &= ~flag; + + return CMD_SUCCESS; +} + +/* runtime */ + +DEFUN(show_phy_clksrc, show_trx_clksrc_cmd, + "show phy <0-255> clock-source", + SHOW_TRX_STR "Display the clock source for this TRX") +{ + int phy_nr = atoi(argv[0]); + struct phy_instance *pinst = vty_get_phy_instance(vty, phy_nr, 0); + + if (!pinst) + return CMD_WARNING; + + vty_out(vty, "PHY Clock Source: %s%s", + get_value_string(femtobts_clksrc_names, + pinst->u.sysmobts.clk_src), VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(show_dsp_trace_f, show_dsp_trace_f_cmd, + "show trx <0-0> dsp-trace-flags", + SHOW_TRX_STR "Display the current setting of the DSP trace flags") +{ + int trx_nr = atoi(argv[0]); + struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct femtol1_hdl *fl1h; + int i; + + if (!trx) + return CMD_WARNING; + + fl1h = trx_femtol1_hdl(trx); + + vty_out(vty, "Femto L1 DSP trace flags:%s", VTY_NEWLINE); + for (i = 0; i < ARRAY_SIZE(femtobts_tracef_names); i++) { + const char *endis; + + if (femtobts_tracef_names[i].value == 0 && + femtobts_tracef_names[i].str == NULL) + break; + + if (fl1h->dsp_trace_f & femtobts_tracef_names[i].value) + endis = "enabled"; + else + endis = "disabled"; + + vty_out(vty, "DSP Trace %-15s %s%s", + femtobts_tracef_names[i].str, endis, + VTY_NEWLINE); + } + + return CMD_SUCCESS; + +} + +DEFUN(dsp_trace_f, dsp_trace_f_cmd, "HIDDEN", TRX_STR) +{ + int phy_nr = atoi(argv[0]); + struct phy_instance *pinst; + struct femtol1_hdl *fl1h; + unsigned int flag ; + + pinst = vty_get_phy_instance(vty, phy_nr, 0); + if (!pinst) + return CMD_WARNING; + + fl1h = pinst->u.sysmobts.hdl; + flag = get_string_value(femtobts_tracef_names, argv[1]); + l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f | flag); + + return CMD_SUCCESS; +} + +DEFUN(no_dsp_trace_f, no_dsp_trace_f_cmd, "HIDDEN", NO_STR TRX_STR) +{ + int phy_nr = atoi(argv[0]); + struct phy_instance *pinst; + struct femtol1_hdl *fl1h; + unsigned int flag ; + + pinst = vty_get_phy_instance(vty, phy_nr, 0); + if (!pinst) + return CMD_WARNING; + + fl1h = pinst->u.sysmobts.hdl; + flag = get_string_value(femtobts_tracef_names, argv[1]); + l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f & ~flag); + + return CMD_SUCCESS; +} + +DEFUN(show_sys_info, show_sys_info_cmd, + "show phy <0-255> instance <0-255> system-information", + SHOW_TRX_STR "Display information about system\n") +{ + int phy_nr = atoi(argv[0]); + int inst_nr = atoi(argv[1]); + struct phy_link *plink = phy_link_by_num(phy_nr); + struct phy_instance *pinst; + struct femtol1_hdl *fl1h; + int i; + + if (!plink) { + vty_out(vty, "Cannot find PHY link %u%s", + phy_nr, VTY_NEWLINE); + return CMD_WARNING; + } + pinst = phy_instance_by_num(plink, inst_nr); + if (!pinst) { + vty_out(vty, "Cannot find PHY instance %u%s", + phy_nr, VTY_NEWLINE); + return CMD_WARNING; + } + fl1h = pinst->u.sysmobts.hdl; + + vty_out(vty, "DSP Version: %u.%u.%u, FPGA Version: %u.%u.%u%s", + fl1h->hw_info.dsp_version[0], + fl1h->hw_info.dsp_version[1], + fl1h->hw_info.dsp_version[2], + fl1h->hw_info.fpga_version[0], + fl1h->hw_info.fpga_version[1], + fl1h->hw_info.fpga_version[2], VTY_NEWLINE); + + vty_out(vty, "GSM Band Support: "); + for (i = 0; i < sizeof(fl1h->hw_info.band_support); i++) { + if (fl1h->hw_info.band_support & (1 << i)) + vty_out(vty, "%s ", gsm_band_name(1 << i)); + } + vty_out(vty, "%s", VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(activate_lchan, activate_lchan_cmd, + "trx <0-0> <0-7> (activate|deactivate) <0-7>", + TRX_STR + "Timeslot number\n" + "Activate Logical Channel\n" + "Deactivate Logical Channel\n" + "Logical Channel Number\n" ) +{ + int trx_nr = atoi(argv[0]); + int ts_nr = atoi(argv[1]); + int lchan_nr = atoi(argv[3]); + struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; + struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; + + if (!strcmp(argv[2], "activate")) + lchan_activate(lchan); + else + lchan_deactivate(lchan); + + return CMD_SUCCESS; +} + +DEFUN(set_tx_power, set_tx_power_cmd, + "trx <0-0> tx-power <-110-100>", + TRX_STR + "Set transmit power (override BSC)\n" + "Transmit power in dBm\n") +{ + int trx_nr = atoi(argv[0]); + int power = atoi(argv[1]); + struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + + power_ramp_start(trx, to_mdB(power), 1); + + return CMD_SUCCESS; +} + +DEFUN(reset_rf_clock_ctr, reset_rf_clock_ctr_cmd, + "trx <0-0> rf-clock-info reset", + TRX_STR + "RF Clock Information\n" "Reset the counter\n") +{ + int trx_nr = atoi(argv[0]); + struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); + + l1if_rf_clock_info_reset(fl1h); + return CMD_SUCCESS; +} + +DEFUN(correct_rf_clock_ctr, correct_rf_clock_ctr_cmd, + "trx <0-0> rf-clock-info correct", + TRX_STR + "RF Clock Information\n" "Apply\n") +{ + int trx_nr = atoi(argv[0]); + struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); + + l1if_rf_clock_info_correct(fl1h); + return CMD_SUCCESS; +} + +DEFUN(loopback, loopback_cmd, + "trx <0-0> <0-7> loopback <0-1>", + TRX_STR + "Timeslot number\n" + "Set TCH loopback\n" + "Logical Channel Number\n") +{ + int trx_nr = atoi(argv[0]); + int ts_nr = atoi(argv[1]); + int lchan_nr = atoi(argv[2]); + struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; + struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; + + lchan->loopback = 1; + + return CMD_SUCCESS; +} + +DEFUN(no_loopback, no_loopback_cmd, + "no trx <0-0> <0-7> loopback <0-1>", + NO_STR TRX_STR + "Timeslot number\n" + "Set TCH loopback\n" + "Logical Channel Number\n") +{ + int trx_nr = atoi(argv[0]); + int ts_nr = atoi(argv[1]); + int lchan_nr = atoi(argv[2]); + struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; + struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; + + lchan->loopback = 0; + + return CMD_SUCCESS; +} + + +void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts) +{ +} + +void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx) +{ + if (trx->nominal_power != get_p_max_out_mdBm(trx)) + vty_out(vty, " nominal-tx-power %d%s", trx->nominal_power, + VTY_NEWLINE); +} + +void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink) +{ +} + +void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst) +{ + int i; + + for (i = 0; i < 32; i++) { + if (pinst->u.sysmobts.dsp_trace_f & (1 << i)) { + const char *name; + name = get_value_string(femtobts_tracef_names, (1 << i)); + vty_out(vty, " dsp-trace-flag %s%s", name, + VTY_NEWLINE); + } + } + + if (pinst->u.sysmobts.clk_use_eeprom) + vty_out(vty, " clock-calibration eeprom%s", VTY_NEWLINE); + else + vty_out(vty, " clock-calibration %d%s", + pinst->u.sysmobts.clk_cal, VTY_NEWLINE); + if (pinst->u.sysmobts.calib_path) + vty_out(vty, " trx-calibration-path %s%s", + pinst->u.sysmobts.calib_path, VTY_NEWLINE); + if (pinst->u.sysmobts.clk_src) + vty_out(vty, " clock-source %s%s", + get_value_string(femtobts_clksrc_names, + pinst->u.sysmobts.clk_src), VTY_NEWLINE); +} + +int bts_model_vty_init(struct gsm_bts *bts) +{ + vty_bts = bts; + + /* runtime-patch the command strings with debug levels */ + dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, femtobts_tracef_names, + "trx <0-0> dsp-trace-flag (", + "|",")", VTY_DO_LOWER); + dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, femtobts_tracef_docs, + TRX_STR DSP_TRACE_F_STR, + "\n", "", 0); + + no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, femtobts_tracef_names, + "no trx <0-0> dsp-trace-flag (", + "|",")", VTY_DO_LOWER); + no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, femtobts_tracef_docs, + NO_STR TRX_STR DSP_TRACE_F_STR, + "\n", "", 0); + + cfg_phy_dsp_trace_f_cmd.string = + vty_cmd_string_from_valstr(bts, femtobts_tracef_names, + "dsp-trace-flag (", "|", ")", + VTY_DO_LOWER); + cfg_phy_dsp_trace_f_cmd.doc = + vty_cmd_string_from_valstr(bts, femtobts_tracef_docs, + DSP_TRACE_F_STR, "\n", "", 0); + + cfg_phy_no_dsp_trace_f_cmd.string = + vty_cmd_string_from_valstr(bts, femtobts_tracef_names, + "no dsp-trace-flag (", "|", ")", + VTY_DO_LOWER); + cfg_phy_no_dsp_trace_f_cmd.doc = + vty_cmd_string_from_valstr(bts, femtobts_tracef_docs, + NO_STR DSP_TRACE_F_STR, "\n", + "", 0); + + install_element_ve(&show_dsp_trace_f_cmd); + install_element_ve(&show_sys_info_cmd); + install_element_ve(&show_trx_clksrc_cmd); + install_element_ve(&dsp_trace_f_cmd); + install_element_ve(&no_dsp_trace_f_cmd); + + install_element(ENABLE_NODE, &activate_lchan_cmd); + install_element(ENABLE_NODE, &set_tx_power_cmd); + install_element(ENABLE_NODE, &reset_rf_clock_ctr_cmd); + install_element(ENABLE_NODE, &correct_rf_clock_ctr_cmd); + + install_element(ENABLE_NODE, &loopback_cmd); + install_element(ENABLE_NODE, &no_loopback_cmd); + + install_element(BTS_NODE, &cfg_bts_auto_band_cmd); + install_element(BTS_NODE, &cfg_bts_no_auto_band_cmd); + + install_element(TRX_NODE, &cfg_trx_ul_power_target_cmd); + install_element(TRX_NODE, &cfg_trx_nominal_power_cmd); + + install_element(PHY_INST_NODE, &cfg_phy_dsp_trace_f_cmd); + install_element(PHY_INST_NODE, &cfg_phy_no_dsp_trace_f_cmd); + install_element(PHY_INST_NODE, &cfg_phy_clkcal_cmd); + install_element(PHY_INST_NODE, &cfg_phy_clkcal_eeprom_cmd); + install_element(PHY_INST_NODE, &cfg_phy_clkcal_def_cmd); + install_element(PHY_INST_NODE, &cfg_phy_clksrc_cmd); + install_element(PHY_INST_NODE, &cfg_phy_cal_path_cmd); + + return 0; +} diff --git a/src/osmo-bts-sysmo/tch.c b/src/osmo-bts-sysmo/tch.c new file mode 100644 index 0000000..4e6e246 --- /dev/null +++ b/src/osmo-bts-sysmo/tch.c @@ -0,0 +1,685 @@ +/* Traffic channel support for Sysmocom BTS L1 */ + +/* (C) 2011-2012 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "femtobts.h" +#include "l1_if.h" + +static struct msgb *l1_to_rtppayload_fr(uint8_t *l1_payload, uint8_t payload_len, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + +#ifdef USE_L1_RTP_MODE + /* new L1 can deliver bits like we need them */ + cur = msgb_put(msg, GSM_FR_BYTES); + memcpy(cur, l1_payload, GSM_FR_BYTES); +#else + /* step1: reverse the bit-order of each payload byte */ + osmo_revbytebits_buf(l1_payload, payload_len); + + cur = msgb_put(msg, GSM_FR_BYTES); + + /* step2: we need to shift the entire L1 payload by 4 bits right */ + osmo_nibble_shift_right(cur, l1_payload, GSM_FR_BITS/4); + + cur[0] |= 0xD0; +#endif /* USE_L1_RTP_MODE */ + + lchan_set_marker(osmo_fr_check_sid(l1_payload, payload_len), lchan); + + return msg; +} + +/*! \brief convert GSM-FR from RTP payload to L1 format + * \param[out] l1_payload payload part of L1 buffer + * \param[in] rtp_payload pointer to RTP payload data + * \param[in] payload_len length of \a rtp_payload + * \returns number of \a l1_payload bytes filled + */ +static int rtppayload_to_l1_fr(uint8_t *l1_payload, const uint8_t *rtp_payload, + unsigned int payload_len) +{ +#ifdef USE_L1_RTP_MODE + /* new L1 can deliver bits like we need them */ + memcpy(l1_payload, rtp_payload, GSM_FR_BYTES); +#else + /* step2: we need to shift the RTP payload left by one nibble*/ + osmo_nibble_shift_left_unal(l1_payload, rtp_payload, GSM_FR_BITS/4); + + /* step1: reverse the bit-order of each payload byte */ + osmo_revbytebits_buf(l1_payload, payload_len); +#endif /* USE_L1_RTP_MODE */ + return GSM_FR_BYTES; +} + +#if defined(L1_HAS_EFR) && defined(USE_L1_RTP_MODE) +static struct msgb *l1_to_rtppayload_efr(uint8_t *l1_payload, + uint8_t payload_len, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + +#ifdef USE_L1_RTP_MODE + /* new L1 can deliver bits like we need them */ + cur = msgb_put(msg, GSM_EFR_BYTES); + memcpy(cur, l1_payload, GSM_EFR_BYTES); +#else + /* step1: reverse the bit-order of each payload byte */ + osmo_revbytebits_buf(l1_payload, payload_len); + + cur = msgb_put(msg, GSM_EFR_BYTES); + + /* step 2: we need to shift the entire L1 payload by 4 bits right */ + osmo_nibble_shift_right(cur, l1_payload, GSM_EFR_BITS/4); + + cur[0] |= 0xC0; +#endif /* USE_L1_RTP_MODE */ + enum osmo_amr_type ft; + enum osmo_amr_quality bfi; + uint8_t cmr; + int8_t sti, cmi; + osmo_amr_rtp_dec(l1_payload, payload_len, &cmr, &cmi, &ft, &bfi, &sti); + lchan_set_marker(ft == AMR_GSM_EFR_SID, lchan); + + return msg; +} + +static int rtppayload_to_l1_efr(uint8_t *l1_payload, const uint8_t *rtp_payload, + unsigned int payload_len) +{ +#ifndef USE_L1_RTP_MODE +#error We don't support EFR with L1 that doesn't support RTP mode! +#else + memcpy(l1_payload, rtp_payload, payload_len); + + return payload_len; +#endif +} +#else +#warning No EFR support in L1 +#endif /* L1_HAS_EFR */ + +#ifdef USE_L1_RTP_MODE +/* change the bit-order of each unaligned field inside the HR codec + * payload from little-endian bit-ordering to bit-endian and vice-versa. + * This is required on all sysmoBTS DSP versions < 5.3.3 in order to + * be compliant with ETSI TS 101 318 Chapter 5.2 */ +static void hr_jumble(uint8_t *dst, const uint8_t *src) +{ + /* Table 2 / Section 5.2.1 of ETSI TS 101 381 */ + const int p_unvoiced[] = + { 5, 11, 9, 8, 1, 2, 7, 7, 5, 7, 7, 5, 7, 7, 5, 7, 7, 5 }; + /* Table 3 / Section 5.2.1 of ETSI TS 101 381 */ + const int p_voiced[] = + { 5, 11, 9, 8, 1, 2, 8, 9, 5, 4, 9, 5, 4, 9, 5, 4, 9, 5 }; + + int base, i, j, l, si, di; + const int *p; + + memset(dst, 0x00, GSM_HR_BYTES); + + p = (src[4] & 0x30) ? p_voiced : p_unvoiced; + + base = 0; + for (i = 0; i < 18; i++) { + l = p[i]; + for (j = 0; j < l; j++) { + si = base + j; + di = base + l - j - 1; + + if (src[si >> 3] & (1 << (7 - (si & 7)))) + dst[di >> 3] |= (1 << (7 - (di & 7))); + } + + base += l; + } +} +#endif /* USE_L1_RTP_MODE */ + +static struct msgb *l1_to_rtppayload_hr(uint8_t *l1_payload, uint8_t payload_len, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + + if (payload_len != GSM_HR_BYTES) { + LOGP(DL1P, LOGL_ERROR, "L1 HR frame length %u != expected %u\n", + payload_len, GSM_HR_BYTES); + return NULL; + } + + cur = msgb_put(msg, GSM_HR_BYTES); +#ifdef USE_L1_RTP_MODE + struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx); + if (fl1h->rtp_hr_jumble_needed) + hr_jumble(cur, l1_payload); + else + memcpy(cur, l1_payload, GSM_HR_BYTES); +#else /* USE_L1_RTP_MODE */ + memcpy(cur, l1_payload, GSM_HR_BYTES); + /* reverse the bit-order of each payload byte */ + osmo_revbytebits_buf(cur, GSM_HR_BYTES); +#endif /* USE_L1_RTP_MODE */ + + lchan_set_marker(osmo_hr_check_sid(l1_payload, payload_len), lchan); + + return msg; +} + +/*! \brief convert GSM-FR from RTP payload to L1 format + * \param[out] l1_payload payload part of L1 buffer + * \param[in] rtp_payload pointer to RTP payload data + * \param[in] payload_len length of \a rtp_payload + * \returns number of \a l1_payload bytes filled + */ +static int rtppayload_to_l1_hr(uint8_t *l1_payload, const uint8_t *rtp_payload, + unsigned int payload_len, struct gsm_lchan *lchan) +{ + + if (payload_len != GSM_HR_BYTES) { + LOGP(DL1P, LOGL_ERROR, "RTP HR frame length %u != expected %u\n", + payload_len, GSM_HR_BYTES); + return 0; + } + +#ifdef USE_L1_RTP_MODE + struct femtol1_hdl *fl1h = trx_femtol1_hdl(lchan->ts->trx); + if (fl1h->rtp_hr_jumble_needed) + hr_jumble(l1_payload, rtp_payload); + else + memcpy(l1_payload, rtp_payload, GSM_HR_BYTES); +#else /* USE_L1_RTP_MODE */ + memcpy(l1_payload, rtp_payload, GSM_HR_BYTES); + /* reverse the bit-order of each payload byte */ + osmo_revbytebits_buf(l1_payload, GSM_HR_BYTES); +#endif /* USE_L1_RTP_MODE */ + + return GSM_HR_BYTES; +} + +static struct msgb *l1_to_rtppayload_amr(uint8_t *l1_payload, uint8_t payload_len, + struct gsm_lchan *lchan) +{ +#ifndef USE_L1_RTP_MODE + struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr; +#endif + struct msgb *msg; + uint8_t amr_if2_len = payload_len - 2; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + +#ifdef USE_L1_RTP_MODE + cur = msgb_put(msg, amr_if2_len); + memcpy(cur, l1_payload+2, amr_if2_len); + + /* + * Audiocode's MGW doesn't like receiving CMRs that are not + * the same as the previous one. This means we need to patch + * the content here. + */ + if ((cur[0] & 0xF0) == 0xF0) + cur[0]= lchan->tch.last_cmr << 4; + else + lchan->tch.last_cmr = cur[0] >> 4; +#else + u_int8_t cmr; + uint8_t ft = l1_payload[2] & 0xF; + uint8_t cmr_idx = l1_payload[1]; + /* CMR == Unset means CMR was not transmitted at this TDMA */ + if (cmr_idx == GsmL1_AmrCodecMode_Unset) + cmr = lchan->tch.last_cmr; + else if (cmr_idx >= amr_mrc->num_modes || + cmr_idx > GsmL1_AmrCodecMode_Unset) { + /* Make sure the CMR of the phone is in the active codec set */ + LOGP(DL1P, LOGL_NOTICE, "L1->RTP: overriding CMR IDX %u\n", cmr_idx); + cmr = AMR_CMR_NONE; + } else { + cmr = amr_mrc->bts_mode[cmr_idx].mode; + lchan->tch.last_cmr = cmr; + } + + /* RFC 3267 4.4.1 Payload Header */ + msgb_put_u8(msg, (cmr << 4)); + + /* RFC 3267 AMR TOC */ + msgb_put_u8(msg, AMR_TOC_QBIT | (ft << 3)); + + cur = msgb_put(msg, amr_if2_len-1); + + /* step1: reverse the bit-order within every byte */ + osmo_revbytebits_buf(l1_payload+2, amr_if2_len); + + /* step2: shift everything left by one nibble */ + osmo_nibble_shift_left_unal(cur, l1_payload+2, amr_if2_len*2 -1); + +#endif /* USE_L1_RTP_MODE */ + + return msg; +} + +/*! \brief convert AMR from RTP payload to L1 format + * \param[out] l1_payload payload part of L1 buffer + * \param[in] rtp_payload pointer to RTP payload data + * \param[in] payload_len length of \a rtp_payload + * \returns number of \a l1_payload bytes filled + */ +static int rtppayload_to_l1_amr(uint8_t *l1_payload, const uint8_t *rtp_payload, + uint8_t payload_len, uint8_t ft) +{ +#ifdef USE_L1_RTP_MODE + memcpy(l1_payload, rtp_payload, payload_len); +#else + uint8_t amr_if2_core_len = payload_len - 2; + + /* step1: shift everything right one nibble; make space for FT */ + osmo_nibble_shift_right(l1_payload+2, rtp_payload+2, amr_if2_core_len*2); + /* step2: reverse the bit-order within every byte of the IF2 + * core frame contained in the RTP payload */ + osmo_revbytebits_buf(l1_payload+2, amr_if2_core_len+1); + + /* lower 4 bit of first FR2 byte contains FT */ + l1_payload[2] |= ft; +#endif /* USE_L1_RTP_MODE */ + return payload_len; +} + +#define RTP_MSGB_ALLOC_SIZE 512 + +/*! \brief function for incoming RTP via TCH.req + * \param[in] rtp_pl buffer containing RTP payload + * \param[in] rtp_pl_len length of \a rtp_pl + * \param[in] use_cache Use cached payload instead of parsing RTP + * \param[in] marker RTP header Marker bit (indicates speech onset) + * \returns 0 if encoding result can be sent further to L1 without extra actions + * positive value if data is ready AND extra actions are required + * negative value otherwise (no data for L1 encoded) + * + * This function prepares a msgb with a L1 PH-DATA.req primitive and + * queues it into lchan->dl_tch_queue. + * + * Note that the actual L1 primitive header is not fully initialized + * yet, as things like the frame number, etc. are unknown at the time we + * pre-fill the primtive. + */ +int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len, + const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn, + bool use_cache, bool marker) +{ + uint8_t *payload_type; + uint8_t *l1_payload, ft; + int rc = 0; + bool is_sid = false; + + DEBUGP(DRTP, "%s RTP IN: %s\n", gsm_lchan_name(lchan), + osmo_hexdump(rtp_pl, rtp_pl_len)); + + payload_type = &data[0]; + l1_payload = &data[1]; + + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + if (lchan->type == GSM_LCHAN_TCH_F) { + *payload_type = GsmL1_TchPlType_Fr; + rc = rtppayload_to_l1_fr(l1_payload, + rtp_pl, rtp_pl_len); + if (rc && lchan->ts->trx->bts->dtxd) + is_sid = osmo_fr_check_sid(rtp_pl, rtp_pl_len); + } else{ + *payload_type = GsmL1_TchPlType_Hr; + rc = rtppayload_to_l1_hr(l1_payload, + rtp_pl, rtp_pl_len, lchan); + if (rc && lchan->ts->trx->bts->dtxd) + is_sid = osmo_hr_check_sid(rtp_pl, rtp_pl_len); + } + if (is_sid) + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, -1); + break; +#if defined(L1_HAS_EFR) && defined(USE_L1_RTP_MODE) + case GSM48_CMODE_SPEECH_EFR: + *payload_type = GsmL1_TchPlType_Efr; + rc = rtppayload_to_l1_efr(l1_payload, rtp_pl, + rtp_pl_len); + /* FIXME: detect and save EFR SID */ + break; +#endif + case GSM48_CMODE_SPEECH_AMR: + if (use_cache) { + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload, lchan->tch.dtx.cache, + lchan->tch.dtx.len, ft); + *len = lchan->tch.dtx.len + 1; + return 0; + } + + rc = dtx_dl_amr_fsm_step(lchan, rtp_pl, rtp_pl_len, fn, + l1_payload, marker, len, &ft); + if (rc < 0) + return rc; + if (!dtx_dl_amr_enabled(lchan)) { + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + } + + /* DTX DL-specific logic below: */ + switch (lchan->tch.dtx.dl_amr_fsm->state) { + case ST_ONSET_V: + *payload_type = GsmL1_TchPlType_Amr_Onset; + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0); + *len = 3; + return 1; + case ST_VOICE: + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + case ST_SID_F1: + if (lchan->type == GSM_LCHAN_TCH_H) { /* AMR HR */ + *payload_type = GsmL1_TchPlType_Amr_SidFirstP1; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, + rtp_pl_len, ft); + return 0; + } + /* AMR FR */ + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + case ST_SID_F2: + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + case ST_F1_INH_V: + *payload_type = GsmL1_TchPlType_Amr_SidFirstInH; + *len = 3; + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0); + return 1; + case ST_U_INH_V: + *payload_type = GsmL1_TchPlType_Amr_SidUpdateInH; + *len = 3; + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0); + return 1; + case ST_SID_U: + case ST_U_NOINH: + return -EAGAIN; + case ST_FACCH: + return -EBADMSG; + default: + LOGP(DRTP, LOGL_ERROR, "Unhandled DTX DL AMR FSM state " + "%d\n", lchan->tch.dtx.dl_amr_fsm->state); + return -EINVAL; + } + break; + default: + /* we don't support CSD modes */ + rc = -1; + break; + } + + if (rc < 0) { + LOGP(DRTP, LOGL_ERROR, "%s unable to parse RTP payload\n", + gsm_lchan_name(lchan)); + return -EBADMSG; + } + + *len = rc + 1; + + DEBUGP(DRTP, "%s RTP->L1: %s\n", gsm_lchan_name(lchan), + osmo_hexdump(data, *len)); + return 0; +} + +static int is_recv_only(uint8_t speech_mode) +{ + return (speech_mode & 0xF0) == (1 << 4); +} + +/*! \brief receive a traffic L1 primitive for a given lchan */ +int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1p_msg); + GsmL1_PhDataInd_t *data_ind = &l1p->u.phDataInd; + uint8_t *payload, payload_type, payload_len, sid_first[9] = { 0 }; + struct msgb *rmsg = NULL; + struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)]; + + if (is_recv_only(lchan->abis_ip.speech_mode)) + return -EAGAIN; + + if (data_ind->msgUnitParam.u8Size < 1) { + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "chan_nr %d Rx Payload size 0\n", chan_nr); + /* Push empty payload to upper layers */ + rmsg = msgb_alloc_headroom(256, 128, "L1P-to-RTP"); + return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn, + data_ind->measParam.fBer * 10000, + data_ind->measParam.fLinkQuality * 10); + } + + payload_type = data_ind->msgUnitParam.u8Buffer[0]; + payload = data_ind->msgUnitParam.u8Buffer + 1; + payload_len = data_ind->msgUnitParam.u8Size - 1; + + switch (payload_type) { + case GsmL1_TchPlType_Fr: +#if defined(L1_HAS_EFR) && defined(USE_L1_RTP_MODE) + case GsmL1_TchPlType_Efr: +#endif + if (lchan->type != GSM_LCHAN_TCH_F) + goto err_payload_match; + break; + case GsmL1_TchPlType_Hr: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + break; + case GsmL1_TchPlType_Amr: + if (lchan->type != GSM_LCHAN_TCH_H && + lchan->type != GSM_LCHAN_TCH_F) + goto err_payload_match; + break; + case GsmL1_TchPlType_Amr_Onset: + if (lchan->type != GSM_LCHAN_TCH_H && + lchan->type != GSM_LCHAN_TCH_F) + goto err_payload_match; + /* according to 3GPP TS 26.093 ONSET frames precede the first + speech frame of a speech burst - set the marker for next RTP + frame */ + lchan->rtp_tx_marker = true; + break; + case GsmL1_TchPlType_Amr_SidFirstP1: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P1 from L1 " + "(%d bytes)\n", payload_len); + break; + case GsmL1_TchPlType_Amr_SidFirstP2: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P2 from L1 " + "(%d bytes)\n", payload_len); + break; + case GsmL1_TchPlType_Amr_SidFirstInH: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + lchan->rtp_tx_marker = true; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_INH from L1 " + "(%d bytes)\n", payload_len); + break; + case GsmL1_TchPlType_Amr_SidUpdateInH: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + lchan->rtp_tx_marker = true; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_UPDATE_INH from L1 " + "(%d bytes)\n", payload_len); + break; + default: + LOGPFN(DL1P, LOGL_NOTICE, data_ind->u32Fn, "%s Rx Payload Type %s is unsupported\n", + gsm_lchan_name(lchan), + get_value_string(femtobts_tch_pl_names, payload_type)); + break; + } + + + switch (payload_type) { + case GsmL1_TchPlType_Fr: + rmsg = l1_to_rtppayload_fr(payload, payload_len, lchan); + break; + case GsmL1_TchPlType_Hr: + rmsg = l1_to_rtppayload_hr(payload, payload_len, lchan); + break; +#if defined(L1_HAS_EFR) && defined(USE_L1_RTP_MODE) + case GsmL1_TchPlType_Efr: + rmsg = l1_to_rtppayload_efr(payload, payload_len, lchan); + break; +#endif + case GsmL1_TchPlType_Amr: + rmsg = l1_to_rtppayload_amr(payload, payload_len, lchan); + break; + case GsmL1_TchPlType_Amr_SidFirstP1: + memcpy(sid_first, payload, payload_len); + int len = osmo_amr_rtp_enc(sid_first, 0, AMR_SID, AMR_GOOD); + if (len < 0) + return 0; + rmsg = l1_to_rtppayload_amr(sid_first, len, lchan); + break; + /* FIXME: what about GsmL1_TchPlType_Amr_SidBad? not well documented. */ + } + + if (rmsg) + return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn, + data_ind->measParam.fBer * 10000, + data_ind->measParam.fLinkQuality * 10); + + return 0; + +err_payload_match: + LOGPFN(DL1P, LOGL_ERROR, data_ind->u32Fn, "%s Rx Payload Type %s incompatible with lchan\n", + gsm_lchan_name(lchan), + get_value_string(femtobts_tch_pl_names, payload_type)); + return -EINVAL; +} + +struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan, uint32_t fn) +{ + struct msgb *msg; + GsmL1_Prim_t *l1p; + GsmL1_PhDataReq_t *data_req; + GsmL1_MsgUnitParam_t *msu_param; + uint8_t *payload_type; + uint8_t *l1_payload; + int rc; + + msg = l1p_msgb_alloc(); + if (!msg) + return NULL; + + l1p = msgb_l1prim(msg); + data_req = &l1p->u.phDataReq; + msu_param = &data_req->msgUnitParam; + payload_type = &msu_param->u8Buffer[0]; + l1_payload = &msu_param->u8Buffer[1]; + + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_AMR: + if (lchan->type == GSM_LCHAN_TCH_H && + dtx_dl_amr_enabled(lchan)) { + /* we have to explicitly handle sending SID FIRST P2 for + AMR HR in here */ + *payload_type = GsmL1_TchPlType_Amr_SidFirstP2; + rc = dtx_dl_amr_fsm_step(lchan, NULL, 0, fn, l1_payload, + false, &(msu_param->u8Size), + NULL); + if (rc == 0) + return msg; + } + *payload_type = GsmL1_TchPlType_Amr; + break; + case GSM48_CMODE_SPEECH_V1: + if (lchan->type == GSM_LCHAN_TCH_F) + *payload_type = GsmL1_TchPlType_Fr; + else + *payload_type = GsmL1_TchPlType_Hr; + break; + case GSM48_CMODE_SPEECH_EFR: + *payload_type = GsmL1_TchPlType_Efr; + break; + default: + msgb_free(msg); + return NULL; + } + + rc = repeat_last_sid(lchan, l1_payload, fn); + if (!rc) { + msgb_free(msg); + return NULL; + } + msu_param->u8Size = rc; + + return msg; +} diff --git a/src/osmo-bts-sysmo/utils.c b/src/osmo-bts-sysmo/utils.c new file mode 100644 index 0000000..7d1aca7 --- /dev/null +++ b/src/osmo-bts-sysmo/utils.c @@ -0,0 +1,113 @@ +/* + * Helper utilities that are used in OML + * + * (C) 2011-2013 by Harald Welte + * (C) 2013 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include "utils.h" + +#include +#include +#include + +#include "femtobts.h" +#include "l1_if.h" + +int band_femto2osmo(GsmL1_FreqBand_t band) +{ + switch (band) { + case GsmL1_FreqBand_850: + return GSM_BAND_850; + case GsmL1_FreqBand_900: + return GSM_BAND_900; + case GsmL1_FreqBand_1800: + return GSM_BAND_1800; + case GsmL1_FreqBand_1900: + return GSM_BAND_1900; + default: + return -1; + } +} + +static int band_osmo2femto(struct gsm_bts_trx *trx, enum gsm_band osmo_band) +{ + struct femtol1_hdl *fl1h = trx_femtol1_hdl(trx); + + /* check if the TRX hardware actually supports the given band */ + if (!(fl1h->hw_info.band_support & osmo_band)) + return -1; + + /* if yes, convert from osmcoom style band definition to L1 band */ + switch (osmo_band) { + case GSM_BAND_850: + return GsmL1_FreqBand_850; + case GSM_BAND_900: + return GsmL1_FreqBand_900; + case GSM_BAND_1800: + return GsmL1_FreqBand_1800; + case GSM_BAND_1900: + return GsmL1_FreqBand_1900; + default: + return -1; + } +} + +/** + * Select the band that matches the ARFCN. In general the ARFCNs + * for GSM1800 and GSM1900 overlap and one needs to specify the + * rightband. When moving between GSM900/GSM1800 and GSM850/1900 + * modifying the BTS configuration is a bit annoying. The auto-band + * configuration allows to ease with this transition. + */ +int sysmobts_select_femto_band(struct gsm_bts_trx *trx, uint16_t arfcn) +{ + enum gsm_band band; + struct gsm_bts *bts = trx->bts; + + if (!bts->auto_band) + return band_osmo2femto(trx, bts->band); + + /* + * We need to check what will happen now. + */ + band = gsm_arfcn2band(arfcn); + + /* if we are already on the right band return */ + if (band == bts->band) + return band_osmo2femto(trx, bts->band); + + /* Check if it is GSM1800/GSM1900 */ + if (band == GSM_BAND_1800 && bts->band == GSM_BAND_1900) + return band_osmo2femto(trx, bts->band); + + /* + * Now to the actual autobauding. We just want DCS/DCS and + * PCS/PCS for PCS we check for 850/1800 though + */ + if ((band == GSM_BAND_900 && bts->band == GSM_BAND_1800) + || (band == GSM_BAND_1800 && bts->band == GSM_BAND_900) + || (band == GSM_BAND_850 && bts->band == GSM_BAND_1900)) + return band_osmo2femto(trx, band); + if (band == GSM_BAND_1800 && bts->band == GSM_BAND_850) + return band_osmo2femto(trx, GSM_BAND_1900); + + /* give up */ + return -1; +} diff --git a/src/osmo-bts-sysmo/utils.h b/src/osmo-bts-sysmo/utils.h new file mode 100644 index 0000000..45908d5 --- /dev/null +++ b/src/osmo-bts-sysmo/utils.h @@ -0,0 +1,12 @@ +#ifndef SYSMOBTS_UTILS_H +#define SYSMOBTS_UTILS_H + +#include +#include "femtobts.h" + +struct gsm_bts_trx; + +int band_femto2osmo(GsmL1_FreqBand_t band); + +int sysmobts_select_femto_band(struct gsm_bts_trx *trx, uint16_t arfcn); +#endif diff --git a/src/osmo-bts-trx/Makefile.am b/src/osmo-bts-trx/Makefile.am new file mode 100644 index 0000000..c241232 --- /dev/null +++ b/src/osmo-bts-trx/Makefile.am @@ -0,0 +1,11 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOCODING_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(ORTP_CFLAGS) +LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOCODING_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) $(ORTP_LIBS) -ldl + +EXTRA_DIST = trx_if.h l1_if.h loops.h + +bin_PROGRAMS = osmo-bts-trx + +osmo_bts_trx_SOURCES = main.c trx_if.c l1_if.c scheduler_trx.c trx_vty.c loops.c +osmo_bts_trx_LDADD = $(top_builddir)/src/common/libl1sched.a $(top_builddir)/src/common/libbts.a $(LDADD) + diff --git a/src/osmo-bts-trx/l1_if.c b/src/osmo-bts-trx/l1_if.c new file mode 100644 index 0000000..a8fb401 --- /dev/null +++ b/src/osmo-bts-trx/l1_if.c @@ -0,0 +1,758 @@ +/* + * layer 1 primitive handling and interface + * + * Copyright (C) 2013 Andreas Eversberg + * Copyright (C) 2015 Alexander Chemeris + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "l1_if.h" +#include "trx_if.h" + + +static const uint8_t transceiver_chan_types[_GSM_PCHAN_MAX] = { + [GSM_PCHAN_NONE] = 8, + [GSM_PCHAN_CCCH] = 4, + [GSM_PCHAN_CCCH_SDCCH4] = 5, + [GSM_PCHAN_TCH_F] = 1, + [GSM_PCHAN_TCH_H] = 3, + [GSM_PCHAN_SDCCH8_SACCH8C] = 7, + [GSM_PCHAN_PDCH] = 13, + /* [GSM_PCHAN_TCH_F_PDCH] not needed here, see trx_set_ts_as_pchan() */ + [GSM_PCHAN_UNKNOWN] = 0, +}; + + +static void check_transceiver_availability_trx(struct trx_l1h *l1h, int avail) +{ + struct phy_instance *pinst = l1h->phy_inst; + struct gsm_bts_trx *trx = pinst->trx; + uint8_t tn; + + /* HACK, we should change state when we receive first clock from + * transceiver */ + if (avail) { + /* signal availability */ + oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK); + oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK); + if (!pinst->u.osmotrx.sw_act_reported) { + oml_mo_tx_sw_act_rep(&trx->mo); + oml_mo_tx_sw_act_rep(&trx->bb_transc.mo); + pinst->u.osmotrx.sw_act_reported = true; + } + + for (tn = 0; tn < TRX_NR_TS; tn++) + oml_mo_state_chg(&trx->ts[tn].mo, NM_OPSTATE_DISABLED, + (l1h->config.slotmask & (1 << tn)) ? + NM_AVSTATE_DEPENDENCY : + NM_AVSTATE_NOT_INSTALLED); + } else { + oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, + NM_AVSTATE_OFF_LINE); + oml_mo_state_chg(&trx->bb_transc.mo, NM_OPSTATE_DISABLED, + NM_AVSTATE_OFF_LINE); + + for (tn = 0; tn < TRX_NR_TS; tn++) + oml_mo_state_chg(&trx->ts[tn].mo, NM_OPSTATE_DISABLED, + NM_AVSTATE_OFF_LINE); + } +} + +int check_transceiver_availability(struct gsm_bts *bts, int avail) +{ + struct gsm_bts_trx *trx; + + llist_for_each_entry(trx, &bts->trx_list, list) { + struct phy_instance *pinst = trx_phy_instance(trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + check_transceiver_availability_trx(l1h, avail); + } + return 0; +} + +int bts_model_lchan_deactivate(struct gsm_lchan *lchan) +{ + struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + if (lchan->rel_act_kind == LCHAN_REL_ACT_REACT) { + lchan->rel_act_kind = LCHAN_REL_ACT_RSL; + /* FIXME: perform whatever is needed (if any) to set proper PCH/AGCH allocation according to + 3GPP TS 44.018 Table 10.5.2.11.1 using num_agch(lchan->ts->trx, "TRX L1"); function */ + return 0; + } + /* set lchan inactive */ + lchan_set_state(lchan, LCHAN_S_NONE); + + return trx_sched_set_lchan(&l1h->l1s, gsm_lchan2chan_nr(lchan), + LID_DEDIC, 0); +} + +int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan) +{ + struct phy_instance *pinst = trx_phy_instance(lchan->ts->trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + return trx_sched_set_lchan(&l1h->l1s, gsm_lchan2chan_nr(lchan), + LID_SACCH, 0); +} + +/* + * transceiver provisioning + */ +int l1if_provision_transceiver_trx(struct trx_l1h *l1h) +{ + uint8_t tn; + + if (!transceiver_available) + return -EIO; + + if (l1h->config.poweron + && l1h->config.tsc_valid + && l1h->config.bsic_valid + && l1h->config.arfcn_valid) { + /* before power on */ + if (!l1h->config.arfcn_sent) { + trx_if_cmd_rxtune(l1h, l1h->config.arfcn); + trx_if_cmd_txtune(l1h, l1h->config.arfcn); + l1h->config.arfcn_sent = 1; + } + if (!l1h->config.tsc_sent) { + trx_if_cmd_settsc(l1h, l1h->config.tsc); + l1h->config.tsc_sent = 1; + } + if (!l1h->config.bsic_sent) { + trx_if_cmd_setbsic(l1h, l1h->config.bsic); + l1h->config.bsic_sent = 1; + } + + if (!l1h->config.poweron_sent) { + trx_if_cmd_poweron(l1h); + l1h->config.poweron_sent = 1; + } + + /* after power on */ + if (l1h->config.rxgain_valid && !l1h->config.rxgain_sent) { + trx_if_cmd_setrxgain(l1h, l1h->config.rxgain); + l1h->config.rxgain_sent = 1; + } + if (l1h->config.power_valid && !l1h->config.power_sent) { + trx_if_cmd_setpower(l1h, l1h->config.power); + l1h->config.power_sent = 1; + } + if (l1h->config.maxdly_valid && !l1h->config.maxdly_sent) { + trx_if_cmd_setmaxdly(l1h, l1h->config.maxdly); + l1h->config.maxdly_sent = 1; + } + if (l1h->config.maxdlynb_valid && !l1h->config.maxdlynb_sent) { + trx_if_cmd_setmaxdlynb(l1h, l1h->config.maxdlynb); + l1h->config.maxdlynb_sent = 1; + } + + for (tn = 0; tn < TRX_NR_TS; tn++) { + if (l1h->config.slottype_valid[tn] + && !l1h->config.slottype_sent[tn]) { + trx_if_cmd_setslot(l1h, tn, + l1h->config.slottype[tn]); + l1h->config.slottype_sent[tn] = 1; + } + } + return 0; + } + + if (!l1h->config.poweron && !l1h->config.poweron_sent) { + trx_if_cmd_poweroff(l1h); + l1h->config.poweron_sent = 1; + l1h->config.rxgain_sent = 0; + l1h->config.power_sent = 0; + l1h->config.maxdly_sent = 0; + l1h->config.maxdlynb_sent = 0; + for (tn = 0; tn < TRX_NR_TS; tn++) + l1h->config.slottype_sent[tn] = 0; + } + + return 0; +} + +int l1if_provision_transceiver(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + uint8_t tn; + + llist_for_each_entry(trx, &bts->trx_list, list) { + struct phy_instance *pinst = trx_phy_instance(trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + l1h->config.arfcn_sent = 0; + l1h->config.tsc_sent = 0; + l1h->config.bsic_sent = 0; + l1h->config.poweron_sent = 0; + l1h->config.rxgain_sent = 0; + l1h->config.power_sent = 0; + l1h->config.maxdly_sent = 0; + l1h->config.maxdlynb_sent = 0; + for (tn = 0; tn < TRX_NR_TS; tn++) + l1h->config.slottype_sent[tn] = 0; + l1if_provision_transceiver_trx(l1h); + } + return 0; +} + +/* + * activation/configuration/deactivation of transceiver's TRX + */ + +/* initialize the layer1 */ +static int trx_init(struct gsm_bts_trx *trx) +{ + struct phy_instance *pinst = trx_phy_instance(trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + /* power on transceiver, if not already */ + if (!l1h->config.poweron) { + l1h->config.poweron = 1; + l1h->config.poweron_sent = 0; + l1if_provision_transceiver_trx(l1h); + } + + if (trx == trx->bts->c0) + lchan_init_lapdm(&trx->ts[0].lchan[CCCH_LCHAN]); + + /* Set to Operational State: Enabled */ + oml_mo_state_chg(&trx->mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); + + /* Send OPSTART ack */ + return oml_mo_opstart_ack(&trx->mo); +} + +/* deactivate transceiver */ +int bts_model_trx_close(struct gsm_bts_trx *trx) +{ + struct phy_instance *pinst = trx_phy_instance(trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + enum gsm_phys_chan_config pchan = trx->ts[0].pchan; + + /* close all logical channels and reset timeslots */ + trx_sched_reset(&l1h->l1s); + + /* deactivate lchan for CCCH */ + if (pchan == GSM_PCHAN_CCCH || pchan == GSM_PCHAN_CCCH_SDCCH4) { + lchan_set_state(&trx->ts[0].lchan[CCCH_LCHAN], LCHAN_S_INACTIVE); + } + + /* power off transceiver, if not already */ + if (l1h->config.poweron) { + l1h->config.poweron = 0; + l1h->config.poweron_sent = 0; + l1if_provision_transceiver_trx(l1h); + } + + /* Set to Operational State: Disabled */ + check_transceiver_availability_trx(l1h, 0); + + return 0; +} + +/* on RSL failure, deactivate transceiver */ +void bts_model_abis_close(struct gsm_bts *bts) +{ + bts_shutdown(bts, "Abis close"); +} + +int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) +{ + /* we always implement the power control loop in osmo-bts software, as + * there is no automatism in the underlying osmo-trx */ + return 0; +} + +/* set bts attributes */ +static uint8_t trx_set_bts(struct gsm_bts *bts, struct tlv_parsed *new_attr) +{ + struct gsm_bts_trx *trx; + uint8_t bsic = bts->bsic; + + llist_for_each_entry(trx, &bts->trx_list, list) { + struct phy_instance *pinst = trx_phy_instance(trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + if (l1h->config.bsic != bsic || !l1h->config.bsic_valid) { + l1h->config.bsic = bsic; + l1h->config.bsic_valid = 1; + l1h->config.bsic_sent = 0; + l1if_provision_transceiver_trx(l1h); + } + } + check_transceiver_availability(bts, transceiver_available); + + + return 0; +} + +/* set trx attributes */ +static uint8_t trx_set_trx(struct gsm_bts_trx *trx) +{ + struct phy_instance *pinst = trx_phy_instance(trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + uint16_t arfcn = trx->arfcn; + + if (l1h->config.arfcn != arfcn || !l1h->config.arfcn_valid) { + l1h->config.arfcn = arfcn; + l1h->config.arfcn_valid = 1; + l1h->config.arfcn_sent = 0; + l1if_provision_transceiver_trx(l1h); + } + + if (l1h->config.power_oml) { + l1h->config.power = trx->max_power_red; + l1h->config.power_valid = 1; + l1h->config.power_sent = 0; + l1if_provision_transceiver_trx(l1h); + } + + return 0; +} + +/* set ts attributes */ +static uint8_t trx_set_ts_as_pchan(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config pchan) +{ + struct phy_instance *pinst = trx_phy_instance(ts->trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + uint8_t tn = ts->nr; + uint16_t tsc = ts->tsc; + uint8_t slottype; + int rc; + + /* all TSC of all timeslots must be equal, because transceiver only + * supports one TSC per TRX */ + + if (l1h->config.tsc != tsc || !l1h->config.tsc_valid) { + l1h->config.tsc = tsc; + l1h->config.tsc_valid = 1; + l1h->config.tsc_sent = 0; + l1if_provision_transceiver_trx(l1h); + } + + /* ignore disabled slots */ + if (!(l1h->config.slotmask & (1 << tn))) + return NM_NACK_RES_NOTAVAIL; + + /* set physical channel. For dynamic timeslots, the caller should have + * decided on a more specific PCHAN type already. */ + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH); + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH); + rc = trx_sched_set_pchan(&l1h->l1s, tn, pchan); + if (rc) + return NM_NACK_RES_NOTAVAIL; + + /* activate lchan for CCCH */ + if (pchan == GSM_PCHAN_CCCH || pchan == GSM_PCHAN_CCCH_SDCCH4) { + ts->lchan[CCCH_LCHAN].rel_act_kind = LCHAN_REL_ACT_OML; + lchan_set_state(&ts->lchan[CCCH_LCHAN], LCHAN_S_ACTIVE); + } + + slottype = transceiver_chan_types[pchan]; + + if (l1h->config.slottype[tn] != slottype + || !l1h->config.slottype_valid[tn]) { + l1h->config.slottype[tn] = slottype; + l1h->config.slottype_valid[tn] = 1; + l1h->config.slottype_sent[tn] = 0; + l1if_provision_transceiver_trx(l1h); + } + + return 0; +} + +static uint8_t trx_set_ts(struct gsm_bts_trx_ts *ts) +{ + enum gsm_phys_chan_config pchan; + + /* For dynamic timeslots, pick the pchan type that should currently be + * active. This should only be called during init, PDCH transitions + * will call trx_set_ts_as_pchan() directly. */ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_PDCH: + OSMO_ASSERT((ts->flags & TS_F_PDCH_PENDING_MASK) == 0); + pchan = (ts->flags & TS_F_PDCH_ACTIVE)? GSM_PCHAN_PDCH + : GSM_PCHAN_TCH_F; + break; + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + OSMO_ASSERT(ts->dyn.pchan_is == ts->dyn.pchan_want); + pchan = ts->dyn.pchan_is; + break; + default: + pchan = ts->pchan; + break; + } + + return trx_set_ts_as_pchan(ts, pchan); +} + + +/* + * primitive handling + */ + +/* enable ciphering */ +static int l1if_set_ciphering(struct trx_l1h *l1h, struct gsm_lchan *lchan, + uint8_t chan_nr, int downlink) +{ + /* ciphering already enabled in both directions */ + if (lchan->ciph_state == LCHAN_CIPH_RXTX_CONF) + return -EINVAL; + + if (!downlink) { + /* set uplink */ + trx_sched_set_cipher(&l1h->l1s, chan_nr, 0, lchan->encr.alg_id - 1, + lchan->encr.key, lchan->encr.key_len); + lchan->ciph_state = LCHAN_CIPH_RX_CONF; + } else { + /* set downlink and also set uplink, if not already */ + if (lchan->ciph_state != LCHAN_CIPH_RX_CONF) { + trx_sched_set_cipher(&l1h->l1s, chan_nr, 0, + lchan->encr.alg_id - 1, lchan->encr.key, + lchan->encr.key_len); + } + trx_sched_set_cipher(&l1h->l1s, chan_nr, 1, lchan->encr.alg_id - 1, + lchan->encr.key, lchan->encr.key_len); + lchan->ciph_state = LCHAN_CIPH_RXTX_CONF; + } + + return 0; +} + +static int mph_info_chan_confirm(struct trx_l1h *l1h, uint8_t chan_nr, + enum osmo_mph_info_type type, uint8_t cause) +{ + struct phy_instance *pinst = l1h->phy_inst; + struct osmo_phsap_prim l1sap; + + memset(&l1sap, 0, sizeof(l1sap)); + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_CONFIRM, + NULL); + l1sap.u.info.type = type; + l1sap.u.info.u.act_cnf.chan_nr = chan_nr; + l1sap.u.info.u.act_cnf.cause = cause; + + return l1sap_up(pinst->trx, &l1sap); +} + +int l1if_mph_time_ind(struct gsm_bts *bts, uint32_t fn) +{ + struct osmo_phsap_prim l1sap; + + memset(&l1sap, 0, sizeof(l1sap)); + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, + PRIM_OP_INDICATION, NULL); + l1sap.u.info.type = PRIM_INFO_TIME; + l1sap.u.info.u.time_ind.fn = fn; + + if (!bts->c0) + return -EINVAL; + + return l1sap_up(bts->c0, &l1sap); +} + + +static void l1if_fill_meas_res(struct osmo_phsap_prim *l1sap, uint8_t chan_nr, int16_t toa256, + float ber, float rssi, uint32_t fn) +{ + memset(l1sap, 0, sizeof(*l1sap)); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_MPH_INFO, + PRIM_OP_INDICATION, NULL); + l1sap->u.info.type = PRIM_INFO_MEAS; + l1sap->u.info.u.meas_ind.chan_nr = chan_nr; + l1sap->u.info.u.meas_ind.ta_offs_256bits = toa256; + l1sap->u.info.u.meas_ind.ber10k = (unsigned int) (ber * 10000); + l1sap->u.info.u.meas_ind.inv_rssi = (uint8_t) (rssi * -1); + l1sap->u.info.u.meas_ind.fn = fn; +} + +int l1if_process_meas_res(struct gsm_bts_trx *trx, uint8_t tn, uint32_t fn, uint8_t chan_nr, + int n_errors, int n_bits_total, float rssi, int16_t toa256) +{ + struct gsm_lchan *lchan = &trx->ts[tn].lchan[l1sap_chan2ss(chan_nr)]; + struct osmo_phsap_prim l1sap; + /* 100% BER is n_bits_total is 0 */ + float ber = n_bits_total==0 ? 1.0 : (float)n_errors / (float)n_bits_total; + + LOGPFN(DMEAS, LOGL_DEBUG, fn, "RX L1 frame %s fn=%u chan_nr=0x%02x MS pwr=%ddBm rssi=%.1f dBFS " + "ber=%.2f%% (%d/%d bits) L1_ta=%d rqd_ta=%d toa256=%d\n", + gsm_lchan_name(lchan), fn, chan_nr, ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power_ctrl.current), + rssi, ber*100, n_errors, n_bits_total, lchan->meas.l1_info[1], lchan->rqd_ta, toa256); + + l1if_fill_meas_res(&l1sap, chan_nr, toa256, ber, rssi, fn); + + return l1sap_up(trx, &l1sap); +} + + +/* primitive from common part */ +int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) +{ + struct phy_instance *pinst = trx_phy_instance(trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + struct msgb *msg = l1sap->oph.msg; + uint8_t chan_nr; + int rc = 0; + struct gsm_lchan *lchan; + + switch (OSMO_PRIM_HDR(&l1sap->oph)) { + case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST): + if (!msg) + break; + /* put data into scheduler's queue */ + return trx_sched_ph_data_req(&l1h->l1s, l1sap); + case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST): + if (!msg) + break; + /* put data into scheduler's queue */ + return trx_sched_tch_req(&l1h->l1s, l1sap); + case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST): + switch (l1sap->u.info.type) { + case PRIM_INFO_ACT_CIPH: + chan_nr = l1sap->u.info.u.ciph_req.chan_nr; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + if (l1sap->u.info.u.ciph_req.uplink) + l1if_set_ciphering(l1h, lchan, chan_nr, 0); + if (l1sap->u.info.u.ciph_req.downlink) + l1if_set_ciphering(l1h, lchan, chan_nr, 1); + break; + case PRIM_INFO_ACTIVATE: + case PRIM_INFO_DEACTIVATE: + case PRIM_INFO_MODIFY: + chan_nr = l1sap->u.info.u.act_req.chan_nr; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + if (l1sap->u.info.type == PRIM_INFO_ACTIVATE) { + if ((chan_nr & 0xE0) == 0x80) { + LOGP(DL1C, LOGL_ERROR, "Cannot activate" + " chan_nr 0x%02x\n", chan_nr); + break; + } + /* activate dedicated channel */ + trx_sched_set_lchan(&l1h->l1s, chan_nr, LID_DEDIC, 1); + /* activate associated channel */ + trx_sched_set_lchan(&l1h->l1s, chan_nr, LID_SACCH, 1); + /* set mode */ + trx_sched_set_mode(&l1h->l1s, chan_nr, + lchan->rsl_cmode, lchan->tch_mode, + lchan->tch.amr_mr.num_modes, + lchan->tch.amr_mr.bts_mode[0].mode, + lchan->tch.amr_mr.bts_mode[1].mode, + lchan->tch.amr_mr.bts_mode[2].mode, + lchan->tch.amr_mr.bts_mode[3].mode, + amr_get_initial_mode(lchan), + (lchan->ho.active == 1)); + /* init lapdm */ + lchan_init_lapdm(lchan); + /* set lchan active */ + lchan_set_state(lchan, LCHAN_S_ACTIVE); + /* set initial ciphering */ + l1if_set_ciphering(l1h, lchan, chan_nr, 0); + l1if_set_ciphering(l1h, lchan, chan_nr, 1); + if (lchan->encr.alg_id) + lchan->ciph_state = LCHAN_CIPH_RXTX_CONF; + else + lchan->ciph_state = LCHAN_CIPH_NONE; + + /* confirm */ + mph_info_chan_confirm(l1h, chan_nr, + PRIM_INFO_ACTIVATE, 0); + break; + } + if (l1sap->u.info.type == PRIM_INFO_MODIFY) { + /* change mode */ + trx_sched_set_mode(&l1h->l1s, chan_nr, + lchan->rsl_cmode, lchan->tch_mode, + lchan->tch.amr_mr.num_modes, + lchan->tch.amr_mr.bts_mode[0].mode, + lchan->tch.amr_mr.bts_mode[1].mode, + lchan->tch.amr_mr.bts_mode[2].mode, + lchan->tch.amr_mr.bts_mode[3].mode, + amr_get_initial_mode(lchan), + 0); + break; + } + /* here, type == PRIM_INFO_DEACTIVATE */ + if ((chan_nr & 0xE0) == 0x80) { + LOGP(DL1C, LOGL_ERROR, "Cannot deactivate " + "chan_nr 0x%02x\n", chan_nr); + break; + } + /* deactivate associated channel */ + bts_model_lchan_deactivate_sacch(lchan); + if (!l1sap->u.info.u.act_req.sacch_only) { + /* deactivate dedicated channel */ + lchan_deactivate(lchan); + /* confirm only on dedicated channel */ + mph_info_chan_confirm(l1h, chan_nr, + PRIM_INFO_DEACTIVATE, 0); + } + break; + default: + LOGP(DL1C, LOGL_NOTICE, "unknown MPH-INFO.req %d\n", + l1sap->u.info.type); + rc = -EINVAL; + goto done; + } + break; + default: + LOGP(DL1C, LOGL_NOTICE, "unknown prim %d op %d\n", + l1sap->oph.primitive, l1sap->oph.operation); + rc = -EINVAL; + goto done; + } + +done: + if (msg) + msgb_free(msg); + return rc; +} + + +/* + * oml handling + */ + +/* callback from OML */ +int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, + struct tlv_parsed *old_attr, struct tlv_parsed *new_attr, + void *obj) +{ + /* FIXME: check if the attributes are valid */ + return 0; +} + +/* callback from OML */ +int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, + struct tlv_parsed *new_attr, int kind, void *obj) +{ + struct abis_om_fom_hdr *foh = msgb_l3(msg); + int cause = 0; + + switch (foh->msg_type) { + case NM_MT_SET_BTS_ATTR: + cause = trx_set_bts(obj, new_attr); + break; + case NM_MT_SET_RADIO_ATTR: + cause = trx_set_trx(obj); + break; + case NM_MT_SET_CHAN_ATTR: + cause = trx_set_ts(obj); + break; + } + + return oml_fom_ack_nack(msg, cause); +} + +/* callback from OML */ +int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, + void *obj) +{ + int rc; + LOGP(DOML, LOGL_DEBUG, "bts_model_opstart: %s received\n", + get_value_string(abis_nm_obj_class_names, mo->obj_class)); + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + /* activate transceiver */ + rc = trx_init(obj); + break; + case NM_OC_CHANNEL: + case NM_OC_BTS: + case NM_OC_SITE_MANAGER: + case NM_OC_BASEB_TRANSC: + case NM_OC_GPRS_NSE: + case NM_OC_GPRS_CELL: + case NM_OC_GPRS_NSVC: + oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); + rc = oml_mo_opstart_ack(mo); + break; + default: + rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP); + } + return rc; +} + +int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo, + void *obj, uint8_t adm_state) +{ + /* blindly accept all state changes */ + mo->nm_state.administrative = adm_state; + return oml_mo_statechg_ack(mo); +} + +int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) +{ + return 0; +} + +int bts_model_oml_estab(struct gsm_bts *bts) +{ + return 0; +} + +int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm) +{ +#warning "implement bts_model_change_power\n" + LOGP(DL1C, LOGL_NOTICE, "Setting TRX output power not supported!\n"); + return 0; +} + +int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) +{ + /* no action required, signal completion right away. */ + cb_ts_disconnected(ts); + return 0; +} + +int bts_model_ts_connect(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config as_pchan) +{ + int rc; + LOGP(DL1C, LOGL_DEBUG, "%s bts_model_ts_connect(as_pchan=%s)\n", + gsm_ts_name(ts), gsm_pchan_name(as_pchan)); + + rc = trx_set_ts_as_pchan(ts, as_pchan); + if (rc) + return rc; + + LOGP(DL1C, LOGL_NOTICE, "%s bts_model_ts_connect(as_pchan=%s) success," + " calling cb_ts_connected()\n", + gsm_ts_name(ts), gsm_pchan_name(as_pchan)); + + cb_ts_connected(ts); + return 0; +} diff --git a/src/osmo-bts-trx/l1_if.h b/src/osmo-bts-trx/l1_if.h new file mode 100644 index 0000000..77c5936 --- /dev/null +++ b/src/osmo-bts-trx/l1_if.h @@ -0,0 +1,81 @@ +#ifndef L1_IF_H_TRX +#define L1_IF_H_TRX + +#include +#include +#include "trx_if.h" + +struct trx_config { + uint8_t poweron; /* poweron(1) or poweroff(0) */ + int poweron_sent; + + int arfcn_valid; + uint16_t arfcn; + int arfcn_sent; + + int tsc_valid; + uint8_t tsc; + int tsc_sent; + + int bsic_valid; + uint8_t bsic; + int bsic_sent; + + int rxgain_valid; + uint8_t rxgain; + int rxgain_sent; + + int power_valid; + uint8_t power; + int power_oml; + int power_sent; + + int maxdly_valid; + int maxdly; + int maxdly_sent; + + int maxdlynb_valid; + int maxdlynb; + int maxdlynb_sent; + + uint8_t slotmask; + + int slottype_valid[TRX_NR_TS]; + uint8_t slottype[TRX_NR_TS]; + int slottype_sent[TRX_NR_TS]; +}; + +struct trx_l1h { + struct llist_head trx_ctrl_list; + /* Latest RSPed cmd, used to catch duplicate RSPs from sent retransmissions */ + struct trx_ctrl_msg *last_acked; + + //struct gsm_bts_trx *trx; + struct phy_instance *phy_inst; + + struct osmo_fd trx_ofd_ctrl; + struct osmo_timer_list trx_ctrl_timer; + struct osmo_fd trx_ofd_data; + + /* transceiver config */ + struct trx_config config; + uint8_t ho_rach_detect[TRX_NR_TS][TS_MAX_LCHAN]; + + struct l1sched_trx l1s; +}; + +int check_transceiver_availability(struct gsm_bts *bts, int avail); +int l1if_provision_transceiver_trx(struct trx_l1h *l1h); +int l1if_provision_transceiver(struct gsm_bts *bts); +int l1if_mph_time_ind(struct gsm_bts *bts, uint32_t fn); +int l1if_process_meas_res(struct gsm_bts_trx *trx, uint8_t tn, uint32_t fn, uint8_t chan_nr, + int n_errors, int n_bits_total, float rssi, int16_t toa256); + +static inline struct l1sched_trx *trx_l1sched_hdl(struct gsm_bts_trx *trx) +{ + struct phy_instance *pinst = trx->role_bts.l1h; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + return &l1h->l1s; +} + +#endif /* L1_IF_H_TRX */ diff --git a/src/osmo-bts-trx/loops.c b/src/osmo-bts-trx/loops.c new file mode 100644 index 0000000..a959a71 --- /dev/null +++ b/src/osmo-bts-trx/loops.c @@ -0,0 +1,341 @@ +/* Loop control for OsmoBTS-TRX */ + +/* (C) 2013 by Andreas Eversberg + * + * 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 . + * + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "trx_if.h" +#include "l1_if.h" +#include "loops.h" + +#define MS_PWR_DBM(arfcn, lvl) ms_pwr_dbm(gsm_arfcn2band(arfcn), lvl) + +/* + * MS Power loop + */ + +static int ms_power_diff(struct gsm_lchan *lchan, uint8_t chan_nr, int8_t diff) +{ + struct gsm_bts_trx *trx = lchan->ts->trx; + uint16_t arfcn = trx->arfcn; + int8_t new_power; + + new_power = lchan->ms_power_ctrl.current - (diff >> 1); + + if (diff == 0) + return 0; + + if (new_power < 0) + new_power = 0; + + // FIXME: to go above 1W, we need to know classmark of MS + if (arfcn >= 512 && arfcn <= 885) { + if (new_power > 15) + new_power = 15; + } else { + if (new_power > 19) + new_power = 19; + } + + /* a higher value means a lower level (and vice versa) */ + if (new_power > lchan->ms_power_ctrl.current + MS_LOWER_MAX) + new_power = lchan->ms_power_ctrl.current + MS_LOWER_MAX; + else if (new_power < lchan->ms_power_ctrl.current - MS_RAISE_MAX) + new_power = lchan->ms_power_ctrl.current - MS_RAISE_MAX; + + if (lchan->ms_power_ctrl.current == new_power) { + LOGP(DLOOP, LOGL_INFO, "Keeping MS new_power of trx=%u " + "chan_nr=0x%02x at control level %d (%d dBm)\n", + trx->nr, chan_nr, new_power, + MS_PWR_DBM(arfcn, new_power)); + + return 0; + } + + LOGP(DLOOP, LOGL_INFO, "%s MS new_power of trx=%u chan_nr=0x%02x from " + "control level %d (%d dBm) to %d (%d dBm)\n", + (diff > 0) ? "Raising" : "Lowering", + trx->nr, chan_nr, lchan->ms_power_ctrl.current, + MS_PWR_DBM(arfcn, lchan->ms_power_ctrl.current), new_power, + MS_PWR_DBM(arfcn, new_power)); + + lchan->ms_power_ctrl.current = new_power; + + return 0; +} + +static int ms_power_val(struct l1sched_chan_state *chan_state, int8_t rssi) +{ + /* ignore inserted dummy frames, treat as lost frames */ + if (rssi < -127) + return 0; + + LOGP(DLOOP, LOGL_DEBUG, "Got RSSI value of %d\n", rssi); + + chan_state->meas.rssi_count++; + + chan_state->meas.rssi_got_burst = 1; + + /* store and process RSSI */ + if (chan_state->meas.rssi_valid_count + == ARRAY_SIZE(chan_state->meas.rssi)) + return 0; + chan_state->meas.rssi[chan_state->meas.rssi_valid_count++] = rssi; + chan_state->meas.rssi_valid_count++; + + return 0; +} + +static int ms_power_clock(struct gsm_lchan *lchan, + uint8_t chan_nr, struct l1sched_chan_state *chan_state) +{ + struct gsm_bts_trx *trx = lchan->ts->trx; + struct phy_instance *pinst = trx_phy_instance(trx); + int rssi; + int i; + + /* skip every second clock, to prevent oscillating due to roundtrip + * delay */ + if (!(chan_state->meas.clock & 1)) + return 0; + + LOGP(DLOOP, LOGL_DEBUG, "Got SACCH master clock at RSSI count %d\n", + chan_state->meas.rssi_count); + + /* wait for initial burst */ + if (!chan_state->meas.rssi_got_burst) + return 0; + + /* if no burst was received from MS at clock */ + if (chan_state->meas.rssi_count == 0) { + LOGP(DLOOP, LOGL_NOTICE, "LOST SACCH frame of trx=%u " + "chan_nr=0x%02x, so we raise MS power\n", + trx->nr, chan_nr); + return ms_power_diff(lchan, chan_nr, MS_RAISE_MAX); + } + + /* reset total counter */ + chan_state->meas.rssi_count = 0; + + /* check the minimum level received after MS acknowledged the ordered + * power level */ + if (chan_state->meas.rssi_valid_count == 0) + return 0; + for (rssi = 999, i = 0; i < chan_state->meas.rssi_valid_count; i++) { + if (rssi > chan_state->meas.rssi[i]) + rssi = chan_state->meas.rssi[i]; + } + + /* reset valid counter */ + chan_state->meas.rssi_valid_count = 0; + + /* change RSSI */ + LOGP(DLOOP, LOGL_DEBUG, "Lowest RSSI: %d Target RSSI: %d Current " + "MS power: %d (%d dBm) of trx=%u chan_nr=0x%02x\n", rssi, + pinst->phy_link->u.osmotrx.trx_target_rssi, lchan->ms_power_ctrl.current, + MS_PWR_DBM(trx->arfcn, lchan->ms_power_ctrl.current), + trx->nr, chan_nr); + ms_power_diff(lchan, chan_nr, pinst->phy_link->u.osmotrx.trx_target_rssi - rssi); + + return 0; +} + + +/* 90% of one bit duration in 1/256 symbols: 256*0.9 */ +#define TOA256_9OPERCENT 230 + +/* + * Timing Advance loop + */ + +int ta_val(struct gsm_lchan *lchan, uint8_t chan_nr, + struct l1sched_chan_state *chan_state, int16_t toa256) +{ + struct gsm_bts_trx *trx = lchan->ts->trx; + + /* check if the current L1 header acks to the current ordered TA */ + if (lchan->meas.l1_info[1] != lchan->rqd_ta) + return 0; + + /* sum measurement */ + chan_state->meas.toa256_sum += toa256; + if (++(chan_state->meas.toa_num) < 16) + return 0; + + /* complete set */ + toa256 = chan_state->meas.toa256_sum / chan_state->meas.toa_num; + + /* check for change of TOA */ + if (toa256 < -TOA256_9OPERCENT && lchan->rqd_ta > 0) { + LOGP(DLOOP, LOGL_INFO, "TOA of trx=%u chan_nr=0x%02x is too " + "early (%d), now lowering TA from %d to %d\n", + trx->nr, chan_nr, toa256, lchan->rqd_ta, + lchan->rqd_ta - 1); + lchan->rqd_ta--; + } else if (toa256 > TOA256_9OPERCENT && lchan->rqd_ta < 63) { + LOGP(DLOOP, LOGL_INFO, "TOA of trx=%u chan_nr=0x%02x is too " + "late (%d), now raising TA from %d to %d\n", + trx->nr, chan_nr, toa256, lchan->rqd_ta, + lchan->rqd_ta + 1); + lchan->rqd_ta++; + } else + LOGP(DLOOP, LOGL_INFO, "TOA of trx=%u chan_nr=0x%02x is " + "correct (%d), keeping current TA of %d\n", + trx->nr, chan_nr, toa256, lchan->rqd_ta); + + chan_state->meas.toa_num = 0; + chan_state->meas.toa256_sum = 0; + + return 0; +} + +int trx_loop_sacch_input(struct l1sched_trx *l1t, uint8_t chan_nr, + struct l1sched_chan_state *chan_state, int8_t rssi, int16_t toa256) +{ + struct gsm_lchan *lchan = &l1t->trx->ts[L1SAP_CHAN2TS(chan_nr)] + .lchan[l1sap_chan2ss(chan_nr)]; + struct phy_instance *pinst = trx_phy_instance(l1t->trx); + + if (pinst->phy_link->u.osmotrx.trx_ms_power_loop) + ms_power_val(chan_state, rssi); + + if (pinst->phy_link->u.osmotrx.trx_ta_loop) + ta_val(lchan, chan_nr, chan_state, toa256); + + return 0; +} + +int trx_loop_sacch_clock(struct l1sched_trx *l1t, uint8_t chan_nr, + struct l1sched_chan_state *chan_state) +{ + struct gsm_lchan *lchan = &l1t->trx->ts[L1SAP_CHAN2TS(chan_nr)] + .lchan[l1sap_chan2ss(chan_nr)]; + struct phy_instance *pinst = trx_phy_instance(l1t->trx); + + if (pinst->phy_link->u.osmotrx.trx_ms_power_loop) + ms_power_clock(lchan, chan_nr, chan_state); + + /* count the number of SACCH clocks */ + chan_state->meas.clock++; + + return 0; +} + +int trx_loop_amr_input(struct l1sched_trx *l1t, uint8_t chan_nr, + struct l1sched_chan_state *chan_state, float ber) +{ + struct gsm_bts_trx *trx = l1t->trx; + struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)] + .lchan[l1sap_chan2ss(chan_nr)]; + + /* check if loop is enabled */ + if (!chan_state->amr_loop) + return 0; + + /* wait for MS to use the requested codec */ + if (chan_state->ul_ft != chan_state->dl_cmr) + return 0; + + /* count bit errors */ + if (L1SAP_IS_CHAN_TCHH(chan_nr)) { + chan_state->ber_num += 2; + chan_state->ber_sum += (ber + ber); + } else { + chan_state->ber_num++; + chan_state->ber_sum += ber; + } + + /* count frames */ + if (chan_state->ber_num < 48) + return 0; + + /* calculate average (reuse ber variable) */ + ber = chan_state->ber_sum / chan_state->ber_num; + + /* reset bit errors */ + chan_state->ber_num = 0; + chan_state->ber_sum = 0; + + LOGP(DLOOP, LOGL_DEBUG, "Current bit error rate (BER) %.6f " + "codec id %d of trx=%u chan_nr=0x%02x\n", ber, + chan_state->ul_ft, trx->nr, chan_nr); + + /* degrade */ + if (chan_state->dl_cmr > 0) { + /* degrade, if ber is above threshold FIXME: C/I */ + if (ber > + lchan->tch.amr_mr.bts_mode[chan_state->dl_cmr-1].threshold) { + LOGP(DLOOP, LOGL_DEBUG, "Degrading due to BER %.6f " + "from codec id %d to %d of trx=%u " + "chan_nr=0x%02x\n", ber, chan_state->dl_cmr, + chan_state->dl_cmr - 1, trx->nr, chan_nr); + chan_state->dl_cmr--; + } + + return 0; + } + + /* upgrade */ + if (chan_state->dl_cmr < chan_state->codecs - 1) { + /* degrade, if ber is above threshold FIXME: C/I*/ + if (ber < + lchan->tch.amr_mr.bts_mode[chan_state->dl_cmr].threshold + - lchan->tch.amr_mr.bts_mode[chan_state->dl_cmr].hysteresis) { + LOGP(DLOOP, LOGL_DEBUG, "Upgrading due to BER %.6f " + "from codec id %d to %d of trx=%u " + "chan_nr=0x%02x\n", ber, chan_state->dl_cmr, + chan_state->dl_cmr + 1, trx->nr, chan_nr); + chan_state->dl_cmr++; + } + + return 0; + } + + return 0; +} + +int trx_loop_amr_set(struct l1sched_chan_state *chan_state, int loop) +{ + if (chan_state->amr_loop && !loop) { + chan_state->amr_loop = 0; + + return 0; + } + + if (!chan_state->amr_loop && loop) { + chan_state->amr_loop = 1; + + /* reset bit errors */ + chan_state->ber_num = 0; + chan_state->ber_sum = 0; + + return 0; + } + + return 0; +} diff --git a/src/osmo-bts-trx/loops.h b/src/osmo-bts-trx/loops.h new file mode 100644 index 0000000..f9e69c8 --- /dev/null +++ b/src/osmo-bts-trx/loops.h @@ -0,0 +1,27 @@ +#ifndef _TRX_LOOPS_H +#define _TRX_LOOPS_H + +/* + * calibration of loops + */ + +/* how much power levels do we raise/lower as maximum (1 level = 2 dB) */ +#define MS_RAISE_MAX 4 +#define MS_LOWER_MAX 2 + +/* + * loops api + */ + +int trx_loop_sacch_input(struct l1sched_trx *l1t, uint8_t chan_nr, + struct l1sched_chan_state *chan_state, int8_t rssi, int16_t toa); + +int trx_loop_sacch_clock(struct l1sched_trx *l1t, uint8_t chan_nr, + struct l1sched_chan_state *chan_state); + +int trx_loop_amr_input(struct l1sched_trx *l1t, uint8_t chan_nr, + struct l1sched_chan_state *chan_state, float ber); + +int trx_loop_amr_set(struct l1sched_chan_state *chan_state, int loop); + +#endif /* _TRX_LOOPS_H */ diff --git a/src/osmo-bts-trx/main.c b/src/osmo-bts-trx/main.c new file mode 100644 index 0000000..3b82e42 --- /dev/null +++ b/src/osmo-bts-trx/main.c @@ -0,0 +1,146 @@ +/* Main program for OsmoBTS-TRX */ + +/* (C) 2011-2015 by Harald Welte + * (C) 2013 by Andreas Eversberg + * + * 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 . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "l1_if.h" +#include "trx_if.h" + +/* dummy, since no direct dsp support */ +uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx) +{ + return 0; +} + +void bts_model_print_help() +{ +} + +int bts_model_handle_options(int argc, char **argv) +{ + int num_errors = 0; + + while (1) { + int option_idx = 0, c; + static const struct option long_options[] = { + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "", + long_options, &option_idx); + + if (c == -1) + break; + + switch (c) { + default: + num_errors++; + break; + } + } + + return num_errors; +} + +int bts_model_init(struct gsm_bts *bts) +{ + bts->variant = BTS_OSMO_TRX; + bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3); + + /* FIXME: this needs to be overridden with the real hardrware + * value */ + bts->c0->nominal_power = 23; + + gsm_bts_set_feature(bts, BTS_FEAT_GPRS); + gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_EFR); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_AMR); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_AMR); + + bts_model_vty_init(bts); + + return 0; +} + +void bts_model_phy_link_set_defaults(struct phy_link *plink) +{ + plink->u.osmotrx.local_ip = talloc_strdup(plink, "127.0.0.1"); + plink->u.osmotrx.remote_ip = talloc_strdup(plink, "127.0.0.1"); + plink->u.osmotrx.base_port_local = 5800; + plink->u.osmotrx.base_port_remote = 5700; + plink->u.osmotrx.clock_advance = 20; + plink->u.osmotrx.rts_advance = 5; + plink->u.osmotrx.trx_ta_loop = true; + plink->u.osmotrx.trx_ms_power_loop = false; + plink->u.osmotrx.trx_target_rssi = -10; +} + +void bts_model_phy_instance_set_defaults(struct phy_instance *pinst) +{ + struct trx_l1h *l1h; + l1h = talloc_zero(tall_bts_ctx, struct trx_l1h); + l1h->phy_inst = pinst; + pinst->u.osmotrx.hdl = l1h; + + l1h->config.power_oml = 1; +} + +int main(int argc, char **argv) +{ + return bts_main(argc, argv); +} diff --git a/src/osmo-bts-trx/scheduler_trx.c b/src/osmo-bts-trx/scheduler_trx.c new file mode 100644 index 0000000..4c4ae6c --- /dev/null +++ b/src/osmo-bts-trx/scheduler_trx.c @@ -0,0 +1,1687 @@ +/* Scheduler worker functions for OsmoBTS-TRX */ + +/* (C) 2013 by Andreas Eversberg + * (C) 2015 by Alexander Chemeris + * (C) 2015-2017 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "l1_if.h" +#include "trx_if.h" +#include "loops.h" + +extern void *tall_bts_ctx; + +/* Maximum size of a EGPRS message in bytes */ +#define EGPRS_0503_MAX_BYTES 155 + + +/* Compute the bit error rate in 1/10000 units */ +static inline uint16_t compute_ber10k(int n_bits_total, int n_errors) +{ + if (n_bits_total == 0) + return 10000; + else + return 10000 * n_errors / n_bits_total; +} + +/* + * TX on downlink + */ + +/* an IDLE burst returns nothing. on C0 it is replaced by dummy burst */ +ubit_t *tx_idle_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting IDLE\n"); + + if (nbits) + *nbits = GSM_BURST_LEN; + + return NULL; +} + +/* obtain a to-be-transmitted FCCH (frequency correction channel) burst */ +ubit_t *tx_fcch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting FCCH\n"); + + if (nbits) + *nbits = GSM_BURST_LEN; + + /* BURST BYPASS */ + + return (ubit_t *) _sched_fcch_burst; +} + +/* obtain a to-be-transmitted SCH (synchronization channel) burst */ +ubit_t *tx_sch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + static ubit_t bits[GSM_BURST_LEN], burst[78]; + uint8_t sb_info[4]; + struct gsm_time t; + uint8_t t3p, bsic; + + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting SCH\n"); + + /* BURST BYPASS */ + + /* create SB info from GSM time and BSIC */ + gsm_fn2gsmtime(&t, fn); + t3p = t.t3 / 10; + bsic = l1t->trx->bts->bsic; + sb_info[0] = + ((bsic & 0x3f) << 2) | + ((t.t1 & 0x600) >> 9); + sb_info[1] = + ((t.t1 & 0x1fe) >> 1); + sb_info[2] = + ((t.t1 & 0x001) << 7) | + ((t.t2 & 0x1f) << 2) | + ((t3p & 0x6) >> 1); + sb_info[3] = + (t3p & 0x1); + + /* encode bursts */ + gsm0503_sch_encode(burst, sb_info); + + /* compose burst */ + memset(bits, 0, 3); + memcpy(bits + 3, burst, 39); + memcpy(bits + 42, _sched_sch_train, 64); + memcpy(bits + 106, burst + 39, 39); + memset(bits + 145, 0, 3); + + if (nbits) + *nbits = GSM_BURST_LEN; + + return bits; +} + +/* obtain a to-be-transmitted data (SACCH/SDCCH) burst */ +ubit_t *tx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn]; + uint8_t link_id = trx_chan_desc[chan].link_id; + uint8_t chan_nr = trx_chan_desc[chan].chan_nr | tn; + struct msgb *msg = NULL; /* make GCC happy */ + ubit_t *burst, **bursts_p = &l1ts->chan_state[chan].dl_bursts; + static ubit_t bits[GSM_BURST_LEN]; + + /* send burst, if we already got a frame */ + if (bid > 0) { + if (!*bursts_p) + return NULL; + goto send_burst; + } + + /* send clock information to loops process */ + if (L1SAP_IS_LINK_SACCH(link_id)) + trx_loop_sacch_clock(l1t, chan_nr, &l1ts->chan_state[chan]); + + /* get mac block from queue */ + msg = _sched_dequeue_prim(l1t, tn, fn, chan); + if (msg) + goto got_msg; + + LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No prim for transmit.\n"); + +no_msg: + /* free burst memory */ + if (*bursts_p) { + talloc_free(*bursts_p); + *bursts_p = NULL; + } + return NULL; + +got_msg: + /* check validity of message */ + if (msgb_l2len(msg) != GSM_MACBLOCK_LEN) { + LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim not 23 bytes, please FIX! " + "(len=%d)\n", msgb_l2len(msg)); + /* free message */ + msgb_free(msg); + goto no_msg; + } + + /* BURST BYPASS */ + + /* handle loss detection of SACCH */ + if (L1SAP_IS_LINK_SACCH(trx_chan_desc[chan].link_id)) { + /* count and send BFI */ + if (++(l1ts->chan_state[chan].lost) > 1) { + /* TODO: Should we pass old TOA here? Otherwise we risk + * unnecessary decreasing TA */ + + /* Send uplink measurement information to L2 */ + l1if_process_meas_res(l1t->trx, tn, fn, trx_chan_desc[chan].chan_nr | tn, + 456, 456, -110, 0); + /* FIXME: use actual values for BER etc */ + _sched_compose_ph_data_ind(l1t, tn, 0, chan, NULL, 0, + -110, 0, 0, 10000, + PRES_INFO_INVALID); + } + } + + /* allocate burst memory, if not already */ + if (!*bursts_p) { + *bursts_p = talloc_zero_size(tall_bts_ctx, 464); + if (!*bursts_p) + return NULL; + } + + /* encode bursts */ + gsm0503_xcch_encode(*bursts_p, msg->l2h); + + /* free message */ + msgb_free(msg); + +send_burst: + /* compose burst */ + burst = *bursts_p + bid * 116; + memset(bits, 0, 3); + memcpy(bits + 3, burst, 58); + memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26); + memcpy(bits + 87, burst + 58, 58); + memset(bits + 145, 0, 3); + + if (nbits) + *nbits = GSM_BURST_LEN; + + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid); + + return bits; +} + +/* obtain a to-be-transmitted PDTCH (packet data) burst */ +ubit_t *tx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn]; + struct msgb *msg = NULL; /* make GCC happy */ + ubit_t *burst, **bursts_p = &l1ts->chan_state[chan].dl_bursts; + enum trx_burst_type *burst_type = &l1ts->chan_state[chan].dl_burst_type; + static ubit_t bits[EGPRS_BURST_LEN]; + int rc = 0; + + /* send burst, if we already got a frame */ + if (bid > 0) { + if (!*bursts_p) + return NULL; + goto send_burst; + } + + /* get mac block from queue */ + msg = _sched_dequeue_prim(l1t, tn, fn, chan); + if (msg) + goto got_msg; + + LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No prim for transmit.\n"); + +no_msg: + /* free burst memory */ + if (*bursts_p) { + talloc_free(*bursts_p); + *bursts_p = NULL; + } + return NULL; + +got_msg: + /* BURST BYPASS */ + + /* allocate burst memory, if not already */ + if (!*bursts_p) { + *bursts_p = talloc_zero_size(tall_bts_ctx, + GSM0503_EGPRS_BURSTS_NBITS); + if (!*bursts_p) + return NULL; + } + + /* encode bursts */ + rc = gsm0503_pdtch_egprs_encode(*bursts_p, msg->l2h, msg->tail - msg->l2h); + if (rc < 0) + rc = gsm0503_pdtch_encode(*bursts_p, msg->l2h, msg->tail - msg->l2h); + + /* check validity of message */ + if (rc < 0) { + LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim invalid length, please FIX! " + "(len=%ld)\n", msg->tail - msg->l2h); + /* free message */ + msgb_free(msg); + goto no_msg; + } else if (rc == GSM0503_EGPRS_BURSTS_NBITS) { + *burst_type = TRX_BURST_8PSK; + } else { + *burst_type = TRX_BURST_GMSK; + } + + /* free message */ + msgb_free(msg); + +send_burst: + /* compose burst */ + if (*burst_type == TRX_BURST_8PSK) { + burst = *bursts_p + bid * 348; + memset(bits, 1, 9); + memcpy(bits + 9, burst, 174); + memcpy(bits + 183, _sched_egprs_tsc[gsm_ts_tsc(ts)], 78); + memcpy(bits + 261, burst + 174, 174); + memset(bits + 435, 1, 9); + + if (nbits) + *nbits = EGPRS_BURST_LEN; + } else { + burst = *bursts_p + bid * 116; + memset(bits, 0, 3); + memcpy(bits + 3, burst, 58); + memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26); + memcpy(bits + 87, burst + 58, 58); + memset(bits + 145, 0, 3); + + if (nbits) + *nbits = GSM_BURST_LEN; + } + + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid); + + return bits; +} + +/* determine if the FN is transmitting a CMR (1) or not (0) */ +static inline int fn_is_codec_mode_request(uint32_t fn) +{ + return (((fn + 4) % 26) >> 2) & 1; +} + +/* common section for generation of TCH bursts (TCH/H and TCH/F) */ +static void tx_tch_common(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, struct msgb **_msg_tch, + struct msgb **_msg_facch) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct msgb *msg1, *msg2, *msg_tch = NULL, *msg_facch = NULL; + struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan]; + uint8_t rsl_cmode = chan_state->rsl_cmode; + uint8_t tch_mode = chan_state->tch_mode; + struct osmo_phsap_prim *l1sap; + + /* handle loss detection of received TCH frames */ + if (rsl_cmode == RSL_CMOD_SPD_SPEECH + && ++(chan_state->lost) > 5) { + uint8_t tch_data[GSM_FR_BYTES]; + int len; + + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, + "Missing TCH bursts detected, sending BFI\n"); + + /* indicate bad frame */ + switch (tch_mode) { + case GSM48_CMODE_SPEECH_V1: /* FR / HR */ + if (chan != TRXC_TCHF) { /* HR */ + tch_data[0] = 0x70; /* F = 0, FT = 111 */ + memset(tch_data + 1, 0, 14); + len = 15; + break; + } + memset(tch_data, 0, GSM_FR_BYTES); + len = GSM_FR_BYTES; + break; + case GSM48_CMODE_SPEECH_EFR: /* EFR */ + if (chan != TRXC_TCHF) + goto inval_mode1; + memset(tch_data, 0, GSM_EFR_BYTES); + len = GSM_EFR_BYTES; + break; + case GSM48_CMODE_SPEECH_AMR: /* AMR */ + len = osmo_amr_rtp_enc(tch_data, + chan_state->codec[chan_state->dl_cmr], + chan_state->codec[chan_state->dl_ft], AMR_BAD); + if (len < 2) + break; + memset(tch_data + 2, 0, len - 2); + _sched_compose_tch_ind(l1t, tn, fn, chan, tch_data, len); + break; + default: +inval_mode1: + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode invalid, please fix!\n"); + len = 0; + } + if (len) + _sched_compose_tch_ind(l1t, tn, fn, chan, tch_data, len); + } + + /* get frame and unlink from queue */ + msg1 = _sched_dequeue_prim(l1t, tn, fn, chan); + msg2 = _sched_dequeue_prim(l1t, tn, fn, chan); + if (msg1) { + l1sap = msgb_l1sap_prim(msg1); + if (l1sap->oph.primitive == PRIM_TCH) { + msg_tch = msg1; + if (msg2) { + l1sap = msgb_l1sap_prim(msg2); + if (l1sap->oph.primitive == PRIM_TCH) { + LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, + "TCH twice, please FIX!\n"); + msgb_free(msg2); + } else + msg_facch = msg2; + } + } else { + msg_facch = msg1; + if (msg2) { + l1sap = msgb_l1sap_prim(msg2); + if (l1sap->oph.primitive != PRIM_TCH) { + LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, + "FACCH twice, please FIX!\n"); + msgb_free(msg2); + } else + msg_tch = msg2; + } + } + } else if (msg2) { + l1sap = msgb_l1sap_prim(msg2); + if (l1sap->oph.primitive == PRIM_TCH) + msg_tch = msg2; + else + msg_facch = msg2; + } + + /* check validity of message */ + if (msg_facch && msgb_l2len(msg_facch) != GSM_MACBLOCK_LEN) { + LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim not 23 bytes, please FIX! " + "(len=%d)\n", msgb_l2len(msg_facch)); + /* free message */ + msgb_free(msg_facch); + msg_facch = NULL; + } + + /* check validity of message, get AMR ft and cmr */ + if (!msg_facch && msg_tch) { + int len; + uint8_t cmr_codec; + int cmr, ft, i; + enum osmo_amr_type ft_codec; + enum osmo_amr_quality bfi; + int8_t sti, cmi; + + if (rsl_cmode != RSL_CMOD_SPD_SPEECH) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Dropping speech frame, " + "because we are not in speech mode\n"); + goto free_bad_msg; + } + + switch (tch_mode) { + case GSM48_CMODE_SPEECH_V1: /* FR / HR */ + if (chan != TRXC_TCHF) /* HR */ + len = 15; + else + len = GSM_FR_BYTES; + break; + case GSM48_CMODE_SPEECH_EFR: /* EFR */ + if (chan != TRXC_TCHF) + goto inval_mode2; + len = GSM_EFR_BYTES; + break; + case GSM48_CMODE_SPEECH_AMR: /* AMR */ + len = osmo_amr_rtp_dec(msg_tch->l2h, msgb_l2len(msg_tch), + &cmr_codec, &cmi, &ft_codec, + &bfi, &sti); + cmr = -1; + ft = -1; + for (i = 0; i < chan_state->codecs; i++) { + if (chan_state->codec[i] == cmr_codec) + cmr = i; + if (chan_state->codec[i] == ft_codec) + ft = i; + } + if (cmr >= 0) { /* new request */ + chan_state->dl_cmr = cmr; + /* disable AMR loop */ + trx_loop_amr_set(chan_state, 0); + } else { + /* enable AMR loop */ + trx_loop_amr_set(chan_state, 1); + } + if (ft < 0) { + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, + "Codec (FT = %d) of RTP frame not in list\n", ft_codec); + goto free_bad_msg; + } + if (fn_is_codec_mode_request(fn) && chan_state->dl_ft != ft) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Codec (FT = %d) " + " of RTP cannot be changed now, but in next frame\n", ft_codec); + goto free_bad_msg; + } + chan_state->dl_ft = ft; + if (bfi == AMR_BAD) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, + "Transmitting 'bad AMR frame'\n"); + goto free_bad_msg; + } + break; + default: +inval_mode2: + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode invalid, please fix!\n"); + goto free_bad_msg; + } + if (len < 0) { + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot send invalid AMR payload\n"); + goto free_bad_msg; + } + if (msgb_l2len(msg_tch) != len) { + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot send payload with " + "invalid length! (expecting %d, received %d)\n", + len, msgb_l2len(msg_tch)); +free_bad_msg: + /* free message */ + msgb_free(msg_tch); + msg_tch = NULL; + goto send_frame; + } + } + +send_frame: + *_msg_tch = msg_tch; + *_msg_facch = msg_facch; +} + +/* obtain a to-be-transmitted TCH/F (Full Traffic Channel) burst */ +ubit_t *tx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + struct msgb *msg_tch = NULL, *msg_facch = NULL; + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn]; + struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan]; + uint8_t tch_mode = chan_state->tch_mode; + ubit_t *burst, **bursts_p = &chan_state->dl_bursts; + static ubit_t bits[GSM_BURST_LEN]; + + /* send burst, if we already got a frame */ + if (bid > 0) { + if (!*bursts_p) + return NULL; + goto send_burst; + } + + tx_tch_common(l1t, tn, fn, chan, bid, &msg_tch, &msg_facch); + + /* BURST BYPASS */ + + /* allocate burst memory, if not already, + * otherwise shift buffer by 4 bursts for interleaving */ + if (!*bursts_p) { + *bursts_p = talloc_zero_size(tall_bts_ctx, 928); + if (!*bursts_p) + return NULL; + } else { + memcpy(*bursts_p, *bursts_p + 464, 464); + memset(*bursts_p + 464, 0, 464); + } + + /* no message at all */ + if (!msg_tch && !msg_facch) { + LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No TCH or FACCH prim for transmit.\n"); + goto send_burst; + } + + /* encode bursts (prioritize FACCH) */ + if (msg_facch) + gsm0503_tch_fr_encode(*bursts_p, msg_facch->l2h, msgb_l2len(msg_facch), + 1); + else if (tch_mode == GSM48_CMODE_SPEECH_AMR) + /* the first FN 4,13,21 defines that CMI is included in frame, + * the first FN 0,8,17 defines that CMR is included in frame. + */ + gsm0503_tch_afs_encode(*bursts_p, msg_tch->l2h + 2, + msgb_l2len(msg_tch) - 2, fn_is_codec_mode_request(fn), + chan_state->codec, chan_state->codecs, + chan_state->dl_ft, + chan_state->dl_cmr); + else + gsm0503_tch_fr_encode(*bursts_p, msg_tch->l2h, msgb_l2len(msg_tch), 1); + + /* free message */ + if (msg_tch) + msgb_free(msg_tch); + if (msg_facch) + msgb_free(msg_facch); + +send_burst: + /* compose burst */ + burst = *bursts_p + bid * 116; + memset(bits, 0, 3); + memcpy(bits + 3, burst, 58); + memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26); + memcpy(bits + 87, burst + 58, 58); + memset(bits + 145, 0, 3); + + if (nbits) + *nbits = GSM_BURST_LEN; + + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid); + + return bits; +} + +/* obtain a to-be-transmitted TCH/H (Half Traffic Channel) burst */ +ubit_t *tx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + struct msgb *msg_tch = NULL, *msg_facch = NULL; + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn]; + struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan]; + uint8_t tch_mode = chan_state->tch_mode; + ubit_t *burst, **bursts_p = &chan_state->dl_bursts; + static ubit_t bits[GSM_BURST_LEN]; + + /* send burst, if we already got a frame */ + if (bid > 0) { + if (!*bursts_p) + return NULL; + goto send_burst; + } + + /* get TCH and/or FACCH */ + tx_tch_common(l1t, tn, fn, chan, bid, &msg_tch, &msg_facch); + + /* check for FACCH alignment */ + if (msg_facch && ((((fn + 4) % 26) >> 2) & 1)) { + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot transmit FACCH starting on " + "even frames, please fix RTS!\n"); + msgb_free(msg_facch); + msg_facch = NULL; + } + + /* BURST BYPASS */ + + /* allocate burst memory, if not already, + * otherwise shift buffer by 2 bursts for interleaving */ + if (!*bursts_p) { + *bursts_p = talloc_zero_size(tall_bts_ctx, 696); + if (!*bursts_p) + return NULL; + } else { + memcpy(*bursts_p, *bursts_p + 232, 232); + if (chan_state->dl_ongoing_facch) { + memcpy(*bursts_p + 232, *bursts_p + 464, 232); + memset(*bursts_p + 464, 0, 232); + } else { + memset(*bursts_p + 232, 0, 232); + } + } + + /* no message at all */ + if (!msg_tch && !msg_facch && !chan_state->dl_ongoing_facch) { + LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No TCH or FACCH prim for transmit.\n"); + goto send_burst; + } + + /* encode bursts (prioritize FACCH) */ + if (msg_facch) { + gsm0503_tch_hr_encode(*bursts_p, msg_facch->l2h, msgb_l2len(msg_facch)); + chan_state->dl_ongoing_facch = 1; /* first of two TCH frames */ + } else if (chan_state->dl_ongoing_facch) /* second of two TCH frames */ + chan_state->dl_ongoing_facch = 0; /* we are done with FACCH */ + else if (tch_mode == GSM48_CMODE_SPEECH_AMR) + /* the first FN 4,13,21 or 5,14,22 defines that CMI is included + * in frame, the first FN 0,8,17 or 1,9,18 defines that CMR is + * included in frame. */ + gsm0503_tch_ahs_encode(*bursts_p, msg_tch->l2h + 2, + msgb_l2len(msg_tch) - 2, fn_is_codec_mode_request(fn), + chan_state->codec, chan_state->codecs, + chan_state->dl_ft, + chan_state->dl_cmr); + else + gsm0503_tch_hr_encode(*bursts_p, msg_tch->l2h, msgb_l2len(msg_tch)); + + /* free message */ + if (msg_tch) + msgb_free(msg_tch); + if (msg_facch) + msgb_free(msg_facch); + +send_burst: + /* compose burst */ + burst = *bursts_p + bid * 116; + memset(bits, 0, 3); + memcpy(bits + 3, burst, 58); + memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26); + memcpy(bits + 87, burst + 58, 58); + memset(bits + 145, 0, 3); + + if (nbits) + *nbits = GSM_BURST_LEN; + + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid); + + return bits; +} + + +/* + * RX on uplink (indication to upper layer) + */ + +int rx_rach_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256) +{ + uint8_t chan_nr; + struct osmo_phsap_prim l1sap; + int n_errors, n_bits_total; + uint8_t ra; + int rc; + + chan_nr = trx_chan_desc[chan].chan_nr | tn; + + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received RACH toa=%d\n", toa256); + + /* decode */ + rc = gsm0503_rach_decode_ber(&ra, bits + 8 + 41, l1t->trx->bts->bsic, &n_errors, &n_bits_total); + if (rc) { + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received bad AB frame\n"); + return 0; + } + + /* compose primitive */ + /* generate prim */ + memset(&l1sap, 0, sizeof(l1sap)); + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, + NULL); + l1sap.u.rach_ind.chan_nr = chan_nr; + l1sap.u.rach_ind.ra = ra; + l1sap.u.rach_ind.acc_delay = (toa256 >= 0) ? toa256/256 : 0; + l1sap.u.rach_ind.fn = fn; + + /* 11bit RACH is not supported for osmo-trx */ + l1sap.u.rach_ind.is_11bit = 0; + l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_0; + l1sap.u.rach_ind.rssi = rssi; + l1sap.u.rach_ind.ber10k = compute_ber10k(n_bits_total, n_errors); + l1sap.u.rach_ind.acc_delay_256bits = toa256; + + /* forward primitive */ + l1sap_up(l1t->trx, &l1sap); + + return 0; +} + +/*! \brief a single (SDCCH/SACCH) burst was received by the PHY, process it */ +int rx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan]; + sbit_t *burst, **bursts_p = &chan_state->ul_bursts; + uint32_t *first_fn = &chan_state->ul_first_fn; + uint8_t *mask = &chan_state->ul_mask; + float *rssi_sum = &chan_state->rssi_sum; + uint8_t *rssi_num = &chan_state->rssi_num; + int32_t *toa256_sum = &chan_state->toa256_sum; + uint8_t *toa_num = &chan_state->toa_num; + uint8_t l2[GSM_MACBLOCK_LEN], l2_len; + int n_errors, n_bits_total; + uint16_t ber10k; + int rc; + + /* handle RACH, if handover RACH detection is turned on */ + if (chan_state->ho_rach_detect == 1) + return rx_rach_fn(l1t, tn, fn, chan, bid, bits, GSM_BURST_LEN, rssi, toa256); + + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received Data, bid=%u\n", bid); + + /* allocate burst memory, if not already */ + if (!*bursts_p) { + *bursts_p = talloc_zero_size(tall_bts_ctx, 464); + if (!*bursts_p) + return -ENOMEM; + } + + /* clear burst & store frame number of first burst */ + if (bid == 0) { + memset(*bursts_p, 0, 464); + *mask = 0x0; + *first_fn = fn; + *rssi_sum = 0; + *rssi_num = 0; + *toa256_sum = 0; + *toa_num = 0; + } + + /* update mask + RSSI */ + *mask |= (1 << bid); + *rssi_sum += rssi; + (*rssi_num)++; + *toa256_sum += toa256; + (*toa_num)++; + + /* copy burst to buffer of 4 bursts */ + burst = *bursts_p + bid * 116; + memcpy(burst, bits + 3, 58); + memcpy(burst + 58, bits + 87, 58); + + /* send burst information to loops process */ + if (L1SAP_IS_LINK_SACCH(trx_chan_desc[chan].link_id)) { + trx_loop_sacch_input(l1t, trx_chan_desc[chan].chan_nr | tn, + chan_state, rssi, toa256); + } + + /* wait until complete set of bursts */ + if (bid != 3) + return 0; + + /* check for complete set of bursts */ + if ((*mask & 0xf) != 0xf) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received incomplete data (%u/%u)\n", + *first_fn, (*first_fn) % l1ts->mf_period); + + /* we require first burst to have correct FN */ + if (!(*mask & 0x1)) { + *mask = 0x0; + return 0; + } + } + *mask = 0x0; + + /* decode */ + rc = gsm0503_xcch_decode(l2, *bursts_p, &n_errors, &n_bits_total); + if (rc) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received bad data (%u/%u)\n", + *first_fn, (*first_fn) % l1ts->mf_period); + l2_len = 0; + } else + l2_len = GSM_MACBLOCK_LEN; + + /* Send uplink measurement information to L2 */ + l1if_process_meas_res(l1t->trx, tn, *first_fn, trx_chan_desc[chan].chan_nr | tn, + n_errors, n_bits_total, *rssi_sum / *rssi_num, *toa256_sum / *toa_num); + ber10k = compute_ber10k(n_bits_total, n_errors); + return _sched_compose_ph_data_ind(l1t, tn, *first_fn, chan, l2, l2_len, + *rssi_sum / *rssi_num, + 4 * (*toa256_sum) / *toa_num, 0, ber10k, + PRES_INFO_UNKNOWN); +} + +/*! \brief a single PDTCH burst was received by the PHY, process it */ +int rx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan]; + sbit_t *burst, **bursts_p = &chan_state->ul_bursts; + uint32_t *first_fn = &chan_state->ul_first_fn; + uint8_t *mask = &chan_state->ul_mask; + float *rssi_sum = &chan_state->rssi_sum; + uint8_t *rssi_num = &chan_state->rssi_num; + int32_t *toa256_sum = &chan_state->toa256_sum; + uint8_t *toa_num = &chan_state->toa_num; + uint8_t l2[EGPRS_0503_MAX_BYTES]; + int n_errors, n_bursts_bits, n_bits_total; + uint16_t ber10k; + int rc; + + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received PDTCH bid=%u\n", bid); + + /* allocate burst memory, if not already */ + if (!*bursts_p) { + *bursts_p = talloc_zero_size(tall_bts_ctx, + GSM0503_EGPRS_BURSTS_NBITS); + if (!*bursts_p) + return -ENOMEM; + } + + /* clear burst */ + if (bid == 0) { + memset(*bursts_p, 0, GSM0503_EGPRS_BURSTS_NBITS); + *mask = 0x0; + *first_fn = fn; + *rssi_sum = 0; + *rssi_num = 0; + *toa256_sum = 0; + *toa_num = 0; + } + + /* update mask + rssi */ + *mask |= (1 << bid); + *rssi_sum += rssi; + (*rssi_num)++; + *toa256_sum += toa256; + (*toa_num)++; + + /* copy burst to buffer of 4 bursts */ + if (nbits == EGPRS_BURST_LEN) { + burst = *bursts_p + bid * 348; + memcpy(burst, bits + 9, 174); + memcpy(burst + 174, bits + 261, 174); + n_bursts_bits = GSM0503_EGPRS_BURSTS_NBITS; + } else { + burst = *bursts_p + bid * 116; + memcpy(burst, bits + 3, 58); + memcpy(burst + 58, bits + 87, 58); + n_bursts_bits = GSM0503_GPRS_BURSTS_NBITS; + } + + /* wait until complete set of bursts */ + if (bid != 3) + return 0; + + /* check for complete set of bursts */ + if ((*mask & 0xf) != 0xf) { + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received incomplete frame (%u/%u)\n", + fn % l1ts->mf_period, l1ts->mf_period); + } + *mask = 0x0; + + /* + * Attempt to decode EGPRS bursts first. For 8-PSK EGPRS this is all we + * do. Attempt GPRS decoding on EGPRS failure. If the burst is GPRS, + * then we incur decoding overhead of 31 bits on the Type 3 EGPRS + * header, which is tolerable. + */ + rc = gsm0503_pdtch_egprs_decode(l2, *bursts_p, n_bursts_bits, + NULL, &n_errors, &n_bits_total); + + if ((nbits == GSM_BURST_LEN) && (rc < 0)) { + rc = gsm0503_pdtch_decode(l2, *bursts_p, NULL, + &n_errors, &n_bits_total); + } + + + /* Send uplink measurement information to L2 */ + l1if_process_meas_res(l1t->trx, tn, *first_fn, trx_chan_desc[chan].chan_nr | tn, + n_errors, n_bits_total, *rssi_sum / *rssi_num, *toa256_sum / *toa_num); + + if (rc <= 0) { + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received bad PDTCH (%u/%u)\n", + fn % l1ts->mf_period, l1ts->mf_period); + return 0; + } + ber10k = compute_ber10k(n_bits_total, n_errors); + return _sched_compose_ph_data_ind(l1t, tn, (fn + GSM_HYPERFRAME - 3) % GSM_HYPERFRAME, chan, + l2, rc, *rssi_sum / *rssi_num, 4 * (*toa256_sum) / *toa_num, 0, + ber10k, PRES_INFO_BOTH); +} + +/*! \brief a single TCH/F burst was received by the PHY, process it */ +int rx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan]; + sbit_t *burst, **bursts_p = &chan_state->ul_bursts; + uint32_t *first_fn = &chan_state->ul_first_fn; + uint8_t *mask = &chan_state->ul_mask; + uint8_t rsl_cmode = chan_state->rsl_cmode; + uint8_t tch_mode = chan_state->tch_mode; + uint8_t tch_data[128]; /* just to be safe */ + int rc, amr = 0; + int n_errors, n_bits_total; + bool bfi_flag = false; + struct gsm_lchan *lchan = + get_lchan_by_chan_nr(l1t->trx, trx_chan_desc[chan].chan_nr | tn); + + /* handle rach, if handover rach detection is turned on */ + if (chan_state->ho_rach_detect == 1) + return rx_rach_fn(l1t, tn, fn, chan, bid, bits, GSM_BURST_LEN, rssi, toa256); + + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received TCH/F, bid=%u\n", bid); + + /* allocate burst memory, if not already */ + if (!*bursts_p) { + *bursts_p = talloc_zero_size(tall_bts_ctx, 928); + if (!*bursts_p) + return -ENOMEM; + } + + /* clear burst */ + if (bid == 0) { + memset(*bursts_p + 464, 0, 464); + *mask = 0x0; + *first_fn = fn; + } + + /* update mask */ + *mask |= (1 << bid); + + /* copy burst to end of buffer of 8 bursts */ + burst = *bursts_p + bid * 116 + 464; + memcpy(burst, bits + 3, 58); + memcpy(burst + 58, bits + 87, 58); + + /* wait until complete set of bursts */ + if (bid != 3) + return 0; + + /* check for complete set of bursts */ + if ((*mask & 0xf) != 0xf) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received incomplete frame (%u/%u)\n", + fn % l1ts->mf_period, l1ts->mf_period); + } + *mask = 0x0; + + /* decode + * also shift buffer by 4 bursts for interleaving */ + switch ((rsl_cmode != RSL_CMOD_SPD_SPEECH) ? GSM48_CMODE_SPEECH_V1 + : tch_mode) { + case GSM48_CMODE_SPEECH_V1: /* FR */ + rc = gsm0503_tch_fr_decode(tch_data, *bursts_p, 1, 0, &n_errors, &n_bits_total); + if (rc >= 0) + lchan_set_marker(osmo_fr_check_sid(tch_data, rc), lchan); /* DTXu */ + break; + case GSM48_CMODE_SPEECH_EFR: /* EFR */ + rc = gsm0503_tch_fr_decode(tch_data, *bursts_p, 1, 1, &n_errors, &n_bits_total); + break; + case GSM48_CMODE_SPEECH_AMR: /* AMR */ + /* the first FN 0,8,17 defines that CMI is included in frame, + * the first FN 4,13,21 defines that CMR is included in frame. + * NOTE: A frame ends 7 FN after start. + */ + rc = gsm0503_tch_afs_decode(tch_data + 2, *bursts_p, + (((fn + 26 - 7) % 26) >> 2) & 1, chan_state->codec, + chan_state->codecs, &chan_state->ul_ft, + &chan_state->ul_cmr, &n_errors, &n_bits_total); + if (rc) + trx_loop_amr_input(l1t, + trx_chan_desc[chan].chan_nr | tn, chan_state, + (float)n_errors/(float)n_bits_total); + amr = 2; /* we store tch_data + 2 header bytes */ + /* only good speech frames get rtp header */ + if (rc != GSM_MACBLOCK_LEN && rc >= 4) { + rc = osmo_amr_rtp_enc(tch_data, + chan_state->codec[chan_state->ul_cmr], + chan_state->codec[chan_state->ul_ft], AMR_GOOD); + } + break; + default: + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode %u invalid, please fix!\n", + tch_mode); + return -EINVAL; + } + memcpy(*bursts_p, *bursts_p + 464, 464); + + /* Send uplink measurement information to L2 */ + l1if_process_meas_res(l1t->trx, tn, *first_fn, trx_chan_desc[chan].chan_nr|tn, + n_errors, n_bits_total, rssi, toa256); + + /* Check if the frame is bad */ + if (rc < 0) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received bad data (%u/%u)\n", + fn % l1ts->mf_period, l1ts->mf_period); + bfi_flag = true; + goto bfi; + } + if (rc < 4) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received bad data (%u/%u) " + "with invalid codec mode %d\n", fn % l1ts->mf_period, l1ts->mf_period, rc); + bfi_flag = true; + goto bfi; + } + + /* FACCH */ + if (rc == GSM_MACBLOCK_LEN) { + uint16_t ber10k = compute_ber10k(n_bits_total, n_errors); + _sched_compose_ph_data_ind(l1t, tn, (fn + GSM_HYPERFRAME - 7) % GSM_HYPERFRAME, chan, + tch_data + amr, GSM_MACBLOCK_LEN, rssi, 4 * toa256, 0, + ber10k, PRES_INFO_UNKNOWN); +bfi: + if (rsl_cmode == RSL_CMOD_SPD_SPEECH) { + /* indicate bad frame */ + switch (tch_mode) { + case GSM48_CMODE_SPEECH_V1: /* FR */ + if (lchan->tch.dtx.ul_sid) + return 0; /* DTXu: pause in progress */ + + /* Perform error concealment if possible */ + rc = osmo_ecu_fr_conceal(&lchan->ecu_state.fr, tch_data); + if (rc) { + memset(tch_data, 0, GSM_FR_BYTES); + tch_data[0] = 0xd0; + } + + rc = GSM_FR_BYTES; + break; + case GSM48_CMODE_SPEECH_EFR: /* EFR */ + memset(tch_data, 0, GSM_EFR_BYTES); + tch_data[0] = 0xc0; + rc = GSM_EFR_BYTES; + break; + case GSM48_CMODE_SPEECH_AMR: /* AMR */ + rc = osmo_amr_rtp_enc(tch_data, + chan_state->codec[chan_state->dl_cmr], + chan_state->codec[chan_state->dl_ft], + AMR_BAD); + if (rc < 2) + break; + memset(tch_data + 2, 0, rc - 2); + break; + default: + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, + "TCH mode %u invalid, please fix!\n", tch_mode); + return -EINVAL; + } + } + } + + if (rsl_cmode != RSL_CMOD_SPD_SPEECH) + return 0; + + /* Reset ECU with a good frame */ + if (!bfi_flag && tch_mode == GSM48_CMODE_SPEECH_V1) + osmo_ecu_fr_reset(&lchan->ecu_state.fr, tch_data); + + /* TCH or BFI */ + return _sched_compose_tch_ind(l1t, tn, (fn + GSM_HYPERFRAME - 7) % GSM_HYPERFRAME, chan, + tch_data, rc); +} + +/*! \brief a single TCH/H burst was received by the PHY, process it */ +int rx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan]; + sbit_t *burst, **bursts_p = &chan_state->ul_bursts; + uint32_t *first_fn = &chan_state->ul_first_fn; + uint8_t *mask = &chan_state->ul_mask; + uint8_t rsl_cmode = chan_state->rsl_cmode; + uint8_t tch_mode = chan_state->tch_mode; + uint8_t tch_data[128]; /* just to be safe */ + int rc, amr = 0; + int n_errors, n_bits_total; + struct gsm_lchan *lchan = + get_lchan_by_chan_nr(l1t->trx, trx_chan_desc[chan].chan_nr | tn); + /* Note on FN-10: If we are at FN 10, we decoded an even aligned + * TCH/FACCH frame, because our burst buffer carries 6 bursts. + * Even FN ending at: 10,11,19,20,2,3 + */ + int fn_is_odd = (((fn + 26 - 10) % 26) >> 2) & 1; + + /* handle RACH, if handover RACH detection is turned on */ + if (chan_state->ho_rach_detect == 1) + return rx_rach_fn(l1t, tn, fn, chan, bid, bits, GSM_BURST_LEN, rssi, toa256); + + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Received TCH/H, bid=%u\n", bid); + + /* allocate burst memory, if not already */ + if (!*bursts_p) { + *bursts_p = talloc_zero_size(tall_bts_ctx, 696); + if (!*bursts_p) + return -ENOMEM; + } + + /* clear burst */ + if (bid == 0) { + memset(*bursts_p + 464, 0, 232); + *mask = 0x0; + *first_fn = fn; + } + + /* update mask */ + *mask |= (1 << bid); + + /* copy burst to end of buffer of 6 bursts */ + burst = *bursts_p + bid * 116 + 464; + memcpy(burst, bits + 3, 58); + memcpy(burst + 58, bits + 87, 58); + + /* wait until complete set of bursts */ + if (bid != 1) + return 0; + + /* check for complete set of bursts */ + if ((*mask & 0x3) != 0x3) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received incomplete frame (%u/%u)\n", + fn % l1ts->mf_period, l1ts->mf_period); + } + *mask = 0x0; + + /* skip second of two TCH frames of FACCH was received */ + if (chan_state->ul_ongoing_facch) { + chan_state->ul_ongoing_facch = 0; + memcpy(*bursts_p, *bursts_p + 232, 232); + memcpy(*bursts_p + 232, *bursts_p + 464, 232); + goto bfi; + } + + /* decode + * also shift buffer by 4 bursts for interleaving */ + switch ((rsl_cmode != RSL_CMOD_SPD_SPEECH) ? GSM48_CMODE_SPEECH_V1 + : tch_mode) { + case GSM48_CMODE_SPEECH_V1: /* HR or signalling */ + /* Note on FN-10: If we are at FN 10, we decoded an even aligned + * TCH/FACCH frame, because our burst buffer carries 6 bursts. + * Even FN ending at: 10,11,19,20,2,3 + */ + rc = gsm0503_tch_hr_decode(tch_data, *bursts_p, + fn_is_odd, &n_errors, &n_bits_total); + if (rc) /* DTXu */ + lchan_set_marker(osmo_hr_check_sid(tch_data, rc), lchan); + break; + case GSM48_CMODE_SPEECH_AMR: /* AMR */ + /* the first FN 0,8,17 or 1,9,18 defines that CMI is included + * in frame, the first FN 4,13,21 or 5,14,22 defines that CMR + * is included in frame. + */ + rc = gsm0503_tch_ahs_decode(tch_data + 2, *bursts_p, + fn_is_odd, fn_is_odd, chan_state->codec, + chan_state->codecs, &chan_state->ul_ft, + &chan_state->ul_cmr, &n_errors, &n_bits_total); + if (rc) + trx_loop_amr_input(l1t, + trx_chan_desc[chan].chan_nr | tn, chan_state, + (float)n_errors/(float)n_bits_total); + amr = 2; /* we store tch_data + 2 two */ + /* only good speech frames get rtp header */ + if (rc != GSM_MACBLOCK_LEN && rc >= 4) { + rc = osmo_amr_rtp_enc(tch_data, + chan_state->codec[chan_state->ul_cmr], + chan_state->codec[chan_state->ul_ft], AMR_GOOD); + } + break; + default: + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode %u invalid, please fix!\n", + tch_mode); + return -EINVAL; + } + memcpy(*bursts_p, *bursts_p + 232, 232); + memcpy(*bursts_p + 232, *bursts_p + 464, 232); + + /* Send uplink measurement information to L2 */ + l1if_process_meas_res(l1t->trx, tn, *first_fn, trx_chan_desc[chan].chan_nr|tn, + n_errors, n_bits_total, rssi, toa256); + + /* Check if the frame is bad */ + if (rc < 0) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received bad data (%u/%u)\n", + fn % l1ts->mf_period, l1ts->mf_period); + goto bfi; + } + if (rc < 4) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Received bad data (%u/%u) " + "with invalid codec mode %d\n", fn % l1ts->mf_period, l1ts->mf_period, rc); + goto bfi; + } + + /* FACCH */ + if (rc == GSM_MACBLOCK_LEN) { + chan_state->ul_ongoing_facch = 1; + uint16_t ber10k = compute_ber10k(n_bits_total, n_errors); + _sched_compose_ph_data_ind(l1t, tn, + (fn + GSM_HYPERFRAME - 10 - ((fn % 26) >= 19)) % GSM_HYPERFRAME, chan, + tch_data + amr, GSM_MACBLOCK_LEN, rssi, toa256/64, 0, + ber10k, PRES_INFO_UNKNOWN); +bfi: + if (rsl_cmode == RSL_CMOD_SPD_SPEECH) { + /* indicate bad frame */ + switch (tch_mode) { + case GSM48_CMODE_SPEECH_V1: /* HR */ + if (lchan->tch.dtx.ul_sid) + return 0; /* DTXu: pause in progress */ + tch_data[0] = 0x70; /* F = 0, FT = 111 */ + memset(tch_data + 1, 0, 14); + rc = 15; + break; + case GSM48_CMODE_SPEECH_AMR: /* AMR */ + rc = osmo_amr_rtp_enc(tch_data, + chan_state->codec[chan_state->dl_cmr], + chan_state->codec[chan_state->dl_ft], + AMR_BAD); + if (rc < 2) + break; + memset(tch_data + 2, 0, rc - 2); + break; + default: + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, + "TCH mode %u invalid, please fix!\n", tch_mode); + return -EINVAL; + } + } + } + + if (rsl_cmode != RSL_CMOD_SPD_SPEECH) + return 0; + + /* TCH or BFI */ + /* Note on FN 19 or 20: If we received the last burst of a frame, + * it actually starts at FN 8 or 9. A burst starting there, overlaps + * with the slot 12, so an extra FN must be subtracted to get correct + * start of frame. + */ + return _sched_compose_tch_ind(l1t, tn, + (fn + GSM_HYPERFRAME - 10 - ((fn%26)==19) - ((fn%26)==20)) % GSM_HYPERFRAME, + chan, tch_data, rc); +} + +/* schedule all frames of all TRX for given FN */ +static int trx_sched_fn(struct gsm_bts *bts, uint32_t fn) +{ + struct gsm_bts_trx *trx; + uint8_t tn; + const ubit_t *bits; + uint8_t gain; + uint16_t nbits = 0; + + /* send time indication */ + l1if_mph_time_ind(bts, fn); + + /* process every TRX */ + llist_for_each_entry(trx, &bts->trx_list, list) { + struct phy_instance *pinst = trx_phy_instance(trx); + struct phy_link *plink = pinst->phy_link; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + struct l1sched_trx *l1t = &l1h->l1s; + + /* advance frame number, so the transceiver has more + * time until it must be transmitted. */ + fn = (fn + plink->u.osmotrx.clock_advance) % GSM_HYPERFRAME; + + /* we don't schedule, if power is off */ + if (!trx_if_powered(l1h)) + continue; + + /* process every TS of TRX */ + for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) { + /* ready-to-send */ + _sched_rts(l1t, tn, + (fn + plink->u.osmotrx.rts_advance) % GSM_HYPERFRAME); + /* get burst for FN */ + bits = _sched_dl_burst(l1t, tn, fn, &nbits); + if (!bits) { + /* if no bits, send no burst */ + continue; + } else + gain = 0; + if (nbits) + trx_if_send_burst(l1h, tn, fn, gain, bits, nbits); + } + } + + return 0; +} + +/* + * TRX frame clock handling + * + * In a "normal" synchronous PHY layer, we would be polled every time + * the PHY needs data for a given frame number. However, the + * OpenBTS-inherited TRX protocol works differently: We (L1) must + * autonomously send burst data based on our own clock, and every so + * often (currently every ~ 216 frames), we get a clock indication from + * the TRX. + * + * We're using a MONOTONIC timerfd interval timer for the 4.615ms frame + * intervals, and then compute + send the 8 bursts for that frame. + * + * Upon receiving a clock indication from the TRX, we compensate + * accordingly: If we were transmitting too fast, we're delaying the + * next interval timer accordingly. If we were too slow, we immediately + * send burst data for the missing frame numbers. + */ + +/*! clock state of a given TRX */ +struct osmo_trx_clock_state { + /*! number of FN periods without TRX clock indication */ + uint32_t fn_without_clock_ind; + struct { + /*! last FN we processed based on FN period timer */ + uint32_t fn; + /*! time at which we last processed FN */ + struct timespec tv; + } last_fn_timer; + struct { + /*! last FN we received a clock indication for */ + uint32_t fn; + /*! time at which we received the last clock indication */ + struct timespec tv; + } last_clk_ind; + /*! Osmocom FD wrapper for timerfd */ + struct osmo_fd fn_timer_ofd; +}; + +/* TODO: This must go and become part of the phy_link */ +static struct osmo_trx_clock_state g_clk_s = { .fn_timer_ofd.fd = -1 }; + +/*! duration of a GSM frame in nano-seconds. (120ms/26) */ +#define FRAME_DURATION_nS 4615384 +/*! duration of a GSM frame in micro-seconds (120s/26) */ +#define FRAME_DURATION_uS (FRAME_DURATION_nS/1000) +/*! maximum number of 'missed' frame periods we can tolerate of OS doesn't schedule us*/ +#define MAX_FN_SKEW 50 +/*! maximum number of frame periods we can tolerate without TRX Clock Indication*/ +#define TRX_LOSS_FRAMES 400 + +/*! compute the number of micro-seconds difference elapsed between \a last and \a now */ +static inline int compute_elapsed_us(const struct timespec *last, const struct timespec *now) +{ + int elapsed; + + elapsed = (now->tv_sec - last->tv_sec) * 1000000 + + (now->tv_nsec - last->tv_nsec) / 1000; + return elapsed; +} + +/*! compute the number of frame number intervals elapsed between \a last and \a now */ +static inline int compute_elapsed_fn(const uint32_t last, const uint32_t now) +{ + int elapsed_fn = (now + GSM_HYPERFRAME - last) % GSM_HYPERFRAME; + if (elapsed_fn >= 135774) + elapsed_fn -= GSM_HYPERFRAME; + return elapsed_fn; +} + +/*! normalise given 'struct timespec', i.e. carry nanoseconds into seconds */ +static inline void normalize_timespec(struct timespec *ts) +{ + ts->tv_sec += ts->tv_nsec / 1000000000; + ts->tv_nsec = ts->tv_nsec % 1000000000; +} + +/*! disable the osmocom-wrapped timerfd */ +static int timer_ofd_disable(struct osmo_fd *ofd) +{ + const struct itimerspec its_null = { + .it_value = { 0, 0 }, + .it_interval = { 0, 0 }, + }; + return timerfd_settime(ofd->fd, 0, &its_null, NULL); +} + +/*! schedule the osmcoom-wrapped timerfd to occur first at \a first, then periodically at \a interval + * \param[in] ofd Osmocom wrapped timerfd + * \param[in] first Relative time at which the timer should first execute (NULL = \a interval) + * \param[in] interval Time interval at which subsequent timer shall fire + * \returns 0 on success; negative on error */ +static int timer_ofd_schedule(struct osmo_fd *ofd, const struct timespec *first, + const struct timespec *interval) +{ + struct itimerspec its; + + if (ofd->fd < 0) + return -EINVAL; + + /* first expiration */ + if (first) + its.it_value = *first; + else + its.it_value = *interval; + /* repeating interval */ + its.it_interval = *interval; + + return timerfd_settime(ofd->fd, 0, &its, NULL); +} + +/*! setup osmocom-wrapped timerfd + * \param[inout] ofd Osmocom-wrapped timerfd on which to operate + * \param[in] cb Call-back function called when timerfd becomes readable + * \param[in] data Opaque data to be passed on to call-back + * \returns 0 on success; negative on error + * + * We simply initialize the data structures here, but do not yet + * schedule the timer. + */ +static int timer_ofd_setup(struct osmo_fd *ofd, int (*cb)(struct osmo_fd *, unsigned int), void *data) +{ + ofd->cb = cb; + ofd->data = data; + ofd->when = BSC_FD_READ; + + if (ofd->fd < 0) { + ofd->fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); + if (ofd->fd < 0) + return ofd->fd; + + osmo_fd_register(ofd); + } + return 0; +} + +/*! Increment a GSM frame number modulo GSM_HYPERFRAME */ +#define INCREMENT_FN(fn) (fn) = (((fn) + 1) % GSM_HYPERFRAME) + +extern int quit; + +/*! this is the timerfd-callback firing for every FN to be processed */ +static int trx_fn_timer_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct gsm_bts *bts = ofd->data; + struct osmo_trx_clock_state *tcs = &g_clk_s; + struct timespec tv_now; + uint64_t expire_count; + int elapsed_us; + int error_us; + int rc, i; + + if (!(what & BSC_FD_READ)) + return 0; + + /* read from timerfd: number of expirations of periodic timer */ + rc = read(ofd->fd, (void *) &expire_count, sizeof(expire_count)); + if (rc < 0 && errno == EAGAIN) + return 0; + OSMO_ASSERT(rc == sizeof(expire_count)); + + if (expire_count > 1) { + LOGP(DL1C, LOGL_NOTICE, "FN timer expire_count=%"PRIu64": We missed %"PRIu64" timers\n", + expire_count, expire_count-1); + } + + /* check if transceiver is still alive */ + if (tcs->fn_without_clock_ind++ == TRX_LOSS_FRAMES) { + LOGP(DL1C, LOGL_NOTICE, "No more clock from transceiver\n"); + goto no_clock; + } + + /* compute actual elapsed time and resulting OS scheduling error */ + clock_gettime(CLOCK_MONOTONIC, &tv_now); + elapsed_us = compute_elapsed_us(&tcs->last_fn_timer.tv, &tv_now); + error_us = elapsed_us - FRAME_DURATION_uS; +#ifdef DEBUG_CLOCK + printf("%s(): %09ld, elapsed_us=%05d, error_us=%-d: fn=%d\n", __func__, + tv_now.tv_nsec, elapsed_us, error_us, tcs->last_fn_timer.fn+1); +#endif + tcs->last_fn_timer.tv = tv_now; + + /* if someone played with clock, or if the process stalled */ + if (elapsed_us > FRAME_DURATION_uS * MAX_FN_SKEW || elapsed_us < 0) { + LOGP(DL1C, LOGL_ERROR, "PC clock skew: elapsed_us=%d, error_us=%d\n", + elapsed_us, error_us); + goto no_clock; + } + + /* call trx_sched_fn() for all expired FN */ + for (i = 0; i < expire_count; i++) { + INCREMENT_FN(tcs->last_fn_timer.fn); + trx_sched_fn(bts, tcs->last_fn_timer.fn); + } + + return 0; + +no_clock: + timer_ofd_disable(&tcs->fn_timer_ofd); + transceiver_available = 0; + + bts_shutdown(bts, "No clock from osmo-trx"); + + return -1; +} + +/*! reset clock with current fn and schedule it. Called when trx becomes + * available or when max clock skew is reached */ +static int trx_setup_clock(struct gsm_bts *bts, struct osmo_trx_clock_state *tcs, + struct timespec *tv_now, const struct timespec *interval, uint32_t fn) +{ + tcs->last_fn_timer.fn = fn; + /* call trx cheduler function for new 'last' FN */ + trx_sched_fn(bts, tcs->last_fn_timer.fn); + + /* schedule first FN clock timer */ + timer_ofd_setup(&tcs->fn_timer_ofd, trx_fn_timer_cb, bts); + timer_ofd_schedule(&tcs->fn_timer_ofd, NULL, interval); + + tcs->last_fn_timer.tv = *tv_now; + tcs->last_clk_ind.tv = *tv_now; + tcs->last_clk_ind.fn = fn; + + return 0; +} + +/*! called every time we receive a clock indication from TRX */ +int trx_sched_clock(struct gsm_bts *bts, uint32_t fn) +{ + struct osmo_trx_clock_state *tcs = &g_clk_s; + struct timespec tv_now; + int elapsed_us, elapsed_fn; + int64_t elapsed_us_since_clk, elapsed_fn_since_clk, error_us_since_clk; + unsigned int fn_caught_up = 0; + const struct timespec interval = { .tv_sec = 0, .tv_nsec = FRAME_DURATION_nS }; + + if (quit) + return 0; + + /* reset lost counter */ + tcs->fn_without_clock_ind = 0; + + clock_gettime(CLOCK_MONOTONIC, &tv_now); + + /* clock becomes valid */ + if (!transceiver_available) { + LOGP(DL1C, LOGL_NOTICE, "initial GSM clock received: fn=%u\n", fn); + + transceiver_available = 1; + + /* start provisioning transceiver */ + l1if_provision_transceiver(bts); + + /* tell BSC */ + check_transceiver_availability(bts, 1); + + return trx_setup_clock(bts, tcs, &tv_now, &interval, fn); + } + + /* calculate elapsed time +fn since last timer */ + elapsed_us = compute_elapsed_us(&tcs->last_fn_timer.tv, &tv_now); + elapsed_fn = compute_elapsed_fn(tcs->last_fn_timer.fn, fn); +#ifdef DEBUG_CLOCK + printf("%s(): LAST_TIMER %9ld, elapsed_us=%7d, elapsed_fn=%+3d\n", __func__, + tv_now.tv_nsec, elapsed_us, elapsed_fn); +#endif + /* negative elapsed_fn values mean that we've already processed + * more FN based on the local interval timer than what the TRX + * now reports in the clock indication. Positive elapsed_fn + * values mean we still have a backlog to process */ + + /* calculate elapsed time +fn since last clk ind */ + elapsed_us_since_clk = compute_elapsed_us(&tcs->last_clk_ind.tv, &tv_now); + elapsed_fn_since_clk = compute_elapsed_fn(tcs->last_clk_ind.fn, fn); + /* error (delta) between local clock since last CLK and CLK based on FN clock at TRX */ + error_us_since_clk = elapsed_us_since_clk - (FRAME_DURATION_uS * elapsed_fn_since_clk); + LOGP(DL1C, LOGL_INFO, "TRX Clock Ind: elapsed_us=%7"PRId64", " + "elapsed_fn=%3"PRId64", error_us=%+5"PRId64"\n", + elapsed_us_since_clk, elapsed_fn_since_clk, error_us_since_clk); + + /* TODO: put this computed error_us_since_clk into some filter + * function and use that to adjust our regular timer interval to + * compensate for clock drift between the PC clock and the + * TRX/SDR clock */ + + tcs->last_clk_ind.tv = tv_now; + tcs->last_clk_ind.fn = fn; + + /* check for max clock skew */ + if (elapsed_fn > MAX_FN_SKEW || elapsed_fn < -MAX_FN_SKEW) { + LOGP(DL1C, LOGL_NOTICE, "GSM clock skew: old fn=%u, " + "new fn=%u\n", tcs->last_fn_timer.fn, fn); + return trx_setup_clock(bts, tcs, &tv_now, &interval, fn); + } + + LOGP(DL1C, LOGL_INFO, "GSM clock jitter: %d us (elapsed_fn=%d)\n", + elapsed_fn * FRAME_DURATION_uS - elapsed_us, elapsed_fn); + + /* too many frames have been processed already */ + if (elapsed_fn < 0) { + struct timespec first = interval; + /* set clock to the time or last FN should have been + * transmitted. */ + first.tv_nsec += (0 - elapsed_fn) * FRAME_DURATION_nS; + normalize_timespec(&first); + LOGP(DL1C, LOGL_NOTICE, "We were %d FN faster than TRX, compensating\n", -elapsed_fn); + /* set time to the time our next FN has to be transmitted */ + timer_ofd_schedule(&tcs->fn_timer_ofd, &first, &interval); + return 0; + } + + /* transmit what we still need to transmit */ + while (fn != tcs->last_fn_timer.fn) { + INCREMENT_FN(tcs->last_fn_timer.fn); + trx_sched_fn(bts, tcs->last_fn_timer.fn); + fn_caught_up++; + } + + if (fn_caught_up) { + LOGP(DL1C, LOGL_NOTICE, "We were %d FN slower than TRX, compensated\n", elapsed_fn); + tcs->last_fn_timer.tv = tv_now; + } + + return 0; +} + +void _sched_act_rach_det(struct l1sched_trx *l1t, uint8_t tn, uint8_t ss, int activate) +{ + struct phy_instance *pinst = trx_phy_instance(l1t->trx); + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + if (activate) + trx_if_cmd_handover(l1h, tn, ss); + else + trx_if_cmd_nohandover(l1h, tn, ss); +} diff --git a/src/osmo-bts-trx/trx_if.c b/src/osmo-bts-trx/trx_if.c new file mode 100644 index 0000000..f3de245 --- /dev/null +++ b/src/osmo-bts-trx/trx_if.c @@ -0,0 +1,795 @@ +/* + * OpenBTS-style TRX interface/protocol handling + * + * This file contains the BTS-side implementation of the OpenBTS-style + * UDP TRX protocol. It manages the clock, control + burst-data UDP + * sockets and their respective protocol encoding/parsing. + * + * Copyright (C) 2013 Andreas Eversberg + * Copyright (C) 2016-2017 Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "l1_if.h" +#include "trx_if.h" + +/* enable to print RSSI level graph */ +//#define TOA_RSSI_DEBUG + +int transceiver_available = 0; + +#define TRX_MAX_BURST_LEN 512 + +/* + * socket helper functions + */ + +/*! convenience wrapper to open socket + fill in osmo_fd */ +static int trx_udp_open(void *priv, struct osmo_fd *ofd, const char *host_local, + uint16_t port_local, const char *host_remote, uint16_t port_remote, + int (*cb)(struct osmo_fd *fd, unsigned int what)) +{ + int rc; + + /* Init */ + ofd->fd = -1; + ofd->cb = cb; + ofd->data = priv; + + /* Listen / Binds + Connect */ + rc = osmo_sock_init2_ofd(ofd, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, host_local, port_local, + host_remote, port_remote, OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT); + if (rc < 0) + return rc; + + return 0; +} + +/* close socket + unregister osmo_fd */ +static void trx_udp_close(struct osmo_fd *ofd) +{ + if (ofd->fd >= 0) { + osmo_fd_unregister(ofd); + close(ofd->fd); + ofd->fd = -1; + } +} + + +/* + * TRX clock socket + */ + +/* get clock from clock socket */ +static int trx_clk_read_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct phy_link *plink = ofd->data; + struct phy_instance *pinst = phy_instance_by_num(plink, 0); + char buf[1500]; + int len; + uint32_t fn; + + OSMO_ASSERT(pinst); + + len = recv(ofd->fd, buf, sizeof(buf) - 1, 0); + if (len <= 0) + return len; + buf[len] = '\0'; + + if (!!strncmp(buf, "IND CLOCK ", 10)) { + LOGP(DTRX, LOGL_NOTICE, "Unknown message on clock port: %s\n", + buf); + return 0; + } + + if (sscanf(buf, "IND CLOCK %u", &fn) != 1) { + LOGP(DTRX, LOGL_ERROR, "Unable to parse '%s'\n", buf); + return 0; + } + + LOGP(DTRX, LOGL_INFO, "Clock indication: fn=%u\n", fn); + + if (fn >= GSM_HYPERFRAME) { + fn %= GSM_HYPERFRAME; + LOGP(DTRX, LOGL_ERROR, "Indicated clock's FN is not wrapping " + "correctly, correcting to fn=%u\n", fn); + } + + /* inform core TRX clock handling code that a FN has been received */ + trx_sched_clock(pinst->trx->bts, fn); + + return 0; +} + + +/* + * TRX ctrl socket + */ + +static void trx_ctrl_timer_cb(void *data); + +/* send first ctrl message and start timer */ +static void trx_ctrl_send(struct trx_l1h *l1h) +{ + struct trx_ctrl_msg *tcm; + char buf[1500]; + int len; + + /* get first command */ + if (llist_empty(&l1h->trx_ctrl_list)) + return; + tcm = llist_entry(l1h->trx_ctrl_list.next, struct trx_ctrl_msg, list); + + len = snprintf(buf, sizeof(buf), "CMD %s%s%s", tcm->cmd, tcm->params_len ? " ":"", tcm->params); + OSMO_ASSERT(len < sizeof(buf)); + + LOGP(DTRX, LOGL_DEBUG, "Sending control '%s' to %s\n", buf, phy_instance_name(l1h->phy_inst)); + /* send command */ + send(l1h->trx_ofd_ctrl.fd, buf, len+1, 0); + + /* start timer */ + l1h->trx_ctrl_timer.cb = trx_ctrl_timer_cb; + l1h->trx_ctrl_timer.data = l1h; + osmo_timer_schedule(&l1h->trx_ctrl_timer, 2, 0); +} + +/* send first ctrl message and start timer */ +static void trx_ctrl_timer_cb(void *data) +{ + struct trx_l1h *l1h = data; + struct trx_ctrl_msg *tcm = NULL; + + /* get first command */ + OSMO_ASSERT(!llist_empty(&l1h->trx_ctrl_list)); + tcm = llist_entry(l1h->trx_ctrl_list.next, struct trx_ctrl_msg, list); + + LOGP(DTRX, LOGL_NOTICE, "No response from transceiver for %s (CMD %s%s%s)\n", + phy_instance_name(l1h->phy_inst), + tcm->cmd, tcm->params_len ? " ":"", tcm->params); + + trx_ctrl_send(l1h); +} + +/*! Send a new TRX control command. + * \param[inout] l1h TRX Layer1 handle to which to send command + * \param[in] criticial + * \param[in] cmd zero-terminated string containing command + * \param[in] fmt Format string (+ variable list of arguments) + * \returns 0 on success; negative on error + * + * The new ocommand will be added to the end of the control command + * queue. + */ +static int trx_ctrl_cmd(struct trx_l1h *l1h, int critical, const char *cmd, + const char *fmt, ...) +{ + struct trx_ctrl_msg *tcm; + struct trx_ctrl_msg *prev = NULL; + va_list ap; + int pending; + + if (!transceiver_available && + !(!strcmp(cmd, "POWEROFF") || !strcmp(cmd, "POWERON"))) { + LOGP(DTRX, LOGL_ERROR, "CTRL %s ignored: No clock from " + "transceiver, please fix!\n", cmd); + return -EIO; + } + + pending = !llist_empty(&l1h->trx_ctrl_list); + + /* create message */ + tcm = talloc_zero(tall_bts_ctx, struct trx_ctrl_msg); + if (!tcm) + return -ENOMEM; + snprintf(tcm->cmd, sizeof(tcm->cmd)-1, "%s", cmd); + tcm->cmd[sizeof(tcm->cmd)-1] = '\0'; + tcm->cmd_len = strlen(tcm->cmd); + if (fmt && fmt[0]) { + va_start(ap, fmt); + vsnprintf(tcm->params, sizeof(tcm->params) - 1, fmt, ap); + va_end(ap); + tcm->params[sizeof(tcm->params)-1] = '\0'; + tcm->params_len = strlen(tcm->params); + } else { + tcm->params[0] ='\0'; + tcm->params_len = 0; + } + tcm->critical = critical; + + /* Avoid adding consecutive duplicate messages, eg: two consecutive POWEROFF */ + if(pending) + prev = llist_entry(l1h->trx_ctrl_list.prev, struct trx_ctrl_msg, list); + + if (!pending || + !(strcmp(tcm->cmd, prev->cmd) == 0 && strcmp(tcm->params, prev->params) == 0)) { + LOGP(DTRX, LOGL_INFO, "Enqueuing TRX control command 'CMD %s%s%s'\n", + tcm->cmd, tcm->params_len ? " ":"", tcm->params); + llist_add_tail(&tcm->list, &l1h->trx_ctrl_list); + } + + /* send message, if we didn't already have pending messages */ + if (!pending) + trx_ctrl_send(l1h); + + return 0; +} + +/*! Send "POWEROFF" command to TRX */ +int trx_if_cmd_poweroff(struct trx_l1h *l1h) +{ + struct phy_instance *pinst = l1h->phy_inst; + if (pinst->num == 0) + return trx_ctrl_cmd(l1h, 1, "POWEROFF", ""); + else + return 0; +} + +/*! Send "POWERON" command to TRX */ +int trx_if_cmd_poweron(struct trx_l1h *l1h) +{ + struct phy_instance *pinst = l1h->phy_inst; + if (pinst->num == 0) + return trx_ctrl_cmd(l1h, 1, "POWERON", ""); + else + return 0; +} + +/*! Send "SETTSC" command to TRX */ +int trx_if_cmd_settsc(struct trx_l1h *l1h, uint8_t tsc) +{ + struct phy_instance *pinst = l1h->phy_inst; + if (pinst->phy_link->u.osmotrx.use_legacy_setbsic) + return 0; + + return trx_ctrl_cmd(l1h, 1, "SETTSC", "%d", tsc); +} + +/*! Send "SETBSIC" command to TRX */ +int trx_if_cmd_setbsic(struct trx_l1h *l1h, uint8_t bsic) +{ + struct phy_instance *pinst = l1h->phy_inst; + if (!pinst->phy_link->u.osmotrx.use_legacy_setbsic) + return 0; + + return trx_ctrl_cmd(l1h, 1, "SETBSIC", "%d", bsic); +} + +/*! Send "SETRXGAIN" command to TRX */ +int trx_if_cmd_setrxgain(struct trx_l1h *l1h, int db) +{ + return trx_ctrl_cmd(l1h, 0, "SETRXGAIN", "%d", db); +} + +/*! Send "SETPOWER" command to TRX */ +int trx_if_cmd_setpower(struct trx_l1h *l1h, int db) +{ + return trx_ctrl_cmd(l1h, 0, "SETPOWER", "%d", db); +} + +/*! Send "SETMAXDLY" command to TRX, i.e. maximum delay for RACH bursts */ +int trx_if_cmd_setmaxdly(struct trx_l1h *l1h, int dly) +{ + return trx_ctrl_cmd(l1h, 0, "SETMAXDLY", "%d", dly); +} + +/*! Send "SETMAXDLYNB" command to TRX, i.e. maximum delay for normal bursts */ +int trx_if_cmd_setmaxdlynb(struct trx_l1h *l1h, int dly) +{ + return trx_ctrl_cmd(l1h, 0, "SETMAXDLYNB", "%d", dly); +} + +/*! Send "SETSLOT" command to TRX: Configure Channel Combination for TS */ +int trx_if_cmd_setslot(struct trx_l1h *l1h, uint8_t tn, uint8_t type) +{ + return trx_ctrl_cmd(l1h, 1, "SETSLOT", "%d %d", tn, type); +} + +/*! Send "RXTUNE" command to TRX: Tune Receiver to given ARFCN */ +int trx_if_cmd_rxtune(struct trx_l1h *l1h, uint16_t arfcn) +{ + struct phy_instance *pinst = l1h->phy_inst; + uint16_t freq10; + + if (pinst->trx->bts->band == GSM_BAND_1900) + arfcn |= ARFCN_PCS; + + freq10 = gsm_arfcn2freq10(arfcn, 1); /* RX = uplink */ + if (freq10 == 0xffff) { + LOGP(DTRX, LOGL_ERROR, "Arfcn %d not defined.\n", + arfcn & ~ARFCN_FLAG_MASK); + return -ENOTSUP; + } + + return trx_ctrl_cmd(l1h, 1, "RXTUNE", "%d", freq10 * 100); +} + +/*! Send "TXTUNE" command to TRX: Tune Transmitter to given ARFCN */ +int trx_if_cmd_txtune(struct trx_l1h *l1h, uint16_t arfcn) +{ + struct phy_instance *pinst = l1h->phy_inst; + uint16_t freq10; + + if (pinst->trx->bts->band == GSM_BAND_1900) + arfcn |= ARFCN_PCS; + + freq10 = gsm_arfcn2freq10(arfcn, 0); /* TX = downlink */ + if (freq10 == 0xffff) { + LOGP(DTRX, LOGL_ERROR, "Arfcn %d not defined.\n", + arfcn & ~ARFCN_FLAG_MASK); + return -ENOTSUP; + } + + return trx_ctrl_cmd(l1h, 1, "TXTUNE", "%d", freq10 * 100); +} + +/*! Send "HANDOVER" command to TRX: Enable handover RACH Detection on timeslot/sub-slot */ +int trx_if_cmd_handover(struct trx_l1h *l1h, uint8_t tn, uint8_t ss) +{ + return trx_ctrl_cmd(l1h, 1, "HANDOVER", "%d %d", tn, ss); +} + +/*! Send "NOHANDOVER" command to TRX: Disable handover RACH Detection on timeslot/sub-slot */ +int trx_if_cmd_nohandover(struct trx_l1h *l1h, uint8_t tn, uint8_t ss) +{ + return trx_ctrl_cmd(l1h, 1, "NOHANDOVER", "%d %d", tn, ss); +} + +static int parse_rsp(const char *buf_in, size_t len_in, char *cmdname_out, size_t cmdname_len, + char *params_out, size_t params_len, int *status) +{ + char *p, *k; + + if (strncmp(buf_in, "RSP ", 4)) + goto parse_err; + + /* Get the RSP cmd name */ + if (!(p = strchr(buf_in + 4, ' '))) + goto parse_err; + + if (p - buf_in >= cmdname_len) { + LOGP(DTRX, LOGL_ERROR, "cmdname buffer too small %lu >= %lu\n", + p - buf_in, cmdname_len); + goto parse_err; + } + + cmdname_out[0] = '\0'; + strncat(cmdname_out, buf_in + 4, p - buf_in - 4); + + /* Now comes the status code of the response */ + p++; + if (sscanf(p, "%d", status) != 1) + goto parse_err; + + /* Now copy back the parameters */ + k = strchr(p, ' '); + if (k) + k++; + else + k = p + strlen(p); + + if (strlen(k) >= params_len) { + LOGP(DTRX, LOGL_ERROR, "params buffer too small %lu >= %lu\n", + strlen(k), params_len); + goto parse_err; + } + params_out[0] = '\0'; + strcat(params_out, k); + return 0; + +parse_err: + LOGP(DTRX, LOGL_NOTICE, "Unknown message on ctrl port: %s\n", + buf_in); + return -1; +} + +static bool cmd_matches_rsp(struct trx_ctrl_msg *tcm, char *rspname, char* params) +{ + if (strcmp(tcm->cmd, rspname)) + return false; + + /* For SETSLOT we also need to check if it's the response for the + specific timeslot. For other commands such as SETRXGAIN, it is + expected that they can return different values */ + if (strcmp(tcm->cmd, "SETSLOT") == 0 && strcmp(tcm->params, params)) + return false; + + return true; +} + +/*! Get + parse response from TRX ctrl socket */ +static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct trx_l1h *l1h = ofd->data; + struct phy_instance *pinst = l1h->phy_inst; + char buf[1500], cmdname[50], params[100]; + int len, resp; + struct trx_ctrl_msg *tcm; + + len = recv(ofd->fd, buf, sizeof(buf) - 1, 0); + if (len <= 0) + return len; + buf[len] = '\0'; + + if (parse_rsp(buf, len, cmdname, sizeof(cmdname), params, sizeof(params), &resp) < 0) + return 0; + + LOGP(DTRX, LOGL_INFO, "Response message: '%s'\n", buf); + + /* abort timer and send next message, if any */ + if (osmo_timer_pending(&l1h->trx_ctrl_timer)) + osmo_timer_del(&l1h->trx_ctrl_timer); + + /* get command for response message */ + if (llist_empty(&l1h->trx_ctrl_list)) { + /* RSP from a retransmission, skip it */ + if (l1h->last_acked && cmd_matches_rsp(l1h->last_acked, cmdname, params)) { + LOGP(DTRX, LOGL_NOTICE, "Discarding duplicated RSP " + "from old CMD '%s'\n", buf); + return 0; + } + LOGP(DTRX, LOGL_NOTICE, "Response message without " + "command\n"); + return -EINVAL; + } + tcm = llist_entry(l1h->trx_ctrl_list.next, struct trx_ctrl_msg, + list); + + /* check if response matches command */ + if (!cmd_matches_rsp(tcm, cmdname, params)) { + /* RSP from a retransmission, skip it */ + if (l1h->last_acked && cmd_matches_rsp(l1h->last_acked, cmdname, params)) { + LOGP(DTRX, LOGL_NOTICE, "Discarding duplicated RSP " + "from old CMD '%s'\n", buf); + return 0; + } + LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_NOTICE, + "Response message '%s' does not match command " + "message 'CMD %s%s%s'\n", + buf, tcm->cmd, tcm->params_len ? " ":"", tcm->params); + goto rsp_error; + } + + /* check for response code */ + if (resp) { + LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_NOTICE, + "transceiver (%s) rejected TRX command " + "with response: '%s'\n", + phy_instance_name(pinst), buf); + if (tcm->critical) + goto rsp_error; + } + + /* remove command from list, save it to last_acked and removed previous last_acked */ + llist_del(&tcm->list); + talloc_free(l1h->last_acked); + l1h->last_acked = tcm; + + trx_ctrl_send(l1h); + + return 0; + +rsp_error: + bts_shutdown(pinst->trx->bts, "TRX-CTRL-MSG: CRITICAL"); + /* keep tcm list, so process is stopped */ + return -EIO; +} + + +/* + * TRX burst data socket + */ + +static int trx_data_read_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct trx_l1h *l1h = ofd->data; + uint8_t buf[TRX_MAX_BURST_LEN]; + int len; + uint8_t tn; + int8_t rssi; + int16_t toa256 = 0; + uint32_t fn; + sbit_t bits[EGPRS_BURST_LEN]; + int i, burst_len = GSM_BURST_LEN; + + len = recv(ofd->fd, buf, sizeof(buf), 0); + if (len <= 0) { + return len; + } else if (len == EGPRS_BURST_LEN + 10) { + burst_len = EGPRS_BURST_LEN; + /* Accept bursts ending with 2 bytes of padding (OpenBTS compatible trx) or without them: */ + } else if (len != GSM_BURST_LEN + 10 && len != GSM_BURST_LEN + 8) { + LOGP(DTRX, LOGL_NOTICE, "Got data message with invalid lenght " + "'%d'\n", len); + return -EINVAL; + } + tn = buf[0]; + fn = (buf[1] << 24) | (buf[2] << 16) | (buf[3] << 8) | buf[4]; + rssi = -(int8_t)buf[5]; + toa256 = ((int16_t)(buf[6] << 8) | buf[7]); + + /* copy and convert bits {254..0} to sbits {-127..127} */ + for (i = 0; i < burst_len; i++) { + if (buf[8 + i] == 255) + bits[i] = -127; + else + bits[i] = 127 - buf[8 + i]; + } + + if (tn >= 8) { + LOGP(DTRX, LOGL_ERROR, "Illegal TS %d\n", tn); + return -EINVAL; + } + if (fn >= GSM_HYPERFRAME) { + LOGP(DTRX, LOGL_ERROR, "Illegal FN %u\n", fn); + return -EINVAL; + } + + LOGP(DTRX, LOGL_DEBUG, "RX burst tn=%u fn=%u rssi=%d toa256=%d\n", + tn, fn, rssi, toa256); + +#ifdef TOA_RSSI_DEBUG + char deb[128]; + + sprintf(deb, "| 0 " + " | rssi=%4d toa=%5d fn=%u", rssi, toa256, fn); + deb[1 + (128 + rssi) / 4] = '*'; + fprintf(stderr, "%s\n", deb); +#endif + + /* feed received burst into scheduler code */ + trx_sched_ul_burst(&l1h->l1s, tn, fn, bits, burst_len, rssi, toa256); + + return 0; +} + +/*! Send burst data for given FN/timeslot to TRX + * \param[inout] l1h TRX Layer1 handle referring to TX + * \param[in] tn Timeslot Number (0..7) + * \param[in] fn GSM Frame Number + * \param[in] pwr Transmit Power to use + * \param[in] bits Unpacked bits to be transmitted + * \param[in] nbits Number of \a bits + * \returns 0 on success; negative on error */ +int trx_if_send_burst(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, uint8_t pwr, + const ubit_t *bits, uint16_t nbits) +{ + uint8_t buf[TRX_MAX_BURST_LEN]; + + if ((nbits != GSM_BURST_LEN) && (nbits != EGPRS_BURST_LEN)) { + LOGP(DTRX, LOGL_ERROR, "Tx burst length %u invalid\n", nbits); + return -1; + } + + LOGP(DTRX, LOGL_DEBUG, "TX burst tn=%u fn=%u pwr=%u\n", tn, fn, pwr); + + buf[0] = tn; + buf[1] = (fn >> 24) & 0xff; + buf[2] = (fn >> 16) & 0xff; + buf[3] = (fn >> 8) & 0xff; + buf[4] = (fn >> 0) & 0xff; + buf[5] = pwr; + + /* copy ubits {0,1} */ + memcpy(buf + 6, bits, nbits); + + /* we must be sure that we have clock, and we have sent all control + * data */ + if (transceiver_available && llist_empty(&l1h->trx_ctrl_list)) { + send(l1h->trx_ofd_data.fd, buf, nbits + 6, 0); + } else + LOGP(DTRX, LOGL_DEBUG, "Ignoring TX data, transceiver " + "offline.\n"); + + return 0; +} + + +/* + * open/close + */ + +/*! flush (delete) all pending control messages */ +void trx_if_flush(struct trx_l1h *l1h) +{ + struct trx_ctrl_msg *tcm; + + /* free ctrl message list */ + while (!llist_empty(&l1h->trx_ctrl_list)) { + tcm = llist_entry(l1h->trx_ctrl_list.next, struct trx_ctrl_msg, + list); + llist_del(&tcm->list); + talloc_free(tcm); + } + talloc_free(l1h->last_acked); +} + +/*! close the TRX for given handle (data + control socket) */ +void trx_if_close(struct trx_l1h *l1h) +{ + struct phy_instance *pinst = l1h->phy_inst; + LOGP(DTRX, LOGL_NOTICE, "Close transceiver for %s\n", + phy_instance_name(pinst)); + + trx_if_flush(l1h); + + /* close sockets */ + trx_udp_close(&l1h->trx_ofd_ctrl); + trx_udp_close(&l1h->trx_ofd_data); +} + +/*! compute UDP port number used for TRX protocol */ +static uint16_t compute_port(struct phy_instance *pinst, int remote, int is_data) +{ + struct phy_link *plink = pinst->phy_link; + uint16_t inc = 1; + + if (is_data) + inc = 2; + + if (remote) + return plink->u.osmotrx.base_port_remote + (pinst->num << 1) + inc; + else + return plink->u.osmotrx.base_port_local + (pinst->num << 1) + inc; +} + +/*! open a TRX interface. creates contro + data sockets */ +static int trx_if_open(struct trx_l1h *l1h) +{ + struct phy_instance *pinst = l1h->phy_inst; + struct phy_link *plink = pinst->phy_link; + int rc; + + LOGP(DTRX, LOGL_NOTICE, "Open transceiver for %s\n", + phy_instance_name(pinst)); + + /* initialize ctrl queue */ + INIT_LLIST_HEAD(&l1h->trx_ctrl_list); + + /* open sockets */ + rc = trx_udp_open(l1h, &l1h->trx_ofd_ctrl, + plink->u.osmotrx.local_ip, + compute_port(pinst, 0, 0), + plink->u.osmotrx.remote_ip, + compute_port(pinst, 1, 0), trx_ctrl_read_cb); + if (rc < 0) + goto err; + rc = trx_udp_open(l1h, &l1h->trx_ofd_data, + plink->u.osmotrx.local_ip, + compute_port(pinst, 0, 1), + plink->u.osmotrx.remote_ip, + compute_port(pinst, 1, 1), trx_data_read_cb); + if (rc < 0) + goto err; + + /* enable all slots */ + l1h->config.slotmask = 0xff; + + /* FIXME: why was this only for TRX0 ? */ + //if (l1h->trx->nr == 0) + trx_if_cmd_poweroff(l1h); + + return 0; + +err: + trx_if_close(l1h); + return rc; +} + +/*! close the control + burst data sockets for one phy_instance */ +static void trx_phy_inst_close(struct phy_instance *pinst) +{ + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + trx_if_close(l1h); + trx_sched_exit(&l1h->l1s); +} + +/*! open the control + burst data sockets for one phy_instance */ +static int trx_phy_inst_open(struct phy_instance *pinst) +{ + struct trx_l1h *l1h; + int rc; + + l1h = pinst->u.osmotrx.hdl; + if (!l1h) + return -EINVAL; + + rc = trx_sched_init(&l1h->l1s, pinst->trx); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "Cannot initialize scheduler for phy " + "instance %d\n", pinst->num); + return -EIO; + } + + rc = trx_if_open(l1h); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "Cannot open TRX interface for phy " + "instance %d\n", pinst->num); + trx_phy_inst_close(pinst); + return -EIO; + } + + return 0; +} + +/*! open the PHY link using TRX protocol */ +int bts_model_phy_link_open(struct phy_link *plink) +{ + struct phy_instance *pinst; + int rc; + + phy_link_state_set(plink, PHY_LINK_CONNECTING); + + /* open the shared/common clock socket */ + rc = trx_udp_open(plink, &plink->u.osmotrx.trx_ofd_clk, + plink->u.osmotrx.local_ip, + plink->u.osmotrx.base_port_local, + plink->u.osmotrx.remote_ip, + plink->u.osmotrx.base_port_remote, + trx_clk_read_cb); + if (rc < 0) { + phy_link_state_set(plink, PHY_LINK_SHUTDOWN); + return -1; + } + + /* open the individual instances with their ctrl+data sockets */ + llist_for_each_entry(pinst, &plink->instances, list) { + if (trx_phy_inst_open(pinst) < 0) + goto cleanup; + } + /* FIXME: is there better way to check/report TRX availability? */ + transceiver_available = 1; + phy_link_state_set(plink, PHY_LINK_CONNECTED); + return 0; + +cleanup: + phy_link_state_set(plink, PHY_LINK_SHUTDOWN); + llist_for_each_entry(pinst, &plink->instances, list) { + if (pinst->u.osmotrx.hdl) { + trx_if_close(pinst->u.osmotrx.hdl); + pinst->u.osmotrx.hdl = NULL; + } + } + trx_udp_close(&plink->u.osmotrx.trx_ofd_clk); + return -1; +} + +/*! determine if the TRX for given handle is powered up */ +int trx_if_powered(struct trx_l1h *l1h) +{ + return l1h->config.poweron; +} diff --git a/src/osmo-bts-trx/trx_if.h b/src/osmo-bts-trx/trx_if.h new file mode 100644 index 0000000..b161044 --- /dev/null +++ b/src/osmo-bts-trx/trx_if.h @@ -0,0 +1,34 @@ +#ifndef TRX_IF_H +#define TRX_IF_H + +extern int transceiver_available; + +struct trx_l1h; + +struct trx_ctrl_msg { + struct llist_head list; + char cmd[28]; + char params[100]; + int cmd_len; + int params_len; + int critical; +}; + +int trx_if_cmd_poweroff(struct trx_l1h *l1h); +int trx_if_cmd_poweron(struct trx_l1h *l1h); +int trx_if_cmd_settsc(struct trx_l1h *l1h, uint8_t tsc); +int trx_if_cmd_setbsic(struct trx_l1h *l1h, uint8_t bsic); +int trx_if_cmd_setrxgain(struct trx_l1h *l1h, int db); +int trx_if_cmd_setpower(struct trx_l1h *l1h, int db); +int trx_if_cmd_setmaxdly(struct trx_l1h *l1h, int dly); +int trx_if_cmd_setmaxdlynb(struct trx_l1h *l1h, int dly); +int trx_if_cmd_setslot(struct trx_l1h *l1h, uint8_t tn, uint8_t type); +int trx_if_cmd_rxtune(struct trx_l1h *l1h, uint16_t arfcn); +int trx_if_cmd_txtune(struct trx_l1h *l1h, uint16_t arfcn); +int trx_if_cmd_handover(struct trx_l1h *l1h, uint8_t tn, uint8_t ss); +int trx_if_cmd_nohandover(struct trx_l1h *l1h, uint8_t tn, uint8_t ss); +int trx_if_send_burst(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, uint8_t pwr, + const ubit_t *bits, uint16_t nbits); +int trx_if_powered(struct trx_l1h *l1h); + +#endif /* TRX_IF_H */ diff --git a/src/osmo-bts-trx/trx_vty.c b/src/osmo-bts-trx/trx_vty.c new file mode 100644 index 0000000..1dfc617 --- /dev/null +++ b/src/osmo-bts-trx/trx_vty.c @@ -0,0 +1,603 @@ +/* VTY interface for sysmoBTS */ + +/* (C) 2013 by Andreas Eversberg + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "l1_if.h" +#include "trx_if.h" +#include "loops.h" + +#define OSMOTRX_STR "OsmoTRX Transceiver configuration\n" + +static struct gsm_bts *vty_bts; + +DEFUN(show_transceiver, show_transceiver_cmd, "show transceiver", + SHOW_STR "Display information about transceivers\n") +{ + struct gsm_bts *bts = vty_bts; + struct gsm_bts_trx *trx; + struct trx_l1h *l1h; + + if (!transceiver_available) { + vty_out(vty, "transceiver is not connected%s", VTY_NEWLINE); + } else { + vty_out(vty, "transceiver is connected%s", VTY_NEWLINE); + } + + llist_for_each_entry(trx, &bts->trx_list, list) { + struct phy_instance *pinst = trx_phy_instance(trx); + l1h = pinst->u.osmotrx.hdl; + vty_out(vty, "TRX %d%s", trx->nr, VTY_NEWLINE); + vty_out(vty, " %s%s", + (l1h->config.poweron) ? "poweron":"poweroff", + VTY_NEWLINE); + if (l1h->config.arfcn_valid) + vty_out(vty, " arfcn : %d%s%s", + (l1h->config.arfcn & ~ARFCN_PCS), + (l1h->config.arfcn & ARFCN_PCS) ? " (PCS)" : "", + VTY_NEWLINE); + else + vty_out(vty, " arfcn : undefined%s", VTY_NEWLINE); + if (l1h->config.tsc_valid) + vty_out(vty, " tsc : %d%s", l1h->config.tsc, + VTY_NEWLINE); + else + vty_out(vty, " tsc : undefined%s", VTY_NEWLINE); + if (l1h->config.bsic_valid) + vty_out(vty, " bsic : %d%s", l1h->config.bsic, + VTY_NEWLINE); + else + vty_out(vty, " bisc : undefined%s", VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + + +static void show_phy_inst_single(struct vty *vty, struct phy_instance *pinst) +{ + uint8_t tn; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + vty_out(vty, "PHY Instance %s%s", + phy_instance_name(pinst), VTY_NEWLINE); + + if (l1h->config.rxgain_valid) + vty_out(vty, " rx-gain : %d dB%s", + l1h->config.rxgain, VTY_NEWLINE); + else + vty_out(vty, " rx-gain : undefined%s", VTY_NEWLINE); + if (l1h->config.power_valid) + vty_out(vty, " tx-attenuation : %d dB%s", + l1h->config.power, VTY_NEWLINE); + else + vty_out(vty, " tx-attenuation : undefined%s", VTY_NEWLINE); + if (l1h->config.maxdly_valid) + vty_out(vty, " maxdly : %d%s", l1h->config.maxdly, + VTY_NEWLINE); + else + vty_out(vty, " maxdly : undefined%s", VTY_NEWLINE); + if (l1h->config.maxdlynb_valid) + vty_out(vty, " maxdlynb : %d%s", l1h->config.maxdlynb, + VTY_NEWLINE); + else + vty_out(vty, " maxdlynb : undefined%s", VTY_NEWLINE); + for (tn = 0; tn < TRX_NR_TS; tn++) { + if (!((1 << tn) & l1h->config.slotmask)) + vty_out(vty, " slot #%d: unsupported%s", tn, + VTY_NEWLINE); + else if (l1h->config.slottype_valid[tn]) + vty_out(vty, " slot #%d: type %d%s", tn, + l1h->config.slottype[tn], + VTY_NEWLINE); + else + vty_out(vty, " slot #%d: undefined%s", tn, + VTY_NEWLINE); + } +} + +static void show_phy_single(struct vty *vty, struct phy_link *plink) +{ + struct phy_instance *pinst; + + vty_out(vty, "PHY %u%s", plink->num, VTY_NEWLINE); + + llist_for_each_entry(pinst, &plink->instances, list) + show_phy_inst_single(vty, pinst); +} + +DEFUN(show_phy, show_phy_cmd, "show phy", + SHOW_STR "Display information about the available PHYs") +{ + int i; + + for (i = 0; i < 255; i++) { + struct phy_link *plink = phy_link_by_num(i); + if (!plink) + break; + show_phy_single(vty, plink); + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_ms_power_loop, cfg_phy_ms_power_loop_cmd, + "osmotrx ms-power-loop <-127-127>", OSMOTRX_STR + "Enable MS power control loop\nTarget RSSI value (transceiver specific, " + "should be 6dB or more above noise floor)\n") +{ + struct phy_link *plink = vty->index; + + plink->u.osmotrx.trx_target_rssi = atoi(argv[0]); + plink->u.osmotrx.trx_ms_power_loop = true; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_no_ms_power_loop, cfg_phy_no_ms_power_loop_cmd, + "no osmotrx ms-power-loop", + NO_STR OSMOTRX_STR "Disable MS power control loop\n") +{ + struct phy_link *plink = vty->index; + + plink->u.osmotrx.trx_ms_power_loop = false; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_timing_advance_loop, cfg_phy_timing_advance_loop_cmd, + "osmotrx timing-advance-loop", OSMOTRX_STR + "Enable timing advance control loop\n") +{ + struct phy_link *plink = vty->index; + + plink->u.osmotrx.trx_ta_loop = true; + + return CMD_SUCCESS; +} +DEFUN(cfg_phy_no_timing_advance_loop, cfg_phy_no_timing_advance_loop_cmd, + "no osmotrx timing-advance-loop", + NO_STR OSMOTRX_STR "Disable timing advance control loop\n") +{ + struct phy_link *plink = vty->index; + + plink->u.osmotrx.trx_ta_loop = false; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phyinst_maxdly, cfg_phyinst_maxdly_cmd, + "osmotrx maxdly <0-31>", + OSMOTRX_STR + "Set the maximum acceptable delay of an Access Burst (in GSM symbols)." + " Access Burst is the first burst a mobile transmits in order to establish" + " a connection and it is used to estimate Timing Advance (TA) which is" + " then applied to Normal Bursts to compensate for signal delay due to" + " distance. So changing this setting effectively changes maximum range of" + " the cell, because if we receive an Access Burst with a delay higher than" + " this value, it will be ignored and connection is dropped.\n" + "GSM symbols (approx. 1.1km per symbol)\n") +{ + struct phy_instance *pinst = vty->index; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + l1h->config.maxdly = atoi(argv[0]); + l1h->config.maxdly_valid = 1; + l1h->config.maxdly_sent = 0; + l1if_provision_transceiver_trx(l1h); + + return CMD_SUCCESS; +} + + +DEFUN(cfg_phyinst_maxdlynb, cfg_phyinst_maxdlynb_cmd, + "osmotrx maxdlynb <0-31>", + OSMOTRX_STR + "Set the maximum acceptable delay of a Normal Burst (in GSM symbols)." + " USE FOR TESTING ONLY, DON'T CHANGE IN PRODUCTION USE!" + " During normal operation, Normal Bursts delay are controled by a Timing" + " Advance control loop and thus Normal Bursts arrive to a BTS with no more" + " than a couple GSM symbols, which is already taken into account in osmo-trx." + " So changing this setting will have no effect in production installations" + " except increasing osmo-trx CPU load. This setting is only useful when" + " testing with a transmitter which can't precisely synchronize to the BTS" + " downlink signal, like e.g. R&S CMD57.\n" + "GSM symbols (approx. 1.1km per symbol)\n") +{ + struct phy_instance *pinst = vty->index; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + l1h->config.maxdlynb = atoi(argv[0]); + l1h->config.maxdlynb_valid = 1; + l1h->config.maxdlynb_sent = 0; + l1if_provision_transceiver_trx(l1h); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phyinst_slotmask, cfg_phyinst_slotmask_cmd, + "slotmask (1|0) (1|0) (1|0) (1|0) (1|0) (1|0) (1|0) (1|0)", + "Set the supported slots\n" + "TS0 supported\nTS0 unsupported\nTS1 supported\nTS1 unsupported\n" + "TS2 supported\nTS2 unsupported\nTS3 supported\nTS3 unsupported\n" + "TS4 supported\nTS4 unsupported\nTS5 supported\nTS5 unsupported\n" + "TS6 supported\nTS6 unsupported\nTS7 supported\nTS7 unsupported\n") +{ + struct phy_instance *pinst = vty->index; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + uint8_t tn; + + l1h->config.slotmask = 0; + for (tn = 0; tn < TRX_NR_TS; tn++) + if (argv[tn][0] == '1') + l1h->config.slotmask |= (1 << tn); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phyinst_power_on, cfg_phyinst_power_on_cmd, + "osmotrx power (on|off)", + OSMOTRX_STR + "Change TRX state\n" + "Turn it ON or OFF\n") +{ + struct phy_instance *pinst = vty->index; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + if (strcmp(argv[0], "on")) + vty_out(vty, "OFF: %d%s", trx_if_cmd_poweroff(l1h), VTY_NEWLINE); + else { + vty_out(vty, "ON: %d%s", trx_if_cmd_poweron(l1h), VTY_NEWLINE); + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_fn_advance, cfg_phy_fn_advance_cmd, + "osmotrx fn-advance <0-30>", + OSMOTRX_STR + "Set the number of frames to be transmitted to transceiver in advance " + "of current FN\n" + "Advance in frames\n") +{ + struct phy_link *plink = vty->index; + + plink->u.osmotrx.clock_advance = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_rts_advance, cfg_phy_rts_advance_cmd, + "osmotrx rts-advance <0-30>", + OSMOTRX_STR + "Set the number of frames to be requested (PCU) in advance of current " + "FN. Do not change this, unless you have a good reason!\n" + "Advance in frames\n") +{ + struct phy_link *plink = vty->index; + + plink->u.osmotrx.rts_advance = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phyinst_rxgain, cfg_phyinst_rxgain_cmd, + "osmotrx rx-gain <0-50>", + OSMOTRX_STR + "Set the receiver gain in dB\n" + "Gain in dB\n") +{ + struct phy_instance *pinst = vty->index; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + l1h->config.rxgain = atoi(argv[0]); + l1h->config.rxgain_valid = 1; + l1h->config.rxgain_sent = 0; + l1if_provision_transceiver_trx(l1h); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phyinst_tx_atten, cfg_phyinst_tx_atten_cmd, + "osmotrx tx-attenuation <0-50>", + OSMOTRX_STR + "Set the transmitter attenuation\n" + "Fixed attenuation in dB, overriding OML\n") +{ + struct phy_instance *pinst = vty->index; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + l1h->config.power = atoi(argv[0]); + l1h->config.power_oml = 0; + l1h->config.power_valid = 1; + l1h->config.power_sent = 0; + l1if_provision_transceiver_trx(l1h); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phyinst_tx_atten_oml, cfg_phyinst_tx_atten_oml_cmd, + "osmotrx tx-attenuation oml", + OSMOTRX_STR + "Set the transmitter attenuation\n" + "Use NM_ATT_RF_MAXPOWR_R (max power reduction) from BSC via OML\n") +{ + struct phy_instance *pinst = vty->index; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + l1h->config.power_oml = 1; + l1h->config.power_valid = 1; + l1h->config.power_sent = 0; + l1if_provision_transceiver_trx(l1h); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phyinst_no_rxgain, cfg_phyinst_no_rxgain_cmd, + "no osmotrx rx-gain", + NO_STR OSMOTRX_STR "Unset the receiver gain in dB\n") +{ + struct phy_instance *pinst = vty->index; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + l1h->config.rxgain_valid = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phyinst_no_tx_atten, cfg_phyinst_no_tx_atten_cmd, + "no osmotrx tx-attenuation", + NO_STR OSMOTRX_STR "Unset the transmitter attenuation\n") +{ + struct phy_instance *pinst = vty->index; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + l1h->config.power_valid = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phyinst_no_maxdly, cfg_phyinst_no_maxdly_cmd, + "no osmotrx maxdly", + NO_STR OSMOTRX_STR + "Unset the maximum delay of GSM symbols\n") +{ + struct phy_instance *pinst = vty->index; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + l1h->config.maxdly_valid = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phyinst_no_maxdlynb, cfg_phyinst_no_maxdlynb_cmd, + "no osmotrx maxdlynb", + NO_STR OSMOTRX_STR + "Unset the maximum delay of GSM symbols\n") +{ + struct phy_instance *pinst = vty->index; + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + l1h->config.maxdlynb_valid = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_transc_ip, cfg_phy_transc_ip_cmd, + "osmotrx ip HOST", + OSMOTRX_STR + "Set local and remote IP address\n" + "IP address (for both OsmoBtsTrx and OsmoTRX)\n") +{ + struct phy_link *plink = vty->index; + + osmo_talloc_replace_string(plink, &plink->u.osmotrx.local_ip, argv[0]); + osmo_talloc_replace_string(plink, &plink->u.osmotrx.remote_ip, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_osmotrx_ip, cfg_phy_osmotrx_ip_cmd, + "osmotrx ip (local|remote) A.B.C.D", + OSMOTRX_STR + "Set IP address\n" "Local IP address (BTS)\n" + "Remote IP address (OsmoTRX)\n" "IP address\n") +{ + struct phy_link *plink = vty->index; + + if (!strcmp(argv[0], "local")) + osmo_talloc_replace_string(plink, &plink->u.osmotrx.local_ip, argv[1]); + else if (!strcmp(argv[0], "remote")) + osmo_talloc_replace_string(plink, &plink->u.osmotrx.remote_ip, argv[1]); + else + return CMD_WARNING; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_base_port, cfg_phy_base_port_cmd, + "osmotrx base-port (local|remote) <0-65535>", + OSMOTRX_STR "Set base UDP port number\n" "Local UDP port\n" + "Remote UDP port\n" "UDP base port number\n") +{ + struct phy_link *plink = vty->index; + + if (!strcmp(argv[0], "local")) + plink->u.osmotrx.base_port_local = atoi(argv[1]); + else + plink->u.osmotrx.base_port_remote = atoi(argv[1]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_setbsic, cfg_phy_setbsic_cmd, + "osmotrx legacy-setbsic", OSMOTRX_STR + "Use SETBSIC to configure transceiver (use ONLY with OpenBTS Transceiver!)\n") +{ + struct phy_link *plink = vty->index; + plink->u.osmotrx.use_legacy_setbsic = true; + + vty_out(vty, "%% You have enabled SETBSIC, which is not supported by OsmoTRX " + "but only useful if you want to interface with legacy OpenBTS Transceivers%s", + VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_no_setbsic, cfg_phy_no_setbsic_cmd, + "no osmotrx legacy-setbsic", + NO_STR OSMOTRX_STR "Disable Legacy SETBSIC to configure transceiver\n") +{ + struct phy_link *plink = vty->index; + plink->u.osmotrx.use_legacy_setbsic = false; + + return CMD_SUCCESS; +} + +void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink) +{ + if (plink->u.osmotrx.local_ip) + vty_out(vty, " osmotrx ip local %s%s", + plink->u.osmotrx.local_ip, VTY_NEWLINE); + if (plink->u.osmotrx.remote_ip) + vty_out(vty, " osmotrx ip remote %s%s", + plink->u.osmotrx.remote_ip, VTY_NEWLINE); + + if (plink->u.osmotrx.trx_ms_power_loop) + vty_out(vty, " osmotrx ms-power-loop %d%s", plink->u.osmotrx.trx_target_rssi, VTY_NEWLINE); + else + vty_out(vty, " no osmotrx ms-power-loop%s", VTY_NEWLINE); + vty_out(vty, " %sosmotrx timing-advance-loop%s", (plink->u.osmotrx.trx_ta_loop) ? "" : "no ", VTY_NEWLINE); + + if (plink->u.osmotrx.base_port_local) + vty_out(vty, " osmotrx base-port local %"PRIu16"%s", + plink->u.osmotrx.base_port_local, VTY_NEWLINE); + if (plink->u.osmotrx.base_port_remote) + vty_out(vty, " osmotrx base-port remote %"PRIu16"%s", + plink->u.osmotrx.base_port_remote, VTY_NEWLINE); + + vty_out(vty, " osmotrx fn-advance %d%s", + plink->u.osmotrx.clock_advance, VTY_NEWLINE); + vty_out(vty, " osmotrx rts-advance %d%s", + plink->u.osmotrx.rts_advance, VTY_NEWLINE); + + if (plink->u.osmotrx.use_legacy_setbsic) + vty_out(vty, " osmotrx legacy-setbsic%s", VTY_NEWLINE); +} + +void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst) +{ + struct trx_l1h *l1h = pinst->u.osmotrx.hdl; + + if (l1h->config.rxgain_valid) + vty_out(vty, " osmotrx rx-gain %d%s", + l1h->config.rxgain, VTY_NEWLINE); + if (l1h->config.power_valid) { + if (l1h->config.power_oml) + vty_out(vty, " osmotrx tx-attenuation oml%s", VTY_NEWLINE); + else + vty_out(vty, " osmotrx tx-attenuation %d%s", + l1h->config.power, VTY_NEWLINE); + } + if (l1h->config.maxdly_valid) + vty_out(vty, " osmotrx maxdly %d%s", l1h->config.maxdly, VTY_NEWLINE); + if (l1h->config.maxdlynb_valid) + vty_out(vty, " osmotrx maxdlynb %d%s", l1h->config.maxdlynb, VTY_NEWLINE); + if (l1h->config.slotmask != 0xff) + vty_out(vty, " slotmask %d %d %d %d %d %d %d %d%s", + l1h->config.slotmask & 1, + (l1h->config.slotmask >> 1) & 1, + (l1h->config.slotmask >> 2) & 1, + (l1h->config.slotmask >> 3) & 1, + (l1h->config.slotmask >> 4) & 1, + (l1h->config.slotmask >> 5) & 1, + (l1h->config.slotmask >> 6) & 1, + l1h->config.slotmask >> 7, + VTY_NEWLINE); +} + +void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts) +{ +} + +void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx) +{ +} + +int bts_model_vty_init(struct gsm_bts *bts) +{ + vty_bts = bts; + + install_element_ve(&show_transceiver_cmd); + install_element_ve(&show_phy_cmd); + + install_element(PHY_NODE, &cfg_phy_ms_power_loop_cmd); + install_element(PHY_NODE, &cfg_phy_no_ms_power_loop_cmd); + install_element(PHY_NODE, &cfg_phy_timing_advance_loop_cmd); + install_element(PHY_NODE, &cfg_phy_no_timing_advance_loop_cmd); + install_element(PHY_NODE, &cfg_phy_base_port_cmd); + install_element(PHY_NODE, &cfg_phy_fn_advance_cmd); + install_element(PHY_NODE, &cfg_phy_rts_advance_cmd); + install_element(PHY_NODE, &cfg_phy_transc_ip_cmd); + install_element(PHY_NODE, &cfg_phy_osmotrx_ip_cmd); + install_element(PHY_NODE, &cfg_phy_setbsic_cmd); + install_element(PHY_NODE, &cfg_phy_no_setbsic_cmd); + + install_element(PHY_INST_NODE, &cfg_phyinst_rxgain_cmd); + install_element(PHY_INST_NODE, &cfg_phyinst_tx_atten_cmd); + install_element(PHY_INST_NODE, &cfg_phyinst_tx_atten_oml_cmd); + install_element(PHY_INST_NODE, &cfg_phyinst_no_rxgain_cmd); + install_element(PHY_INST_NODE, &cfg_phyinst_no_tx_atten_cmd); + install_element(PHY_INST_NODE, &cfg_phyinst_slotmask_cmd); + install_element(PHY_INST_NODE, &cfg_phyinst_power_on_cmd); + install_element(PHY_INST_NODE, &cfg_phyinst_maxdly_cmd); + install_element(PHY_INST_NODE, &cfg_phyinst_no_maxdly_cmd); + install_element(PHY_INST_NODE, &cfg_phyinst_maxdlynb_cmd); + install_element(PHY_INST_NODE, &cfg_phyinst_no_maxdlynb_cmd); + + return 0; +} + +int bts_model_ctrl_cmds_install(struct gsm_bts *bts) +{ + return 0; +} diff --git a/src/osmo-bts-virtual/Makefile.am b/src/osmo-bts-virtual/Makefile.am new file mode 100644 index 0000000..eeb76aa --- /dev/null +++ b/src/osmo-bts-virtual/Makefile.am @@ -0,0 +1,10 @@ +AM_CFLAGS = -Wall -fno-strict-aliasing $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS) $(ORTP_CFLAGS) +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -Iinclude +COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) $(ORTP_LIBS) -ldl + +noinst_HEADERS = l1_if.h osmo_mcast_sock.h virtual_um.h + +bin_PROGRAMS = osmo-bts-virtual + +osmo_bts_virtual_SOURCES = main.c bts_model.c virtualbts_vty.c scheduler_virtbts.c l1_if.c virtual_um.c osmo_mcast_sock.c +osmo_bts_virtual_LDADD = $(top_builddir)/src/common/libl1sched.a $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) diff --git a/src/osmo-bts-virtual/bts_model.c b/src/osmo-bts-virtual/bts_model.c new file mode 100644 index 0000000..b971af5 --- /dev/null +++ b/src/osmo-bts-virtual/bts_model.c @@ -0,0 +1,176 @@ +/* (C) 2015 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* TODO: check if dummy method is sufficient, else implement */ +int bts_model_lchan_deactivate(struct gsm_lchan *lchan) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return -1; +} + +/* TODO: check if dummy method is sufficient, else implement */ +int osmo_amr_rtp_dec(const uint8_t *rtppayload, int payload_len, uint8_t *cmr, + int8_t *cmi, enum osmo_amr_type *ft, enum osmo_amr_quality *bfi, int8_t *sti) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return -1; +} + +int bts_model_trx_close(struct gsm_bts_trx *trx) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return 0; +} + +int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return 0; +} + +int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, + struct tlv_parsed *old_attr, struct tlv_parsed *new_attr, + void *obj) +{ + return 0; +} + +static uint8_t vbts_set_bts(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + uint8_t tn; + + llist_for_each_entry(trx, &bts->trx_list, list) { + oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK); + oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK); + + for (tn = 0; tn < TRX_NR_TS; tn++) + oml_mo_state_chg(&trx->ts[tn].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY); + + /* report availability of trx to the bts. this will trigger the rsl connection */ + oml_mo_tx_sw_act_rep(&trx->mo); + oml_mo_tx_sw_act_rep(&trx->bb_transc.mo); + } + return 0; +} + +static uint8_t vbts_set_trx(struct gsm_bts_trx *trx) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return 0; +} + +static uint8_t vbts_set_ts(struct gsm_bts_trx_ts *ts) +{ + struct phy_instance *pinst = trx_phy_instance(ts->trx); + int rc; + + rc = trx_sched_set_pchan(&pinst->u.virt.sched, ts->nr, ts->pchan); + if (rc) + return NM_NACK_RES_NOTAVAIL; + + return 0; +} + +int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, + struct tlv_parsed *new_attr, int kind, void *obj) +{ + struct abis_om_fom_hdr *foh = msgb_l3(msg); + int cause = 0; + + switch (foh->msg_type) { + case NM_MT_SET_BTS_ATTR: + cause = vbts_set_bts(obj); + break; + case NM_MT_SET_RADIO_ATTR: + cause = vbts_set_trx(obj); + break; + case NM_MT_SET_CHAN_ATTR: + cause = vbts_set_ts(obj); + break; + } + return oml_fom_ack_nack(msg, cause); +} + +/* MO: TS 12.21 Managed Object */ +int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj) +{ + int rc; + + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + case NM_OC_CHANNEL: + case NM_OC_SITE_MANAGER: + case NM_OC_BASEB_TRANSC: + case NM_OC_BTS: + case NM_OC_GPRS_NSE: + case NM_OC_GPRS_CELL: + case NM_OC_GPRS_NSVC: + oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); + rc = oml_mo_opstart_ack(mo); + break; + default: + rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP); + } + return rc; +} + +int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo, + void *obj, uint8_t adm_state) +{ + mo->nm_state.administrative = adm_state; + return oml_mo_statechg_ack(mo); +} + +int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return 0; +} + + +int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return 0; +} + +int bts_model_ctrl_cmds_install(struct gsm_bts *bts) +{ + LOGP(DL1C, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return 0; +} diff --git a/src/osmo-bts-virtual/l1_if.c b/src/osmo-bts-virtual/l1_if.c new file mode 100644 index 0000000..d0c368e --- /dev/null +++ b/src/osmo-bts-virtual/l1_if.c @@ -0,0 +1,461 @@ +/* Virtual BTS layer 1 primitive handling and interface + * + * Copyright (C) 2015-2017 Harald Welte + * Copyright (C) 2017 Sebastian Stumpf + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "virtual_um.h" + +extern int vbts_sched_start(struct gsm_bts *bts); + +static struct phy_instance *phy_instance_by_arfcn(struct phy_link *plink, uint16_t arfcn) +{ + struct phy_instance *pinst; + + llist_for_each_entry(pinst, &plink->instances, list) { + if (pinst->trx && pinst->trx->arfcn == arfcn) + return pinst; + } + + return NULL; +} + +static int l1if_process_meas_res(struct gsm_bts_trx *trx, uint8_t tn, uint32_t fn, uint8_t chan_nr, + int n_errors, int n_bits_total, float rssi, float toa); +/** + * Callback to handle incoming messages from the MS. + * The incoming message should be GSM_TAP encapsulated. + * TODO: implement all channels + */ +static void virt_um_rcv_cb(struct virt_um_inst *vui, struct msgb *msg) +{ + struct phy_link *plink = (struct phy_link *)vui->priv; + struct phy_instance *pinst; + if (!msg) { + pinst = phy_instance_by_num(plink, 0); + bts_shutdown(pinst->trx->bts, "VirtPHY read socket died\n"); + return; + } + + struct gsmtap_hdr *gh = msgb_l1(msg); + uint32_t fn = ntohl(gh->frame_number); /* frame number of the rcv msg */ + uint16_t arfcn = ntohs(gh->arfcn); /* arfcn of the cell we currently camp on */ + uint8_t gsmtap_chantype = gh->sub_type; /* gsmtap channel type */ + uint8_t signal_dbm = gh->signal_dbm; /* signal strength in dBm */ + //uint8_t snr = gh->snr_db; /* signal noise ratio in dB */ + uint8_t subslot = gh->sub_slot; /* multiframe subslot to send msg in (tch -> 0-26, bcch/ccch -> 0-51) */ + uint8_t timeslot = gh->timeslot; /* tdma timeslot to send in (0-7) */ + uint8_t rsl_chantype; /* rsl chan type (8.58, 9.3.1) */ + uint8_t link_id; /* rsl link id tells if this is an ssociated or dedicated link */ + uint8_t chan_nr; /* encoded rsl channel type, timeslot and mf subslot */ + struct osmo_phsap_prim l1sap; + + memset(&l1sap, 0, sizeof(l1sap)); + /* get rid of l1 gsmtap hdr */ + msg->l2h = msgb_pull(msg, sizeof(*gh)); + + /* convert gsmtap chan to RSL chan and link id */ + chantype_gsmtap2rsl(gsmtap_chantype, &rsl_chantype, &link_id); + chan_nr = rsl_enc_chan_nr(rsl_chantype, subslot, timeslot); + + /* ... or not uplink */ + if (!(arfcn & GSMTAP_ARFCN_F_UPLINK)) { + LOGPFN(DL1P, LOGL_NOTICE, fn, "Ignoring incoming msg - no uplink flag\n"); + goto nomessage; + } + + /* Generally ignore all msgs that are either not received with the right ARFCN... */ + pinst = phy_instance_by_arfcn(plink, arfcn & GSMTAP_ARFCN_MASK); + if (!pinst) + goto nomessage; + + /* switch case with removed ACCH flag */ + switch ((gsmtap_chantype & ~GSMTAP_CHANNEL_ACCH) & 0xff) { + case GSMTAP_CHANNEL_RACH: + /* generate primitive for upper layer + * see 04.08 - 3.3.1.3.1: the IMMEDIATE_ASSIGNMENT coming back from the network has to be + * sent with the same ra reference as in the CHANNEL_REQUEST that was received */ + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, msg); + + l1sap.u.rach_ind.chan_nr = chan_nr; + /* TODO: 11bit RACH */ + l1sap.u.rach_ind.ra = msgb_pull_u8(msg); /* directly after gh hdr comes ra */ + l1sap.u.rach_ind.acc_delay = 0; /* probably not used in virt um */ + l1sap.u.rach_ind.is_11bit = 0; + l1sap.u.rach_ind.fn = fn; + /* we don't rally know which RACH bursrt type the virtual MS is using, as this field is not + * part of information present in the GSMTAP header. So we simply report all of them as 0 */ + l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_0; + break; + case GSMTAP_CHANNEL_TCH_F: + case GSMTAP_CHANNEL_TCH_H: +#if 0 + /* TODO: handle voice messages */ + if (!facch && ! tch_acch) { + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_TCH, PRIM_OP_INDICATION, msg); + } +#endif + case GSMTAP_CHANNEL_SDCCH4: + case GSMTAP_CHANNEL_SDCCH8: + case GSMTAP_CHANNEL_PACCH: + case GSMTAP_CHANNEL_PDCH: + case GSMTAP_CHANNEL_PTCCH: + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_PH_DATA, + PRIM_OP_INDICATION, msg); + l1sap.u.data.chan_nr = chan_nr; + l1sap.u.data.link_id = link_id; + l1sap.u.data.fn = fn; + l1sap.u.data.rssi = 0; /* Radio Signal Strength Indicator. Best -> 0 */ + l1sap.u.data.ber10k = 0; /* Bit Error Rate in 0.01%. Best -> 0 */ + l1sap.u.data.ta_offs_256bits = 0; /* Burst time of arrival in quarter bits. Probably used for Timing Advance calc. Best -> 0 */ + l1sap.u.data.lqual_cb = 10 * signal_dbm; /* Link quality in centiBel = 10 * dB. */ + l1sap.u.data.pdch_presence_info = PRES_INFO_BOTH; + l1if_process_meas_res(pinst->trx, timeslot, fn, chan_nr, 0, 0, 0, 0); + break; + case GSMTAP_CHANNEL_AGCH: + case GSMTAP_CHANNEL_PCH: + case GSMTAP_CHANNEL_BCCH: + LOGPFN(DL1P, LOGL_NOTICE, fn, "Ignore incoming msg - channel type downlink only!\n"); + goto nomessage; + case GSMTAP_CHANNEL_SDCCH: + case GSMTAP_CHANNEL_CCCH: + case GSMTAP_CHANNEL_CBCH51: + case GSMTAP_CHANNEL_CBCH52: + LOGPFN(DL1P, LOGL_NOTICE, fn, "Ignore incoming msg - channel type not supported!\n"); + goto nomessage; + default: + LOGPFN(DL1P, LOGL_NOTICE, fn, "Ignore incoming msg - channel type unknown\n"); + goto nomessage; + } + + /* forward primitive, lsap takes ownership of the msgb. */ + l1sap_up(pinst->trx, &l1sap); + DEBUGPFN(DL1P, fn, "Message forwarded to layer 2.\n"); + return; + +nomessage: + talloc_free(msg); +} + +/* called by common part once OML link is established */ +int bts_model_oml_estab(struct gsm_bts *bts) +{ + return 0; +} + +/* called by bts_main to initialize physical link */ +int bts_model_phy_link_open(struct phy_link *plink) +{ + struct phy_instance *pinst; + + //OSMO_ASSERT(plink->type == PHY_LINK_T_VIRTUAL); + + if (plink->u.virt.virt_um) + virt_um_destroy(plink->u.virt.virt_um); + + phy_link_state_set(plink, PHY_LINK_CONNECTING); + + plink->u.virt.virt_um = virt_um_init(plink, plink->u.virt.ms_mcast_group, plink->u.virt.ms_mcast_port, + plink->u.virt.bts_mcast_group, plink->u.virt.bts_mcast_port, + virt_um_rcv_cb); + if (!plink->u.virt.virt_um) { + phy_link_state_set(plink, PHY_LINK_SHUTDOWN); + return -1; + } + /* set back reference to plink */ + plink->u.virt.virt_um->priv = plink; + + /* iterate over list of PHY instances and initialize the scheduler */ + llist_for_each_entry(pinst, &plink->instances, list) { + trx_sched_init(&pinst->u.virt.sched, pinst->trx); + /* Only start the scheduler for the transceiver on C0. + * If we have multiple tranceivers, CCCH is always on C0 + * and has to be auto active */ + /* Other TRX are activated via OML by a PRIM_INFO_MODIFY + * / PRIM_INFO_ACTIVATE */ + if (pinst->trx && pinst->trx == pinst->trx->bts->c0) { + vbts_sched_start(pinst->trx->bts); + /* init lapdm layer 3 callback for the trx on timeslot 0 == BCCH */ + lchan_init_lapdm(&pinst->trx->ts[0].lchan[CCCH_LCHAN]); + /* FIXME: This is probably the wrong location to set the CCCH to active... the OML link def. needs to be reworked and fixed. */ + pinst->trx->ts[0].lchan[CCCH_LCHAN].rel_act_kind = LCHAN_REL_ACT_OML; + lchan_set_state(&pinst->trx->ts[0].lchan[CCCH_LCHAN], LCHAN_S_ACTIVE); + } + } + + /* this will automatically update the MO state of all associated TRX objects */ + phy_link_state_set(plink, PHY_LINK_CONNECTED); + + return 0; +} + + +/* + * primitive handling + */ + +/* enable ciphering */ +static int l1if_set_ciphering(struct gsm_lchan *lchan, uint8_t chan_nr, int downlink) +{ + struct gsm_bts_trx *trx = lchan->ts->trx; + struct phy_instance *pinst = trx_phy_instance(trx); + struct l1sched_trx *sched = &pinst->u.virt.sched; + + /* ciphering already enabled in both directions */ + if (lchan->ciph_state == LCHAN_CIPH_RXTX_CONF) + return -EINVAL; + + if (!downlink) { + /* set uplink */ + trx_sched_set_cipher(sched, chan_nr, 0, lchan->encr.alg_id - 1, + lchan->encr.key, lchan->encr.key_len); + lchan->ciph_state = LCHAN_CIPH_RX_CONF; + } else { + /* set downlink and also set uplink, if not already */ + if (lchan->ciph_state != LCHAN_CIPH_RX_CONF) { + trx_sched_set_cipher(sched, chan_nr, 0, + lchan->encr.alg_id - 1, lchan->encr.key, + lchan->encr.key_len); + } + trx_sched_set_cipher(sched, chan_nr, 1, lchan->encr.alg_id - 1, + lchan->encr.key, lchan->encr.key_len); + lchan->ciph_state = LCHAN_CIPH_RXTX_CONF; + } + + return 0; +} + +static int mph_info_chan_confirm(struct gsm_bts_trx *trx, uint8_t chan_nr, + enum osmo_mph_info_type type, uint8_t cause) +{ + struct osmo_phsap_prim l1sap; + + memset(&l1sap, 0, sizeof(l1sap)); + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_CONFIRM, + NULL); + l1sap.u.info.type = type; + l1sap.u.info.u.act_cnf.chan_nr = chan_nr; + l1sap.u.info.u.act_cnf.cause = cause; + + return l1sap_up(trx, &l1sap); +} + +int l1if_mph_time_ind(struct gsm_bts *bts, uint32_t fn) +{ + struct osmo_phsap_prim l1sap; + + memset(&l1sap, 0, sizeof(l1sap)); + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, + PRIM_OP_INDICATION, NULL); + l1sap.u.info.type = PRIM_INFO_TIME; + l1sap.u.info.u.time_ind.fn = fn; + + if (!bts->c0) + return -EINVAL; + + return l1sap_up(bts->c0, &l1sap); +} + + +static void l1if_fill_meas_res(struct osmo_phsap_prim *l1sap, uint8_t chan_nr, float ta, + float ber, float rssi, uint32_t fn) +{ + memset(l1sap, 0, sizeof(*l1sap)); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_MPH_INFO, + PRIM_OP_INDICATION, NULL); + l1sap->u.info.type = PRIM_INFO_MEAS; + l1sap->u.info.u.meas_ind.chan_nr = chan_nr; + l1sap->u.info.u.meas_ind.ta_offs_256bits = (int16_t)(ta*4); + l1sap->u.info.u.meas_ind.ber10k = (unsigned int) (ber * 10000); + l1sap->u.info.u.meas_ind.inv_rssi = (uint8_t) (rssi * -1); + l1sap->u.info.u.meas_ind.fn = fn; +} + +static int l1if_process_meas_res(struct gsm_bts_trx *trx, uint8_t tn, uint32_t fn, uint8_t chan_nr, + int n_errors, int n_bits_total, float rssi, float toa) +{ + struct gsm_lchan *lchan = &trx->ts[tn].lchan[l1sap_chan2ss(chan_nr)]; + struct osmo_phsap_prim l1sap; + /* 100% BER is n_bits_total is 0 */ + float ber = n_bits_total==0 ? 1.0 : (float)n_errors / (float)n_bits_total; + + DEBUGPFN(DMEAS, fn, "RX L1 frame %s chan_nr=0x%02x MS pwr=%ddBm rssi=%.1f dBFS " + "ber=%.2f%% (%d/%d bits) L1_ta=%d rqd_ta=%d toa=%.2f\n", + gsm_lchan_name(lchan), chan_nr, ms_pwr_dbm(lchan->ts->trx->bts->band, lchan->ms_power), + rssi, ber*100, n_errors, n_bits_total, lchan->meas.l1_info[1], lchan->rqd_ta, toa); + + l1if_fill_meas_res(&l1sap, chan_nr, lchan->rqd_ta + toa, ber, rssi, fn); + + return l1sap_up(trx, &l1sap); +} + + + +/* primitive from common part */ +int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) +{ + struct phy_instance *pinst = trx_phy_instance(trx); + struct l1sched_trx *sched = &pinst->u.virt.sched; + struct msgb *msg = l1sap->oph.msg; + uint8_t chan_nr; + uint8_t tn, ss; + int rc = 0; + struct gsm_lchan *lchan; + + switch (OSMO_PRIM_HDR(&l1sap->oph)) { + case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST): + if (!msg) + break; + /* put data into scheduler's queue */ + return trx_sched_ph_data_req(sched, l1sap); + case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST): + if (!msg) + break; + /* put data into scheduler's queue */ + return trx_sched_tch_req(sched, l1sap); + case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST): + switch (l1sap->u.info.type) { + case PRIM_INFO_ACT_CIPH: + chan_nr = l1sap->u.info.u.ciph_req.chan_nr; + tn = L1SAP_CHAN2TS(chan_nr); + ss = l1sap_chan2ss(chan_nr); + lchan = &trx->ts[tn].lchan[ss]; + if (l1sap->u.info.u.ciph_req.uplink) + l1if_set_ciphering(lchan, chan_nr, 0); + if (l1sap->u.info.u.ciph_req.downlink) + l1if_set_ciphering(lchan, chan_nr, 1); + break; + case PRIM_INFO_ACTIVATE: + case PRIM_INFO_DEACTIVATE: + case PRIM_INFO_MODIFY: + chan_nr = l1sap->u.info.u.act_req.chan_nr; + tn = L1SAP_CHAN2TS(chan_nr); + ss = l1sap_chan2ss(chan_nr); + lchan = &trx->ts[tn].lchan[ss]; + /* we receive a channel activation request from the BSC, + * e.g. as a response to a channel req on RACH */ + if (l1sap->u.info.type == PRIM_INFO_ACTIVATE) { + if ((chan_nr & 0xE0) == 0x80) { + LOGP(DL1C, LOGL_ERROR, "Cannot activate" + " chan_nr 0x%02x\n", chan_nr); + break; + } + /* activate dedicated channel */ + trx_sched_set_lchan(sched, chan_nr, LID_DEDIC, 1); + /* activate associated channel */ + trx_sched_set_lchan(sched, chan_nr, LID_SACCH, 1); + /* set mode */ + trx_sched_set_mode(sched, chan_nr, + lchan->rsl_cmode, lchan->tch_mode, + lchan->tch.amr_mr.num_modes, + lchan->tch.amr_mr.bts_mode[0].mode, + lchan->tch.amr_mr.bts_mode[1].mode, + lchan->tch.amr_mr.bts_mode[2].mode, + lchan->tch.amr_mr.bts_mode[3].mode, + amr_get_initial_mode(lchan), + (lchan->ho.active == 1)); + /* init lapdm */ + lchan_init_lapdm(lchan); + /* set lchan active */ + lchan_set_state(lchan, LCHAN_S_ACTIVE); + /* set initial ciphering */ + l1if_set_ciphering(lchan, chan_nr, 0); + l1if_set_ciphering(lchan, chan_nr, 1); + if (lchan->encr.alg_id) + lchan->ciph_state = LCHAN_CIPH_RXTX_CONF; + else + lchan->ciph_state = LCHAN_CIPH_NONE; + + /* confirm */ + mph_info_chan_confirm(trx, chan_nr, + PRIM_INFO_ACTIVATE, 0); + break; + } + if (l1sap->u.info.type == PRIM_INFO_MODIFY) { + /* change mode */ + trx_sched_set_mode(sched, chan_nr, + lchan->rsl_cmode, lchan->tch_mode, + lchan->tch.amr_mr.num_modes, + lchan->tch.amr_mr.bts_mode[0].mode, + lchan->tch.amr_mr.bts_mode[1].mode, + lchan->tch.amr_mr.bts_mode[2].mode, + lchan->tch.amr_mr.bts_mode[3].mode, + amr_get_initial_mode(lchan), + 0); + break; + } + if ((chan_nr & 0xE0) == 0x80) { + LOGP(DL1C, LOGL_ERROR, "Cannot deactivate " + "chan_nr 0x%02x\n", chan_nr); + break; + } + /* deactivate associated channel */ + trx_sched_set_lchan(sched, chan_nr, 0x40, 0); + if (!l1sap->u.info.u.act_req.sacch_only) { + /* set lchan inactive */ + lchan_set_state(lchan, LCHAN_S_NONE); + /* deactivate dedicated channel */ + trx_sched_set_lchan(sched, chan_nr, 0x00, 0); + /* confirm only on dedicated channel */ + mph_info_chan_confirm(trx, chan_nr, + PRIM_INFO_DEACTIVATE, 0); + lchan->ciph_state = 0; /* FIXME: do this in common/\*.c */ + } + break; + default: + LOGP(DL1C, LOGL_NOTICE, "unknown MPH-INFO.req %d\n", + l1sap->u.info.type); + rc = -EINVAL; + goto done; + } + break; + default: + LOGP(DL1C, LOGL_NOTICE, "unknown prim %d op %d\n", + l1sap->oph.primitive, l1sap->oph.operation); + rc = -EINVAL; + goto done; + } + +done: + if (msg) + msgb_free(msg); + return rc; +} diff --git a/src/osmo-bts-virtual/l1_if.h b/src/osmo-bts-virtual/l1_if.h new file mode 100644 index 0000000..6a843b3 --- /dev/null +++ b/src/osmo-bts-virtual/l1_if.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +#include "virtual_um.h" + +struct vbts_l1h { + struct gsm_bts_trx *trx; + struct l1sched_trx l1s; + struct virt_um_inst *virt_um; +}; + +struct vbts_l1h *l1if_open(struct gsm_bts_trx *trx); +void l1if_close(struct vbts_l1h *l1h); +void l1if_reset(struct vbts_l1h *l1h); + +int l1if_mph_time_ind(struct gsm_bts *bts, uint32_t fn); + +int vbts_sched_start(struct gsm_bts *bts); diff --git a/src/osmo-bts-virtual/main.c b/src/osmo-bts-virtual/main.c new file mode 100644 index 0000000..81fb958 --- /dev/null +++ b/src/osmo-bts-virtual/main.c @@ -0,0 +1,139 @@ +/* Main program for Virtual OsmoBTS */ + +/* (C) 2015 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include "virtual_um.h" + +/* dummy, since no direct dsp support */ +uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx) +{ + return 0; +} + +int bts_model_init(struct gsm_bts *bts) +{ + bts->variant = BTS_OSMO_VIRTUAL; + bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3); + + gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_EFR); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_AMR); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_AMR); + + bts_model_vty_init(bts); + + return 0; +} + +void bts_model_print_help() +{ + LOGP(DSUM, LOGL_NOTICE, "Unimplemented %s\n", __func__); +} + +int bts_model_handle_options(int argc, char **argv) +{ + int num_errors = 0; + + while (1) { + int option_idx = 0, c; + static const struct option long_options[] = { + /* specific to this hardware */ + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "", + long_options, &option_idx); + if (c == -1) + break; + + switch (c) { + default: + num_errors++; + break; + } + } + + return num_errors; +} + +void bts_model_abis_close(struct gsm_bts *bts) +{ + /* for now, we simply terminate the program and re-spawn */ + bts_shutdown(bts, "Abis close"); +} + +void bts_model_phy_link_set_defaults(struct phy_link *plink) +{ + plink->u.virt.bts_mcast_group = DEFAULT_BTS_MCAST_GROUP; + plink->u.virt.bts_mcast_port = DEFAULT_BTS_MCAST_PORT; + plink->u.virt.ms_mcast_group = DEFAULT_MS_MCAST_GROUP; + plink->u.virt.ms_mcast_port = DEFAULT_MS_MCAST_PORT; +} + +void bts_model_phy_instance_set_defaults(struct phy_instance *pinst) +{ + LOGP(DSUM, LOGL_NOTICE, "Unimplemented %s\n", __func__); +} + +int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) +{ + LOGP(DSUM, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return -ENOTSUP; +} + +int bts_model_ts_connect(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config as_pchan) +{ + LOGP(DSUM, LOGL_NOTICE, "Unimplemented %s\n", __func__); + return -ENOTSUP; +} + +int main(int argc, char **argv) +{ + return bts_main(argc, argv); +} diff --git a/src/osmo-bts-virtual/osmo_mcast_sock.c b/src/osmo-bts-virtual/osmo_mcast_sock.c new file mode 100644 index 0000000..f092a73 --- /dev/null +++ b/src/osmo-bts-virtual/osmo_mcast_sock.c @@ -0,0 +1,112 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "osmo_mcast_sock.h" + +/* server socket is what we use for transmission. It is not subscribed + * to a multicast group or locally bound, but it is just a normal UDP + * socket that's connected to the remote mcast group + port */ +int mcast_server_sock_setup(struct osmo_fd *ofd, const char* tx_mcast_group, + uint16_t tx_mcast_port, bool loopback) +{ + int rc; + unsigned int flags = OSMO_SOCK_F_CONNECT; + + if (!loopback) + flags |= OSMO_SOCK_F_NO_MCAST_LOOP; + + /* setup mcast server socket */ + rc = osmo_sock_init_ofd(ofd, AF_INET, SOCK_DGRAM, IPPROTO_UDP, + tx_mcast_group, tx_mcast_port, flags); + if (rc < 0) { + perror("Failed to create Multicast Server Socket"); + return rc; + } + + return 0; +} + +/* the client socket is what we use for reception. It is a UDP socket + * that's bound to the GSMTAP UDP port and subscribed to the respective + * multicast group */ +int mcast_client_sock_setup(struct osmo_fd *ofd, const char *mcast_group, uint16_t mcast_port, + int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what), + void *osmo_fd_data) +{ + int rc; + + ofd->cb = fd_rx_cb; + ofd->when = BSC_FD_READ; + ofd->data = osmo_fd_data; + + /* Create mcast client socket */ + rc = osmo_sock_init_ofd(ofd, AF_INET, SOCK_DGRAM, IPPROTO_UDP, + NULL, mcast_port, OSMO_SOCK_F_BIND|OSMO_SOCK_F_NO_MCAST_ALL); + if (rc < 0) { + perror("Could not create mcast client socket"); + return rc; + } + + /* Configure and join the multicast group */ + rc = osmo_sock_mcast_subscribe(ofd->fd, mcast_group); + if (rc < 0) { + perror("Failed to join to mcast goup"); + osmo_fd_close(ofd); + return rc; + } + + return 0; +} + +struct mcast_bidir_sock * +mcast_bidir_sock_setup(void *ctx, const char *tx_mcast_group, uint16_t tx_mcast_port, + const char *rx_mcast_group, uint16_t rx_mcast_port, bool loopback, + int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what), + void *osmo_fd_data) +{ + struct mcast_bidir_sock *bidir_sock = talloc(ctx, struct mcast_bidir_sock); + int rc; + + if (!bidir_sock) + return NULL; + + rc = mcast_client_sock_setup(&bidir_sock->rx_ofd, rx_mcast_group, rx_mcast_port, + fd_rx_cb, osmo_fd_data); + if (rc < 0) { + talloc_free(bidir_sock); + return NULL; + } + rc = mcast_server_sock_setup(&bidir_sock->tx_ofd, tx_mcast_group, tx_mcast_port, loopback); + if (rc < 0) { + osmo_fd_close(&bidir_sock->rx_ofd); + talloc_free(bidir_sock); + return NULL; + } + return bidir_sock; + +} + +int mcast_bidir_sock_tx(struct mcast_bidir_sock *bidir_sock, const uint8_t *data, + unsigned int data_len) +{ + return send(bidir_sock->tx_ofd.fd, data, data_len, 0); +} + +int mcast_bidir_sock_rx(struct mcast_bidir_sock *bidir_sock, uint8_t *buf, unsigned int buf_len) +{ + return recv(bidir_sock->rx_ofd.fd, buf, buf_len, 0); +} + +void mcast_bidir_sock_close(struct mcast_bidir_sock *bidir_sock) +{ + osmo_fd_close(&bidir_sock->tx_ofd); + osmo_fd_close(&bidir_sock->rx_ofd); + talloc_free(bidir_sock); +} diff --git a/src/osmo-bts-virtual/osmo_mcast_sock.h b/src/osmo-bts-virtual/osmo_mcast_sock.h new file mode 100644 index 0000000..aa2013c --- /dev/null +++ b/src/osmo-bts-virtual/osmo_mcast_sock.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include +#include + +struct mcast_bidir_sock { + struct osmo_fd tx_ofd; + struct osmo_fd rx_ofd; +}; + +struct mcast_bidir_sock *mcast_bidir_sock_setup(void *ctx, + const char *tx_mcast_group, uint16_t tx_mcast_port, + const char *rx_mcast_group, uint16_t rx_mcast_port, bool loopback, + int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what), + void *osmo_fd_data); + +int mcast_server_sock_setup(struct osmo_fd *ofd, const char *tx_mcast_group, + uint16_t tx_mcast_port, bool loopback); + +int mcast_client_sock_setup(struct osmo_fd *ofd, const char *mcast_group, uint16_t mcast_port, + int (*fd_rx_cb)(struct osmo_fd *ofd, unsigned int what), + void *osmo_fd_data); + +int mcast_bidir_sock_tx(struct mcast_bidir_sock *bidir_sock, const uint8_t *data, unsigned int data_len); +int mcast_bidir_sock_rx(struct mcast_bidir_sock *bidir_sock, uint8_t *buf, unsigned int buf_len); +void mcast_bidir_sock_close(struct mcast_bidir_sock* bidir_sock); + diff --git a/src/osmo-bts-virtual/scheduler_virtbts.c b/src/osmo-bts-virtual/scheduler_virtbts.c new file mode 100644 index 0000000..de995e6 --- /dev/null +++ b/src/osmo-bts-virtual/scheduler_virtbts.c @@ -0,0 +1,618 @@ +/* Scheduler worker functiosn for Virtua OsmoBTS */ + +/* (C) 2015-2017 by Harald Welte + * (C) 2017 Sebastian Stumpf + * + * 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 . + * + */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "virtual_um.h" +#include "l1_if.h" + +#define MODULO_HYPERFRAME 0 + +static const char *gsmtap_hdr_stringify(const struct gsmtap_hdr *gh) +{ + static char buf[256]; + snprintf(buf, sizeof(buf), "(ARFCN=%u, ts=%u, ss=%u, type=%u/%u)", + gh->arfcn & GSMTAP_ARFCN_MASK, gh->timeslot, gh->sub_slot, gh->type, gh->sub_type); + return buf; +} + +/** + * Send a message over the virtual um interface. + * This will at first wrap the msg with a GSMTAP header and then write it to the declared multicast socket. + * TODO: we might want to remove unused argument uint8_t tn + */ +static void tx_to_virt_um(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, struct msgb *msg) +{ + const struct trx_chan_desc *chdesc = &trx_chan_desc[chan]; + struct msgb *outmsg; /* msg to send with gsmtap header prepended */ + uint16_t arfcn = l1t->trx->arfcn; /* ARFCN of the tranceiver the message is send with */ + uint8_t signal_dbm = 63; /* signal strength, 63 is best */ + uint8_t snr = 63; /* signal noise ratio, 63 is best */ + uint8_t *data = msgb_l2(msg); /* data to transmit (whole message without l1 header) */ + uint8_t data_len = msgb_l2len(msg); /* length of data */ + uint8_t rsl_chantype; /* RSL chan type (TS 08.58, 9.3.1) */ + uint8_t subslot; /* multiframe subslot to send msg in (tch -> 0-26, bcch/ccch -> 0-51) */ + uint8_t timeslot; /* TDMA timeslot to send in (0-7) */ + uint8_t gsmtap_chantype; /* the GSMTAP channel */ + + rsl_dec_chan_nr(chdesc->chan_nr, &rsl_chantype, &subslot, ×lot); + /* the timeslot is not encoded in the chan_nr of the chdesc, and so has to be overwritten */ + timeslot = tn; + /* in Osmocom, AGCH is only sent on ccch block 0. no idea why. this seems to cause false GSMTAP channel + * types for agch and pch. */ + if (rsl_chantype == RSL_CHAN_PCH_AGCH && L1SAP_FN2CCCHBLOCK(fn) == 0) + gsmtap_chantype = GSMTAP_CHANNEL_PCH; + else + gsmtap_chantype = chantype_rsl2gsmtap(rsl_chantype, chdesc->link_id); /* the logical channel type */ + +#if MODULO_HYPERFRAME + /* Restart fn after every superframe (26 * 51 frames) to simulate hyperframe overflow each 6 seconds. */ + fn %= 26 * 51; +#endif + + outmsg = gsmtap_makemsg(arfcn, timeslot, gsmtap_chantype, subslot, fn, signal_dbm, snr, data, data_len); + + if (outmsg) { + struct phy_instance *pinst = trx_phy_instance(l1t->trx); + struct gsmtap_hdr *gh = (struct gsmtap_hdr *)msgb_data(outmsg); + int rc; + + rc = virt_um_write_msg(pinst->phy_link->u.virt.virt_um, outmsg); + if (rc < 0) + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, + "%s GSMTAP msg could not send to virtual Um\n", gsmtap_hdr_stringify(gh)); + else if (rc == 0) + bts_shutdown(l1t->trx->bts, "VirtPHY write socket died\n"); + else + LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, + "%s Sending GSMTAP message to virtual Um\n", gsmtap_hdr_stringify(gh)); + } else + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "GSMTAP msg could not be created!\n"); + + /* free incoming message */ + msgb_free(msg); +} + +/* + * TX on downlink + */ + +/* an IDLE burst returns nothing. on C0 it is replaced by dummy burst */ +ubit_t *tx_idle_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + return NULL; +} + +ubit_t *tx_fcch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + return NULL; +} + +ubit_t *tx_sch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + return NULL; +} + +ubit_t *tx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + struct msgb *msg; + + if (bid > 0) + return NULL; + + /* get mac block from queue */ + msg = _sched_dequeue_prim(l1t, tn, fn, chan); + if (!msg) { + LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "has not been served !! No prim\n"); + return NULL; + } + + /* check validity of message */ + if (msgb_l2len(msg) != GSM_MACBLOCK_LEN) { + LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim not 23 bytes, please FIX! (len=%d)\n", + msgb_l2len(msg)); + /* free message */ + msgb_free(msg); + return NULL; + } + + /* transmit the msg received on dl from bsc to layer1 (virt Um) */ + tx_to_virt_um(l1t, tn, fn, chan, msg); + + return NULL; +} + +ubit_t *tx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + struct msgb *msg = NULL; /* make GCC happy */ + + if (bid > 0) + return NULL; + + /* get mac block from queue */ + msg = _sched_dequeue_prim(l1t, tn, fn, chan); + if (!msg) { + LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "has not been served !! No prim\n"); + return NULL; + } + + tx_to_virt_um(l1t, tn, fn, chan, msg); + + return NULL; +} + +static void tx_tch_common(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, struct msgb **_msg_tch, + struct msgb **_msg_facch, int codec_mode_request) +{ + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct msgb *msg1, *msg2, *msg_tch = NULL, *msg_facch = NULL; + struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan]; + uint8_t rsl_cmode = chan_state->rsl_cmode; + uint8_t tch_mode = chan_state->tch_mode; + struct osmo_phsap_prim *l1sap; +#if 0 + /* handle loss detection of received TCH frames */ + if (rsl_cmode == RSL_CMOD_SPD_SPEECH + && ++(chan_state->lost) > 5) { + uint8_t tch_data[GSM_FR_BYTES]; + int len; + + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Missing TCH bursts detected, sending " + "BFI for %s\n", trx_chan_desc[chan].name); + + /* indicate bad frame */ + switch (tch_mode) { + case GSM48_CMODE_SPEECH_V1: /* FR / HR */ + if (chan != TRXC_TCHF) { /* HR */ + tch_data[0] = 0x70; /* F = 0, FT = 111 */ + memset(tch_data + 1, 0, 14); + len = 15; + break; + } + memset(tch_data, 0, GSM_FR_BYTES); + len = GSM_FR_BYTES; + break; + case GSM48_CMODE_SPEECH_EFR: /* EFR */ + if (chan != TRXC_TCHF) + goto inval_mode1; + memset(tch_data, 0, GSM_EFR_BYTES); + len = GSM_EFR_BYTES; + break; + case GSM48_CMODE_SPEECH_AMR: /* AMR */ + len = amr_compose_payload(tch_data, + chan_state->codec[chan_state->dl_cmr], + chan_state->codec[chan_state->dl_ft], 1); + if (len < 2) + break; + memset(tch_data + 2, 0, len - 2); + _sched_compose_tch_ind(l1t, tn, 0, chan, tch_data, len); + break; + default: +inval_mode1: + LOGP(DL1P, LOGL_ERROR, "TCH mode invalid, please " + "fix!\n"); + len = 0; + } + if (len) + _sched_compose_tch_ind(l1t, tn, 0, chan, tch_data, len); + } +#endif + + /* get frame and unlink from queue */ + msg1 = _sched_dequeue_prim(l1t, tn, fn, chan); + msg2 = _sched_dequeue_prim(l1t, tn, fn, chan); + if (msg1) { + l1sap = msgb_l1sap_prim(msg1); + if (l1sap->oph.primitive == PRIM_TCH) { + msg_tch = msg1; + if (msg2) { + l1sap = msgb_l1sap_prim(msg2); + if (l1sap->oph.primitive == PRIM_TCH) { + LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, + "TCH twice, please FIX! "); + msgb_free(msg2); + } else + msg_facch = msg2; + } + } else { + msg_facch = msg1; + if (msg2) { + l1sap = msgb_l1sap_prim(msg2); + if (l1sap->oph.primitive != PRIM_TCH) { + LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, + "FACCH twice, please FIX! "); + msgb_free(msg2); + } else + msg_tch = msg2; + } + } + } else if (msg2) { + l1sap = msgb_l1sap_prim(msg2); + if (l1sap->oph.primitive == PRIM_TCH) + msg_tch = msg2; + else + msg_facch = msg2; + } + + /* check validity of message */ + if (msg_facch && msgb_l2len(msg_facch) != GSM_MACBLOCK_LEN) { + LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim not 23 bytes, please FIX! (len=%d)\n", + msgb_l2len(msg_facch)); + /* free message */ + msgb_free(msg_facch); + msg_facch = NULL; + } + + /* check validity of message, get AMR ft and cmr */ + if (!msg_facch && msg_tch) { + int len; +#if 0 + uint8_t bfi, cmr_codec, ft_codec; + int cmr, ft, i; +#endif + + if (rsl_cmode != RSL_CMOD_SPD_SPEECH) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Dropping speech frame, " + "because we are not in speech mode\n"); + goto free_bad_msg; + } + + switch (tch_mode) { + case GSM48_CMODE_SPEECH_V1: /* FR / HR */ + if (chan != TRXC_TCHF) { /* HR */ + len = 15; + if (msgb_l2len(msg_tch) >= 1 + && (msg_tch->l2h[0] & 0xf0) != 0x00) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, + "Transmitting 'bad HR frame'\n"); + goto free_bad_msg; + } + break; + } + len = GSM_FR_BYTES; + if (msgb_l2len(msg_tch) >= 1 + && (msg_tch->l2h[0] >> 4) != 0xd) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, + "Transmitting 'bad FR frame'\n"); + goto free_bad_msg; + } + break; + case GSM48_CMODE_SPEECH_EFR: /* EFR */ + if (chan != TRXC_TCHF) + goto inval_mode2; + len = GSM_EFR_BYTES; + if (msgb_l2len(msg_tch) >= 1 + && (msg_tch->l2h[0] >> 4) != 0xc) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, + "Transmitting 'bad EFR frame'\n"); + goto free_bad_msg; + } + break; + case GSM48_CMODE_SPEECH_AMR: /* AMR */ +#if 0 + len = amr_decompose_payload(msg_tch->l2h, + msgb_l2len(msg_tch), &cmr_codec, &ft_codec, + &bfi); + cmr = -1; + ft = -1; + for (i = 0; i < chan_state->codecs; i++) { + if (chan_state->codec[i] == cmr_codec) + cmr = i; + if (chan_state->codec[i] == ft_codec) + ft = i; + } + if (cmr >= 0) { /* new request */ + chan_state->dl_cmr = cmr; + /* disable AMR loop */ + trx_loop_amr_set(chan_state, 0); + } else { + /* enable AMR loop */ + trx_loop_amr_set(chan_state, 1); + } + if (ft < 0) { + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, + "Codec (FT = %d) of RTP frame not in list. ", ft_codec); + goto free_bad_msg; + } + if (codec_mode_request && chan_state->dl_ft != ft) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, + "Codec (FT = %d) of RTP cannot be changed now, but in " + "next frame\n", ft_codec); + goto free_bad_msg; + } + chan_state->dl_ft = ft; + if (bfi) { + LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, + "Transmitting 'bad AMR frame'\n"); + goto free_bad_msg; + } +#else + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "AMR not supported!\n"); + goto free_bad_msg; +#endif + break; + default: +inval_mode2: + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode invalid, please fix!\n"); + goto free_bad_msg; + } + if (len < 0) { + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot send invalid AMR payload\n"); + goto free_bad_msg; + } + if (msgb_l2len(msg_tch) != len) { + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot send payload with " + "invalid length! (expecing %d, received %d)\n", len, msgb_l2len(msg_tch)); +free_bad_msg: + /* free message */ + msgb_free(msg_tch); + msg_tch = NULL; + goto send_frame; + } + } + +send_frame: + *_msg_tch = msg_tch; + *_msg_facch = msg_facch; +} + +ubit_t *tx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + struct msgb *msg_tch = NULL, *msg_facch = NULL; + + if (bid > 0) + return NULL; + + tx_tch_common(l1t, tn, fn, chan, bid, &msg_tch, &msg_facch, + (((fn + 4) % 26) >> 2) & 1); + + /* no message at all */ + if (!msg_tch && !msg_facch) { + LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "has not been served !! No prim\n"); + goto send_burst; + } + + if (msg_facch) { + tx_to_virt_um(l1t, tn, fn, chan, msg_facch); + msgb_free(msg_tch); + } else + tx_to_virt_um(l1t, tn, fn, chan, msg_tch); + +send_burst: + + return NULL; +} + +ubit_t *tx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, uint16_t *nbits) +{ + struct msgb *msg_tch = NULL, *msg_facch = NULL; + struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn); + struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan]; + //uint8_t tch_mode = chan_state->tch_mode; + + /* send burst, if we already got a frame */ + if (bid > 0) + return NULL; + + /* get TCH and/or FACCH */ + tx_tch_common(l1t, tn, fn, chan, bid, &msg_tch, &msg_facch, + (((fn + 4) % 26) >> 2) & 1); + + /* check for FACCH alignment */ + if (msg_facch && ((((fn + 4) % 26) >> 2) & 1)) { + LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot transmit FACCH starting on " + "even frames, please fix RTS!\n"); + msgb_free(msg_facch); + msg_facch = NULL; + } + + /* no message at all */ + if (!msg_tch && !msg_facch && !chan_state->dl_ongoing_facch) { + LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "has not been served !! No prim\n"); + goto send_burst; + } + + if (msg_facch) { + tx_to_virt_um(l1t, tn, fn, chan, msg_facch); + msgb_free(msg_tch); + } else + tx_to_virt_um(l1t, tn, fn, chan, msg_tch); + +send_burst: + return NULL; +} + + +/*********************************************************************** + * RX on uplink (indication to upper layer) + ***********************************************************************/ + +/* we don't use those functions, as we feed the MAC frames from GSMTAP + * directly into the L1SAP, bypassing the TDMA multiplex logic oriented + * towards receiving bursts */ + +int rx_rach_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256) +{ + return 0; +} + +/*! \brief a single burst was received by the PHY, process it */ +int rx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256) +{ + return 0; +} + +int rx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256) +{ + return 0; +} + +int rx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256) +{ + return 0; +} + +int rx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn, + enum trx_chan_type chan, uint8_t bid, sbit_t *bits, uint16_t nbits, + int8_t rssi, int16_t toa256) +{ + return 0; +} + +void _sched_act_rach_det(struct l1sched_trx *l1t, uint8_t tn, uint8_t ss, int activate) +{ +} + +/*********************************************************************** + * main scheduler function + ***********************************************************************/ + +#define RTS_ADVANCE 5 /* about 20ms */ +#define FRAME_DURATION_uS 4615 + +static int vbts_sched_fn(struct gsm_bts *bts, uint32_t fn) +{ + struct gsm_bts_trx *trx; + + /* send time indication */ + /* update model with new frame number, lot of stuff happening, measurements of timeslots */ + /* saving GSM time in BTS model, and more */ + l1if_mph_time_ind(bts, fn); + + /* advance the frame number? */ + llist_for_each_entry(trx, &bts->trx_list, list) { + struct phy_instance *pinst = trx_phy_instance(trx); + struct l1sched_trx *l1t = &pinst->u.virt.sched; + int tn; + uint16_t nbits; + + /* do for each of the 8 timeslots */ + for (tn = 0; tn < ARRAY_SIZE(l1t->ts); tn++) { + /* Generate RTS indication to higher layers */ + /* This will basically do 2 things (check l1_if:bts_model_l1sap_down): + * 1) Get pending messages from layer 2 (from the lapdm queue) + * 2) Process the messages + * --> Handle and process non-transparent RSL-Messages (activate channel, ) + * --> Forward transparent RSL-DATA-Messages to the ms by appending them to + * the l1-dl-queue */ + _sched_rts(l1t, tn, (fn + RTS_ADVANCE) % GSM_HYPERFRAME); + /* schedule transmit backend functions */ + /* Process data in the l1-dlqueue and forward it + * to MS */ + /* the returned bits are not used here, the routines called will directly forward their + * bits to the virt Um */ + _sched_dl_burst(l1t, tn, fn, &nbits); + } + } + + return 0; +} + +static void vbts_fn_timer_cb(void *data) +{ + struct gsm_bts *bts = data; + struct timeval tv_now; + struct timeval *tv_clock = &bts->vbts.tv_clock; + int32_t elapsed_us; + + gettimeofday(&tv_now, NULL); + + /* check how much time elapsed till the last timer callback call. + * this value should be about 4.615 ms (a bit greater) as this is the scheduling interval */ + elapsed_us = (tv_now.tv_sec - tv_clock->tv_sec) * 1000000 + + (tv_now.tv_usec - tv_clock->tv_usec); + + /* not so good somehow a lot of time passed between two timer callbacks */ + if (elapsed_us > 2 *FRAME_DURATION_uS) + LOGP(DL1P, LOGL_NOTICE, "vbts_fn_timer_cb after %d us\n", elapsed_us); + + /* schedule the current frame/s (fn = frame number) + * this loop will be called at least once, but can also be executed + * multiple times if more than one frame duration (4615us) passed till the last callback */ + while (elapsed_us > FRAME_DURATION_uS / 2) { + const struct timeval tv_frame = { + .tv_sec = 0, + .tv_usec = FRAME_DURATION_uS, + }; + timeradd(tv_clock, &tv_frame, tv_clock); + /* increment the frame number in the BTS model instance */ + bts->vbts.last_fn = (bts->vbts.last_fn + 1) % GSM_HYPERFRAME; + vbts_sched_fn(bts, bts->vbts.last_fn); + elapsed_us -= FRAME_DURATION_uS; + } + + /* re-schedule the timer */ + /* timer is set to frame duration - elapsed time to guarantee that this cb method will be + * periodically executed every 4.615ms */ + osmo_timer_schedule(&bts->vbts.fn_timer, 0, FRAME_DURATION_uS - elapsed_us); +} + +int vbts_sched_start(struct gsm_bts *bts) +{ + LOGP(DL1P, LOGL_NOTICE, "starting VBTS scheduler\n"); + + memset(&bts->vbts.fn_timer, 0, sizeof(bts->vbts.fn_timer)); + bts->vbts.fn_timer.cb = vbts_fn_timer_cb; + bts->vbts.fn_timer.data = bts; + + gettimeofday(&bts->vbts.tv_clock, NULL); + /* trigger the first timer after 4615us (a frame duration) */ + osmo_timer_schedule(&bts->vbts.fn_timer, 0, FRAME_DURATION_uS); + + return 0; +} diff --git a/src/osmo-bts-virtual/virtual_um.c b/src/osmo-bts-virtual/virtual_um.c new file mode 100644 index 0000000..fd0940f --- /dev/null +++ b/src/osmo-bts-virtual/virtual_um.c @@ -0,0 +1,100 @@ +/* Routines for a Virtual Um interface over GSMTAP/UDP */ + +/* (C) 2015 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include "osmo_mcast_sock.h" +#include "virtual_um.h" +#include + +/** + * Virtual UM interface file descriptor callback. + * Should be called by select.c when the fd is ready for reading. + */ +static int virt_um_fd_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct virt_um_inst *vui = ofd->data; + + if (what & BSC_FD_READ) { + struct msgb *msg = msgb_alloc(VIRT_UM_MSGB_SIZE, "Virtual UM Rx"); + int rc; + + /* read message from fd into message buffer */ + rc = mcast_bidir_sock_rx(vui->mcast_sock, msgb_data(msg), msgb_tailroom(msg)); + if (rc > 0) { + msgb_put(msg, rc); + msg->l1h = msgb_data(msg); + /* call the l1 callback function for a received msg */ + vui->recv_cb(vui, msg); + } else if (rc == 0) { + vui->recv_cb(vui, NULL); + osmo_fd_close(ofd); + } else + perror("Read from multicast socket"); + + } + + return 0; +} + +struct virt_um_inst *virt_um_init(void *ctx, char *tx_mcast_group, uint16_t tx_mcast_port, + char *rx_mcast_group, uint16_t rx_mcast_port, + void (*recv_cb)(struct virt_um_inst *vui, struct msgb *msg)) +{ + struct virt_um_inst *vui = talloc_zero(ctx, struct virt_um_inst); + vui->mcast_sock = mcast_bidir_sock_setup(ctx, tx_mcast_group, tx_mcast_port, + rx_mcast_group, rx_mcast_port, 1, virt_um_fd_cb, vui); + if (!vui->mcast_sock) { + perror("Unable to create VirtualUm multicast socket"); + talloc_free(vui); + return NULL; + } + vui->recv_cb = recv_cb; + + return vui; + +} + +void virt_um_destroy(struct virt_um_inst *vui) +{ + mcast_bidir_sock_close(vui->mcast_sock); + talloc_free(vui); +} + +/** + * Write msg to to multicast socket and free msg afterwards + */ +int virt_um_write_msg(struct virt_um_inst *vui, struct msgb *msg) +{ + int rc; + + rc = mcast_bidir_sock_tx(vui->mcast_sock, msgb_data(msg), + msgb_length(msg)); + if (rc < 0) + perror("Writing to multicast socket"); + msgb_free(msg); + + return rc; +} diff --git a/src/osmo-bts-virtual/virtual_um.h b/src/osmo-bts-virtual/virtual_um.h new file mode 100644 index 0000000..ac098dd --- /dev/null +++ b/src/osmo-bts-virtual/virtual_um.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include "osmo_mcast_sock.h" + +/* We use multicast group addresses from the 239.192.0.0/14 rage, as + * those are designated by RFC2365 as "IPv4 Organization Local Scope, + * "... the space from which an organization should allocate sub- + * ranges when defining scopes for private use." */ + +#define VIRT_UM_MSGB_SIZE 256 +#define DEFAULT_MS_MCAST_GROUP "239.193.23.1" +#define DEFAULT_MS_MCAST_PORT 4729 /* IANA-registered port for GSMTAP */ +#define DEFAULT_BTS_MCAST_GROUP "239.193.23.2" +#define DEFAULT_BTS_MCAST_PORT 4729 /* IANA-registered port for GSMTAP */ + +struct virt_um_inst { + void *priv; + struct mcast_bidir_sock *mcast_sock; + void (*recv_cb)(struct virt_um_inst *vui, struct msgb *msg); +}; + +struct virt_um_inst *virt_um_init( + void *ctx, char *tx_mcast_group, uint16_t tx_mcast_port, + char *rx_mcast_group, uint16_t rx_mcast_port, + void (*recv_cb)(struct virt_um_inst *vui, struct msgb *msg)); + +void virt_um_destroy(struct virt_um_inst *vui); + +int virt_um_write_msg(struct virt_um_inst *vui, struct msgb *msg); diff --git a/src/osmo-bts-virtual/virtualbts_vty.c b/src/osmo-bts-virtual/virtualbts_vty.c new file mode 100644 index 0000000..323222b --- /dev/null +++ b/src/osmo-bts-virtual/virtualbts_vty.c @@ -0,0 +1,185 @@ +/* VTY interface for virtual OsmoBTS */ + +/* (C) 2015-2017 by Harald Welte + * (C) 2017 Sebastian Stumpf + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include "virtual_um.h" + +#define TRX_STR "Transceiver related commands\n" "TRX number\n" + +#define SHOW_TRX_STR \ + SHOW_STR \ + TRX_STR + +static struct gsm_bts *vty_bts; + +void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts) +{ +} + +void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx) +{ +} + +void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst) +{ +} + +void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink) +{ + if (plink->u.virt.mcast_dev) + vty_out(vty, " virtual-um net-device %s%s", + plink->u.virt.mcast_dev, VTY_NEWLINE); + if (strcmp(plink->u.virt.ms_mcast_group, DEFAULT_BTS_MCAST_GROUP)) + vty_out(vty, " virtual-um ms-multicast-group %s%s", + plink->u.virt.ms_mcast_group, VTY_NEWLINE); + if (plink->u.virt.ms_mcast_port != DEFAULT_BTS_MCAST_PORT) + vty_out(vty, " virtual-um ms-udp-port %u%s", + plink->u.virt.ms_mcast_port, VTY_NEWLINE); + if (strcmp(plink->u.virt.bts_mcast_group, DEFAULT_MS_MCAST_GROUP)) + vty_out(vty, " virtual-um bts-multicast-group %s%s", + plink->u.virt.bts_mcast_group, VTY_NEWLINE); + if (plink->u.virt.bts_mcast_port != DEFAULT_MS_MCAST_PORT) + vty_out(vty, " virtual-um bts-udp-port %u%s", + plink->u.virt.bts_mcast_port, VTY_NEWLINE); + +} + +#define VUM_STR "Virtual Um layer\n" + +DEFUN(cfg_phy_ms_mcast_group, cfg_phy_ms_mcast_group_cmd, + "virtual-um ms-multicast-group GROUP", + VUM_STR "Configure the MS multicast group\n") +{ + struct phy_link *plink = vty->index; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Can only reconfigure a PHY link that is down%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + osmo_talloc_replace_string(plink, &plink->u.virt.ms_mcast_group, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_ms_mcast_port, cfg_phy_ms_mcast_port_cmd, + "virtual-um ms-udp-port <0-65535>", + VUM_STR "Configure the MS UDP port\n") +{ + struct phy_link *plink = vty->index; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Can only reconfigure a PHY link that is down%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + plink->u.virt.ms_mcast_port = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_bts_mcast_group, cfg_phy_bts_mcast_group_cmd, + "virtual-um bts-multicast-group GROUP", + VUM_STR "Configure the BTS multicast group\n") +{ + struct phy_link *plink = vty->index; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Can only reconfigure a PHY link that is down%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + osmo_talloc_replace_string(plink, &plink->u.virt.bts_mcast_group, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_bts_mcast_port, cfg_phy_bts_mcast_port_cmd, + "virtual-um bts-udp-port <0-65535>", + VUM_STR "Configure the BTS UDP port\n") +{ + struct phy_link *plink = vty->index; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Can only reconfigure a PHY link that is down%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + plink->u.virt.bts_mcast_port = atoi(argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_mcast_dev, cfg_phy_mcast_dev_cmd, + "virtual-um net-device NETDEV", + VUM_STR "Configure the network device\n") +{ + struct phy_link *plink = vty->index; + + if (plink->state != PHY_LINK_SHUTDOWN) { + vty_out(vty, "Can only reconfigure a PHY link that is down%s", + VTY_NEWLINE); + return CMD_WARNING; + } + + osmo_talloc_replace_string(plink, &plink->u.virt.mcast_dev, argv[0]); + + return CMD_SUCCESS; +} + +int bts_model_vty_init(struct gsm_bts *bts) +{ + vty_bts = bts; + + install_element(PHY_NODE, &cfg_phy_ms_mcast_group_cmd); + install_element(PHY_NODE, &cfg_phy_ms_mcast_port_cmd); + install_element(PHY_NODE, &cfg_phy_bts_mcast_group_cmd); + install_element(PHY_NODE, &cfg_phy_bts_mcast_port_cmd); + install_element(PHY_NODE, &cfg_phy_mcast_dev_cmd); + + return 0; +} diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..1eb28d6 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,45 @@ +SUBDIRS = paging cipher agch misc handover tx_power power meas + +if ENABLE_SYSMOBTS +SUBDIRS += sysmobts +endif + +# 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) +TESTSUITE = $(srcdir)/testsuite +DISTCLEANFILES = atconfig + +check-local: atconfig $(TESTSUITE) + $(SHELL) '$(TESTSUITE)' $(TESTSUITEFLAGS) + +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/agch/Makefile.am b/tests/agch/Makefile.am new file mode 100644 index 0000000..1357ea2 --- /dev/null +++ b/tests/agch/Makefile.am @@ -0,0 +1,8 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(ORTP_CFLAGS) +LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCODEC_LIBS) $(ORTP_LIBS) +noinst_PROGRAMS = agch_test +EXTRA_DIST = agch_test.ok + +agch_test_SOURCES = agch_test.c $(srcdir)/../stubs.c +agch_test_LDADD = $(top_builddir)/src/common/libbts.a $(LDADD) diff --git a/tests/agch/agch_test.c b/tests/agch/agch_test.c new file mode 100644 index 0000000..e6c56d9 --- /dev/null +++ b/tests/agch/agch_test.c @@ -0,0 +1,240 @@ +/* testing the agch code */ + +/* (C) 2011 by Holger Hans Peter Freyther + * (C) 2014 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include + +#include +#include +#include + +#include +#include + +static struct gsm_bts *bts; + +static int count_imm_ass_rej_refs(struct gsm48_imm_ass_rej *rej) +{ + int count = 0; + count++; + + if (memcmp(&rej->req_ref1, &rej->req_ref2, sizeof(rej->req_ref2))) { + count++; + } + + if (memcmp(&rej->req_ref1, &rej->req_ref3, sizeof(rej->req_ref3)) + && memcmp(&rej->req_ref2, &rej->req_ref3, sizeof(rej->req_ref3))) { + count++; + } + + if (memcmp(&rej->req_ref1, &rej->req_ref4, sizeof(rej->req_ref4)) + && memcmp(&rej->req_ref2, &rej->req_ref4, sizeof(rej->req_ref4)) + && memcmp(&rej->req_ref3, &rej->req_ref4, sizeof(rej->req_ref4))) { + count++; + } + + return count; +} + +static void put_imm_ass_rej(struct msgb *msg, int idx, uint8_t wait_ind) +{ + /* GSM CCCH - Immediate Assignment Reject */ + static const unsigned char gsm_a_ccch_data[23] = { + 0x4d, 0x06, 0x3a, 0x03, 0x25, 0x00, 0x00, 0x0a, + 0x25, 0x00, 0x00, 0x0a, 0x25, 0x00, 0x00, 0x0a, + 0x25, 0x00, 0x00, 0x0a, 0x2b, 0x2b, 0x2b + }; + + struct gsm48_imm_ass_rej *rej; + msg->l3h = msgb_put(msg, sizeof(gsm_a_ccch_data)); + rej = (struct gsm48_imm_ass_rej *)msg->l3h; + memmove(msg->l3h, gsm_a_ccch_data, sizeof(gsm_a_ccch_data)); + + rej->req_ref1.t1 = idx; + rej->wait_ind1 = wait_ind; + + rej->req_ref2.t1 = idx; + rej->req_ref3.t1 = idx; + rej->req_ref4.t1 = idx; +} + +static void put_imm_ass(struct msgb *msg, int idx) +{ + /* GSM CCCH - Immediate Assignment */ + static const unsigned char gsm_a_ccch_data[23] = { + 0x2d, 0x06, 0x3f, 0x03, 0x0c, 0xe3, 0x69, 0x25, + 0x00, 0x00, 0x00, 0x00, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b + }; + + struct gsm48_imm_ass *ima; + msg->l3h = msgb_put(msg, sizeof(gsm_a_ccch_data)); + ima = (struct gsm48_imm_ass *)msg->l3h; + memmove(msg->l3h, gsm_a_ccch_data, sizeof(gsm_a_ccch_data)); + + ima->req_ref.t1 = idx; +} + +static void test_agch_queue(void) +{ + int rc; + uint8_t out_buf[GSM_MACBLOCK_LEN]; + struct gsm_time g_time; + const int num_rounds = 40; + const int num_ima_per_round = 2; + const int num_rej_per_round = 16; + + int round, idx; + int count = 0; + struct msgb *msg = NULL; + int multiframes = 0; + int imm_ass_count = 0; + int imm_ass_rej_count = 0; + int imm_ass_rej_ref_count = 0; + + g_time.fn = 0; + g_time.t1 = 0; + g_time.t2 = 0; + g_time.t3 = 6; + + printf("Testing AGCH messages queue handling.\n"); + bts->agch_queue.max_length = 32; + + bts->agch_queue.low_level = 30; + bts->agch_queue.high_level = 30; + bts->agch_queue.thresh_level = 60; + + for (round = 1; round <= num_rounds; round++) { + for (idx = 0; idx < num_ima_per_round; idx++) { + msg = msgb_alloc(GSM_MACBLOCK_LEN, __FUNCTION__); + put_imm_ass(msg, ++count); + bts_agch_enqueue(bts, msg); + imm_ass_count++; + } + for (idx = 0; idx < num_rej_per_round; idx++) { + msg = msgb_alloc(GSM_MACBLOCK_LEN, __FUNCTION__); + put_imm_ass_rej(msg, ++count, 10); + bts_agch_enqueue(bts, msg); + imm_ass_rej_count++; + imm_ass_rej_ref_count++; + } + } + + printf("AGCH filled: count %u, imm.ass %d, imm.ass.rej %d (refs %d), " + "queue limit %u, occupied %d, " + "dropped %"PRIu64", merged %"PRIu64", rejected %"PRIu64", " + "ag-res %"PRIu64", non-res %"PRIu64"\n", + count, imm_ass_count, imm_ass_rej_count, imm_ass_rej_ref_count, + bts->agch_queue.max_length, bts->agch_queue.length, + bts->agch_queue.dropped_msgs, bts->agch_queue.merged_msgs, + bts->agch_queue.rejected_msgs, bts->agch_queue.agch_msgs, + bts->agch_queue.pch_msgs); + + imm_ass_count = 0; + imm_ass_rej_count = 0; + imm_ass_rej_ref_count = 0; + + for (idx = 0; 1; idx++) { + struct gsm48_imm_ass *ima; + int is_agch = (idx % 3) == 0; /* 1 AG reserved, 2 PCH */ + if (is_agch) + multiframes++; + + rc = bts_ccch_copy_msg(bts, out_buf, &g_time, is_agch); + ima = (struct gsm48_imm_ass *)out_buf; + switch (ima->msg_type) { + case GSM48_MT_RR_IMM_ASS: + imm_ass_count++; + break; + case GSM48_MT_RR_IMM_ASS_REJ: + imm_ass_rej_count++; + imm_ass_rej_ref_count += + count_imm_ass_rej_refs((struct gsm48_imm_ass_rej *)ima); + break; + default: + break; + } + if (is_agch && rc <= 0) + break; + + } + + printf("AGCH drained: multiframes %u, imm.ass %d, imm.ass.rej %d (refs %d), " + "queue limit %u, occupied %d, " + "dropped %"PRIu64", merged %"PRIu64", rejected %"PRIu64", " + "ag-res %"PRIu64", non-res %"PRIu64"\n", + multiframes, imm_ass_count, imm_ass_rej_count, imm_ass_rej_ref_count, + bts->agch_queue.max_length, bts->agch_queue.length, + bts->agch_queue.dropped_msgs, bts->agch_queue.merged_msgs, + bts->agch_queue.rejected_msgs, bts->agch_queue.agch_msgs, + bts->agch_queue.pch_msgs); +} + +static void test_agch_queue_length_computation(void) +{ + static const int ccch_configs[] = { + RSL_BCCH_CCCH_CONF_1_NC, + RSL_BCCH_CCCH_CONF_1_C, + RSL_BCCH_CCCH_CONF_2_NC, + RSL_BCCH_CCCH_CONF_3_NC, + RSL_BCCH_CCCH_CONF_4_NC, + }; + static const uint8_t tx_integer[] = { + 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 20, 25, 32, 50, + }; + + int T_idx, c_idx, max_len; + + printf("Testing AGCH queue length computation.\n"); + + printf("T\t\tBCCH slots\n"); + printf("\t1(NC)\t1(C)\t2(NC)\t3(NC)\t4(NC)\n"); + for (T_idx = 0; T_idx < ARRAY_SIZE(tx_integer); T_idx++) { + printf("%d", tx_integer[T_idx]); + for (c_idx = 0; c_idx < ARRAY_SIZE(ccch_configs); c_idx++) { + max_len = bts_agch_max_queue_length(tx_integer[T_idx], + ccch_configs[c_idx]); + printf("\t%d", max_len); + } + printf("\n"); + } +} + +int main(int argc, char **argv) +{ + tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context"); + msgb_talloc_ctx_init(tall_bts_ctx, 0); + + osmo_init_logging2(tall_bts_ctx, &bts_log_info); + + bts = gsm_bts_alloc(tall_bts_ctx, 0); + if (bts_init(bts) < 0) { + fprintf(stderr, "unable to open bts\n"); + exit(1); + } + + test_agch_queue_length_computation(); + test_agch_queue(); + printf("Success\n"); + + return 0; +} + diff --git a/tests/agch/agch_test.ok b/tests/agch/agch_test.ok new file mode 100644 index 0000000..49173a3 --- /dev/null +++ b/tests/agch/agch_test.ok @@ -0,0 +1,23 @@ +Testing AGCH queue length computation. +T BCCH slots + 1(NC) 1(C) 2(NC) 3(NC) 4(NC) +3 20 9 20 20 20 +4 28 11 28 28 28 +5 40 13 40 40 40 +6 59 19 59 59 59 +7 79 25 79 79 79 +8 21 9 21 21 21 +9 28 12 28 28 28 +10 40 13 40 40 40 +11 60 20 60 60 60 +12 80 26 80 80 80 +14 22 10 22 22 22 +16 30 13 30 30 30 +20 42 14 42 42 42 +25 63 21 63 63 63 +32 83 28 83 83 83 +50 28 14 28 28 28 +Testing AGCH messages queue handling. +AGCH filled: count 720, imm.ass 80, imm.ass.rej 640 (refs 640), queue limit 32, occupied 240, dropped 0, merged 480, rejected 0, ag-res 0, non-res 0 +AGCH drained: multiframes 32, imm.ass 80, imm.ass.rej 12 (refs 48), queue limit 32, occupied 0, dropped 148, merged 480, rejected 0, ag-res 31, non-res 61 +Success diff --git a/tests/cipher/Makefile.am b/tests/cipher/Makefile.am new file mode 100644 index 0000000..a671550 --- /dev/null +++ b/tests/cipher/Makefile.am @@ -0,0 +1,8 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(ORTP_CFLAGS) +LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCODEC_LIBS) $(ORTP_LIBS) +noinst_PROGRAMS = cipher_test +EXTRA_DIST = cipher_test.ok + +cipher_test_SOURCES = cipher_test.c $(srcdir)/../stubs.c +cipher_test_LDADD = $(top_builddir)/src/common/libbts.a $(LDADD) diff --git a/tests/cipher/cipher_test.c b/tests/cipher/cipher_test.c new file mode 100644 index 0000000..9d78a88 --- /dev/null +++ b/tests/cipher/cipher_test.c @@ -0,0 +1,85 @@ +/* (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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include +#include + +#include +#include + +static struct gsm_bts *bts; + +#define ASSERT_TRUE(rc) \ + if (!(rc)) { \ + printf("Assert failed in %s:%d.\n", \ + __FILE__, __LINE__); \ + abort(); \ + } + +static void test_cipher_parsing(void) +{ + int i; + + bts->support.ciphers = 0; + + /* always support A5/0 */ + ASSERT_TRUE(bts_supports_cipher(bts, 0x0) == -ENOTSUP); + ASSERT_TRUE(bts_supports_cipher(bts, 0x1) == 1); /* A5/0 */ + for (i = 2; i <= 8; ++i) { + ASSERT_TRUE(bts_supports_cipher(bts, i) == 0); + } + + /* checking default A5/1 to A5/3 support */ + bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3); + ASSERT_TRUE(bts_supports_cipher(bts, 0x0) == -ENOTSUP); + ASSERT_TRUE(bts_supports_cipher(bts, 0x1) == 1); /* A5/0 */ + ASSERT_TRUE(bts_supports_cipher(bts, 0x2) == 1); /* A5/1 */ + ASSERT_TRUE(bts_supports_cipher(bts, 0x3) == 1); /* A5/2 */ + ASSERT_TRUE(bts_supports_cipher(bts, 0x4) == 1); /* A5/3 */ + ASSERT_TRUE(bts_supports_cipher(bts, 0x5) == 0); /* A5/4 */ + ASSERT_TRUE(bts_supports_cipher(bts, 0x6) == 0); /* A5/5 */ + ASSERT_TRUE(bts_supports_cipher(bts, 0x7) == 0); /* A5/6 */ + ASSERT_TRUE(bts_supports_cipher(bts, 0x8) == 0); /* A5/7 */ + ASSERT_TRUE(bts_supports_cipher(bts, 0x9) == -ENOTSUP); +} + +int main(int argc, char **argv) +{ + tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context"); + msgb_talloc_ctx_init(tall_bts_ctx, 0); + + osmo_init_logging2(tall_bts_ctx, &bts_log_info); + + bts = gsm_bts_alloc(tall_bts_ctx, 0); + if (bts_init(bts) < 0) { + fprintf(stderr, "unable to open bts\n"); + exit(1); + } + + test_cipher_parsing(); + printf("Success\n"); + + return 0; +} + diff --git a/tests/cipher/cipher_test.ok b/tests/cipher/cipher_test.ok new file mode 100644 index 0000000..3582111 --- /dev/null +++ b/tests/cipher/cipher_test.ok @@ -0,0 +1 @@ +Success diff --git a/tests/handover/Makefile.am b/tests/handover/Makefile.am new file mode 100644 index 0000000..966ea46 --- /dev/null +++ b/tests/handover/Makefile.am @@ -0,0 +1,8 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) +LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) +noinst_PROGRAMS = handover_test +EXTRA_DIST = handover_test.ok + +handover_test_SOURCES = handover_test.c +handover_test_LDADD = $(top_builddir)/src/common/libbts.a $(LDADD) diff --git a/tests/handover/handover_test.c b/tests/handover/handover_test.c new file mode 100644 index 0000000..c7bd8f8 --- /dev/null +++ b/tests/handover/handover_test.c @@ -0,0 +1,278 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +uint8_t phys_info[] = { 0x03, 0x03, 0x0d, 0x06, 0x2d, 0x00, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b }; + +static struct gsm_bts *bts; +struct gsm_bts_trx *trx; +int quit = 0; +uint8_t abis_mac[6] = { 0, 1, 2, 3, 4, 5 }; +int modify_count = 0; + +static void expect_phys_info(struct lapdm_entity *le) +{ + struct osmo_phsap_prim pp; + int rc; + + rc = lapdm_phsap_dequeue_prim(le, &pp); + OSMO_ASSERT(rc == 0); + OSMO_ASSERT(sizeof(phys_info) == pp.oph.msg->len); + OSMO_ASSERT(!memcmp(phys_info, pp.oph.msg->data, pp.oph.msg->len)); + msgb_free(pp.oph.msg); +} + +int main(int argc, char **argv) +{ + void *tall_bts_ctx; + struct e1inp_line *line; + struct gsm_lchan *lchan; + struct osmo_phsap_prim nl1sap; + struct msgb *msg; + struct abis_rsl_dchan_hdr *rslh; + int i; + + tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context"); + msgb_talloc_ctx_init(tall_bts_ctx, 0); + + osmo_init_logging2(tall_bts_ctx, &bts_log_info); + osmo_stderr_target->categories[DHO].loglevel = LOGL_DEBUG; + + bts = gsm_bts_alloc(tall_bts_ctx, 0); + if (!bts) { + fprintf(stderr, "Failed to create BTS structure\n"); + exit(1); + } + trx = gsm_bts_trx_alloc(bts); + if (!trx) { + fprintf(stderr, "Failed to TRX structure\n"); + exit(1); + } + + if (bts_init(bts) < 0) { + fprintf(stderr, "unable to to open bts\n"); + exit(1); + } + + libosmo_abis_init(NULL); + + line = e1inp_line_create(0, "ipa"); + OSMO_ASSERT(line); + + e1inp_ts_config_sign(&line->ts[E1INP_SIGN_RSL-1], line); + trx->rsl_link = e1inp_sign_link_create(&line->ts[E1INP_SIGN_RSL-1], E1INP_SIGN_RSL, NULL, 0, 0); + OSMO_ASSERT(trx->rsl_link); + trx->rsl_link->trx = trx; + + fprintf(stderr, "test 1: without timeout\n"); + + /* create two lchans for handover */ + lchan = &trx->ts[1].lchan[0]; + lchan->type = GSM_LCHAN_SDCCH; + l1sap_chan_act(lchan->ts->trx, 0x09, NULL); + lchan = &trx->ts[2].lchan[0]; + lchan->type = GSM_LCHAN_TCH_F; + lchan->ho.active = HANDOVER_ENABLED; + lchan->ho.ref = 23; + l1sap_chan_act(lchan->ts->trx, 0x0a, NULL); + OSMO_ASSERT(msgb_dequeue(&trx->rsl_link->tx_list)); + OSMO_ASSERT(msgb_dequeue(&trx->rsl_link->tx_list)); + OSMO_ASSERT(!msgb_dequeue(&trx->rsl_link->tx_list)); + + /* send access burst with wrong ref */ + memset(&nl1sap, 0, sizeof(nl1sap)); + osmo_prim_init(&nl1sap.oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, NULL); + nl1sap.u.rach_ind.chan_nr = 0x0a; + nl1sap.u.rach_ind.ra = 42; + l1sap_up(trx, &nl1sap); + + /* expect no action */ + OSMO_ASSERT(modify_count == 0); + OSMO_ASSERT(!msgb_dequeue(&trx->rsl_link->tx_list)); + + /* send access burst with correct ref */ + nl1sap.u.rach_ind.ra = 23; + l1sap_up(trx, &nl1sap); + OSMO_ASSERT(modify_count == 1); + + /* expect PHYS INFO */ + expect_phys_info(&trx->ts[2].lchan[0].lapdm_ch.lapdm_dcch); + + /* expect exactly one HO.DET */ + OSMO_ASSERT(msg = msgb_dequeue(&trx->rsl_link->tx_list)); + rslh = msgb_l2(msg); + OSMO_ASSERT(rslh->c.msg_type == RSL_MT_HANDO_DET); + OSMO_ASSERT(!msgb_dequeue(&trx->rsl_link->tx_list)); + + /* expect T3105 running */ + OSMO_ASSERT(osmo_timer_pending(&trx->ts[2].lchan[0].ho.t3105)) + + /* indicate frame */ + handover_frame(&trx->ts[2].lchan[0]); + + /* expect T3105 not running */ + OSMO_ASSERT(!osmo_timer_pending(&trx->ts[2].lchan[0].ho.t3105)) + + fprintf(stderr, "test 2: with timeout\n"); + + /* enable handover again */ + lchan = &trx->ts[2].lchan[0]; + lchan->ho.active = HANDOVER_ENABLED; + lchan->ho.ref = 23; + modify_count = 0; + + /* send access burst with correct ref */ + nl1sap.u.rach_ind.ra = 23; + l1sap_up(trx, &nl1sap); + OSMO_ASSERT(modify_count == 1); + + /* expect PHYS INFO */ + expect_phys_info(&trx->ts[2].lchan[0].lapdm_ch.lapdm_dcch); + + /* expect exactly one HO.DET */ + OSMO_ASSERT(msg = msgb_dequeue(&trx->rsl_link->tx_list)); + rslh = msgb_l2(msg); + OSMO_ASSERT(rslh->c.msg_type == RSL_MT_HANDO_DET); + OSMO_ASSERT(!msgb_dequeue(&trx->rsl_link->tx_list)); + + for (i = 0; i < bts->ny1 - 1; i++) { + /* expect T3105 running */ + OSMO_ASSERT(osmo_timer_pending(&trx->ts[2].lchan[0].ho.t3105)) + + /* timeout T3105 */ + gettimeofday(&trx->ts[2].lchan[0].ho.t3105.timeout, NULL); + osmo_select_main(0); + + /* expect PHYS INFO */ + expect_phys_info(&trx->ts[2].lchan[0].lapdm_ch.lapdm_dcch); + } + + /* timeout T3105 */ + gettimeofday(&trx->ts[2].lchan[0].ho.t3105.timeout, NULL); + osmo_select_main(0); + + /* expect T3105 not running */ + OSMO_ASSERT(!osmo_timer_pending(&trx->ts[2].lchan[0].ho.t3105)) + + /* expect exactly one CONN.FAIL */ + OSMO_ASSERT(msg = msgb_dequeue(&trx->rsl_link->tx_list)); + rslh = msgb_l2(msg); + OSMO_ASSERT(rslh->c.msg_type == RSL_MT_CONN_FAIL); + OSMO_ASSERT(!msgb_dequeue(&trx->rsl_link->tx_list)); + +#if 0 + while (!quit) { + log_reset_context(); + osmo_select_main(0); + } +#endif + + printf("Success\n"); + + return 0; +} + +void bts_model_abis_close(struct gsm_bts *bts) +{ +} + +int bts_model_oml_estab(struct gsm_bts *bts) +{ + return 0; +} + +int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) +{ + int rc = 0; + uint8_t chan_nr; + uint8_t tn, ss; + struct gsm_lchan *lchan; + struct msgb *msg = l1sap->oph.msg; + struct osmo_phsap_prim nl1sap; + + switch (OSMO_PRIM_HDR(&l1sap->oph)) { + case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST): + switch (l1sap->u.info.type) { + case PRIM_INFO_ACTIVATE: + chan_nr = l1sap->u.info.u.act_req.chan_nr; + tn = L1SAP_CHAN2TS(chan_nr); + ss = l1sap_chan2ss(chan_nr); + lchan = &trx->ts[tn].lchan[ss]; + + lchan_init_lapdm(lchan); + + lchan_set_state(lchan, LCHAN_S_ACTIVE); + + memset(&nl1sap, 0, sizeof(nl1sap)); + osmo_prim_init(&nl1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_CONFIRM, NULL); + nl1sap.u.info.type = PRIM_INFO_ACTIVATE; + nl1sap.u.info.u.act_cnf.chan_nr = chan_nr; + return l1sap_up(trx, &nl1sap); + case PRIM_INFO_MODIFY: + modify_count++; + break; + default: + LOGP(DL1C, LOGL_NOTICE, "unknown MPH-INFO.req %d\n", + l1sap->u.info.type); + rc = -EINVAL; + goto done; + } + break; + default: + LOGP(DL1C, LOGL_NOTICE, "unknown prim %d op %d\n", + l1sap->oph.primitive, l1sap->oph.operation); + rc = -EINVAL; + goto done; + } + +done: + if (msg) + msgb_free(msg); + return rc; +} + +int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, struct tlv_parsed *old_attr, struct tlv_parsed *new_attr, void *obj) { return 0; } +int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, struct tlv_parsed *new_attr, int obj_kind, void *obj) { return 0; } +int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj) { return 0; } +int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj, uint8_t adm_state) { return 0; } +int bts_model_init(struct gsm_bts *bts) { return 0; } +int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) { return 0; } +int bts_model_trx_close(struct gsm_bts_trx *trx) { return 0; } +void trx_get_hlayer1(void) {} +int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) { return 0; } +int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) { return 0; } +int bts_model_ts_connect(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config as_pchan) { return 0; } +int bts_model_lchan_deactivate(struct gsm_lchan *lchan) { return 0; } +int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan) { return 0; } diff --git a/tests/handover/handover_test.ok b/tests/handover/handover_test.ok new file mode 100644 index 0000000..3582111 --- /dev/null +++ b/tests/handover/handover_test.ok @@ -0,0 +1 @@ +Success diff --git a/tests/meas/Makefile.am b/tests/meas/Makefile.am new file mode 100644 index 0000000..3c83e52 --- /dev/null +++ b/tests/meas/Makefile.am @@ -0,0 +1,9 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) +LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) +noinst_PROGRAMS = meas_test +noinst_HEADERS = sysmobts_fr_samples.h +EXTRA_DIST = meas_test.ok + +meas_test_SOURCES = meas_test.c +meas_test_LDADD = $(top_builddir)/src/common/libbts.a $(LDADD) diff --git a/tests/meas/meas_test.c b/tests/meas/meas_test.c new file mode 100644 index 0000000..cbc673f --- /dev/null +++ b/tests/meas/meas_test.c @@ -0,0 +1,197 @@ +#include +#include + +#include +#include + +#include +#include +#include +#include + +static struct gsm_bts *bts; +struct gsm_bts_trx *trx; + +struct fn_sample { + uint32_t fn; + uint8_t ts; + uint8_t ss; + int rc; +}; + +#include "sysmobts_fr_samples.h" + +void test_fn_sample(struct fn_sample *s, unsigned int len, uint8_t pchan, uint8_t tsmap) +{ + int rc; + struct gsm_lchan *lchan; + unsigned int i; + unsigned int delta = 0; + uint8_t tsmap_result = 0; + uint32_t fn_prev = 0; + struct gsm_time gsm_time; + + + printf("\n\n"); + printf("===========================================================\n"); + + for (i = 0; i < len; i++) { + + lchan = &trx->ts[s[i].ts].lchan[s[i].ss]; + trx->ts[s[i].ts].pchan = pchan; + lchan->meas.num_ul_meas = 1; + + rc = lchan_meas_check_compute(lchan, s[i].fn); + if (rc) { + gsm_fn2gsmtime(&gsm_time, s[i].fn); + fprintf(stdout, "Testing: ts[%i]->lchan[%i], fn=%u=>%s, fn%%104=%u, rc=%i, delta=%i\n", s[i].ts, + s[i].ss, s[i].fn, osmo_dump_gsmtime(&gsm_time), s[i].fn % 104, rc, s[i].fn - fn_prev); + fn_prev = s[i].fn; + tsmap_result |= (1 << s[i].ts); + } else + delta++; + + /* If the test data set provides a return + * code, we check that as well */ + if (s[i].rc != -1) + OSMO_ASSERT(s[i].rc == rc); + } + + /* Make sure that we exactly trigger on the right frames + * timeslots must match exactlty to what we expect */ + OSMO_ASSERT(tsmap_result == tsmap); +} + +int main(int argc, char **argv) +{ + void *tall_bts_ctx; + + tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context"); + msgb_talloc_ctx_init(tall_bts_ctx, 0); + + osmo_init_logging2(tall_bts_ctx, &bts_log_info); + osmo_stderr_target->categories[DMEAS].loglevel = LOGL_DEBUG; + + bts = gsm_bts_alloc(tall_bts_ctx, 0); + if (!bts) { + fprintf(stderr, "Failed to create BTS structure\n"); + exit(1); + } + trx = gsm_bts_trx_alloc(bts); + if (!trx) { + fprintf(stderr, "Failed to TRX structure\n"); + exit(1); + } + + if (bts_init(bts) < 0) { + fprintf(stderr, "unable to to open bts\n"); + exit(1); + } + + printf("\n"); + printf("***********************\n"); + printf("*** FULL RATE TESTS ***\n"); + printf("***********************\n"); + + /* Test full rate */ + test_fn_sample(test_fn_tch_f_ts_2_3, ARRAY_SIZE(test_fn_tch_f_ts_2_3), GSM_PCHAN_TCH_F, (1 << 2) | (1 << 3)); + test_fn_sample(test_fn_tch_f_ts_4_5, ARRAY_SIZE(test_fn_tch_f_ts_4_5), GSM_PCHAN_TCH_F, (1 << 4) | (1 << 5)); + test_fn_sample(test_fn_tch_f_ts_6_7, ARRAY_SIZE(test_fn_tch_f_ts_6_7), GSM_PCHAN_TCH_F, (1 << 6) | (1 << 7)); + + printf("\n"); + printf("***********************\n"); + printf("*** HALF RATE TESTS ***\n"); + printf("***********************\n"); + + /* Test half rate */ + test_fn_sample(test_fn_tch_h_ts_2_ss0_ss1, ARRAY_SIZE(test_fn_tch_h_ts_2_ss0_ss1), GSM_PCHAN_TCH_H, (1 << 2)); + test_fn_sample(test_fn_tch_h_ts_3_ss0_ss1, ARRAY_SIZE(test_fn_tch_h_ts_3_ss0_ss1), GSM_PCHAN_TCH_H, (1 << 3)); + test_fn_sample(test_fn_tch_h_ts_4_ss0_ss1, ARRAY_SIZE(test_fn_tch_h_ts_4_ss0_ss1), GSM_PCHAN_TCH_H, (1 << 4)); + test_fn_sample(test_fn_tch_h_ts_5_ss0_ss1, ARRAY_SIZE(test_fn_tch_h_ts_5_ss0_ss1), GSM_PCHAN_TCH_H, (1 << 5)); + test_fn_sample(test_fn_tch_h_ts_6_ss0_ss1, ARRAY_SIZE(test_fn_tch_h_ts_6_ss0_ss1), GSM_PCHAN_TCH_H, (1 << 6)); + test_fn_sample(test_fn_tch_h_ts_7_ss0_ss1, ARRAY_SIZE(test_fn_tch_h_ts_7_ss0_ss1), GSM_PCHAN_TCH_H, (1 << 7)); + + printf("Success\n"); + + return 0; +} + +/* Stubs */ +void bts_model_abis_close(struct gsm_bts *bts) +{ +} + +int bts_model_oml_estab(struct gsm_bts *bts) +{ + return 0; +} + +int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) +{ + return 0; +} + +int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, struct tlv_parsed *old_attr, struct tlv_parsed *new_attr, + void *obj) +{ + return 0; +} + +int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, struct tlv_parsed *new_attr, int obj_kind, void *obj) +{ + return 0; +} + +int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj) +{ + return 0; +} + +int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo, void *obj, uint8_t adm_state) +{ + return 0; +} + +int bts_model_init(struct gsm_bts *bts) +{ + return 0; +} + +int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) +{ + return 0; +} + +int bts_model_trx_close(struct gsm_bts_trx *trx) +{ + return 0; +} + +void trx_get_hlayer1(void) +{ +} + +int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) +{ + return 0; +} + +int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) +{ + return 0; +} + +int bts_model_ts_connect(struct gsm_bts_trx_ts *ts, enum gsm_phys_chan_config as_pchan) +{ + return 0; +} + +int bts_model_lchan_deactivate(struct gsm_lchan *lchan) +{ + return 0; +} + +int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan) +{ + return 0; +} diff --git a/tests/meas/meas_test.ok b/tests/meas/meas_test.ok new file mode 100644 index 0000000..6dbda54 --- /dev/null +++ b/tests/meas/meas_test.ok @@ -0,0 +1,542 @@ + +*********************** +*** FULL RATE TESTS *** +*********************** + + +=========================================================== +Testing: ts[2]->lchan[0], fn=10958=>010958/08/12/44/50, fn%104=38, rc=1, delta=10958 +Testing: ts[2]->lchan[0], fn=11062=>011062/08/12/46/02, fn%104=38, rc=1, delta=104 +Testing: ts[2]->lchan[0], fn=11166=>011166/08/12/48/02, fn%104=38, rc=1, delta=104 +Testing: ts[2]->lchan[0], fn=11270=>011270/08/12/50/06, fn%104=38, rc=1, delta=104 +Testing: ts[2]->lchan[0], fn=11374=>011374/08/12/01/06, fn%104=38, rc=1, delta=104 +Testing: ts[2]->lchan[0], fn=11478=>011478/08/12/03/06, fn%104=38, rc=1, delta=104 +Testing: ts[3]->lchan[0], fn=11491=>011491/08/25/16/19, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=11582=>011582/08/12/05/10, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[0], fn=11595=>011595/08/25/18/23, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=11686=>011686/08/12/07/10, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[0], fn=11699=>011699/08/25/20/23, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=11790=>011790/08/12/09/14, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[0], fn=11803=>011803/08/25/22/27, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=11894=>011894/08/12/11/14, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[0], fn=11907=>011907/08/25/24/27, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=11998=>011998/09/12/13/14, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[0], fn=12011=>012011/09/25/26/27, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=12102=>012102/09/12/15/18, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[0], fn=12115=>012115/09/25/28/31, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=12206=>012206/09/12/17/18, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[0], fn=12219=>012219/09/25/30/31, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=12310=>012310/09/12/19/22, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[0], fn=12323=>012323/09/25/32/35, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=12414=>012414/09/12/21/22, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[0], fn=12427=>012427/09/25/34/35, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=12518=>012518/09/12/23/22, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[0], fn=12531=>012531/09/25/36/35, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=12622=>012622/09/12/25/26, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[0], fn=12635=>012635/09/25/38/39, fn%104=51, rc=1, delta=13 + + +=========================================================== +Testing: ts[4]->lchan[0], fn=5888=>005888/04/12/23/00, fn%104=64, rc=1, delta=5888 +Testing: ts[4]->lchan[0], fn=5992=>005992/04/12/25/00, fn%104=64, rc=1, delta=104 +Testing: ts[4]->lchan[0], fn=6096=>006096/04/12/27/00, fn%104=64, rc=1, delta=104 +Testing: ts[4]->lchan[0], fn=6200=>006200/04/12/29/04, fn%104=64, rc=1, delta=104 +Testing: ts[5]->lchan[0], fn=6213=>006213/04/25/42/17, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=6304=>006304/04/12/31/04, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=6317=>006317/04/25/44/17, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=6408=>006408/04/12/33/08, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=6421=>006421/04/25/46/21, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=6512=>006512/04/12/35/08, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=6525=>006525/04/25/48/21, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=6616=>006616/04/12/37/08, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=6629=>006629/04/25/50/21, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=6720=>006720/05/12/39/12, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=6733=>006733/05/25/01/25, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=6824=>006824/05/12/41/12, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=6837=>006837/05/25/03/25, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=6928=>006928/05/12/43/16, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=6941=>006941/05/25/05/29, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=7032=>007032/05/12/45/16, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=7045=>007045/05/25/07/29, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=7136=>007136/05/12/47/16, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=7149=>007149/05/25/09/29, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=7240=>007240/05/12/49/20, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=7253=>007253/05/25/11/33, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=7344=>007344/05/12/00/20, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=7357=>007357/05/25/13/33, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=7448=>007448/05/12/02/24, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=7461=>007461/05/25/15/37, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=7552=>007552/05/12/04/24, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=7565=>007565/05/25/17/37, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=7656=>007656/05/12/06/24, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=7669=>007669/05/25/19/37, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=7760=>007760/05/12/08/28, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=7773=>007773/05/25/21/41, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=7864=>007864/05/12/10/28, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=7877=>007877/05/25/23/41, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=7968=>007968/06/12/12/32, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=7981=>007981/06/25/25/45, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8072=>008072/06/12/14/32, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=8085=>008085/06/25/27/45, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8176=>008176/06/12/16/32, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=8189=>008189/06/25/29/45, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8280=>008280/06/12/18/36, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=8293=>008293/06/25/31/49, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8384=>008384/06/12/20/36, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=8397=>008397/06/25/33/49, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8488=>008488/06/12/22/40, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=8501=>008501/06/25/35/01, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8592=>008592/06/12/24/40, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=8605=>008605/06/25/37/01, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8696=>008696/06/12/26/40, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=8709=>008709/06/25/39/05, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8800=>008800/06/12/28/44, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=8813=>008813/06/25/41/05, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8904=>008904/06/12/30/44, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=8917=>008917/06/25/43/05, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9008=>009008/06/12/32/48, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=9021=>009021/06/25/45/09, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9112=>009112/06/12/34/48, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=9125=>009125/06/25/47/09, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9216=>009216/06/12/36/00, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=9229=>009229/06/25/49/13, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9320=>009320/07/12/38/00, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=9333=>009333/07/25/00/13, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9424=>009424/07/12/40/00, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=9437=>009437/07/25/02/13, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9528=>009528/07/12/42/04, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=9541=>009541/07/25/04/17, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9632=>009632/07/12/44/04, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[0], fn=9645=>009645/07/25/06/17, fn%104=77, rc=1, delta=13 + + +=========================================================== +Testing: ts[6]->lchan[0], fn=8618=>008618/06/12/50/14, fn%104=90, rc=1, delta=8618 +Testing: ts[6]->lchan[0], fn=8722=>008722/06/12/01/18, fn%104=90, rc=1, delta=104 +Testing: ts[6]->lchan[0], fn=8826=>008826/06/12/03/18, fn%104=90, rc=1, delta=104 +Testing: ts[6]->lchan[0], fn=8930=>008930/06/12/05/18, fn%104=90, rc=1, delta=104 +Testing: ts[7]->lchan[0], fn=8943=>008943/06/25/18/31, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9034=>009034/06/12/07/22, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=9047=>009047/06/25/20/35, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9138=>009138/06/12/09/22, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=9151=>009151/06/25/22/35, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9242=>009242/06/12/11/26, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=9255=>009255/06/25/24/39, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9346=>009346/07/12/13/26, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=9359=>009359/07/25/26/39, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9450=>009450/07/12/15/26, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=9463=>009463/07/25/28/39, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9554=>009554/07/12/17/30, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=9567=>009567/07/25/30/43, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9658=>009658/07/12/19/30, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=9671=>009671/07/25/32/43, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9762=>009762/07/12/21/34, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=9775=>009775/07/25/34/47, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9866=>009866/07/12/23/34, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=9879=>009879/07/25/36/47, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9970=>009970/07/12/25/34, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=9983=>009983/07/25/38/47, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10074=>010074/07/12/27/38, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=10087=>010087/07/25/40/51, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10178=>010178/07/12/29/38, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=10191=>010191/07/25/42/51, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10282=>010282/07/12/31/42, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=10295=>010295/07/25/44/03, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10386=>010386/07/12/33/42, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=10399=>010399/07/25/46/03, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10490=>010490/07/12/35/42, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=10503=>010503/07/25/48/07, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10594=>010594/07/12/37/46, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=10607=>010607/07/25/50/07, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10698=>010698/08/12/39/46, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=10711=>010711/08/25/01/07, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10802=>010802/08/12/41/50, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=10815=>010815/08/25/03/11, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10906=>010906/08/12/43/50, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=10919=>010919/08/25/05/11, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11010=>011010/08/12/45/02, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=11023=>011023/08/25/07/15, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11114=>011114/08/12/47/02, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=11127=>011127/08/25/09/15, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11218=>011218/08/12/49/02, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=11231=>011231/08/25/11/15, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11322=>011322/08/12/00/06, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=11335=>011335/08/25/13/19, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11426=>011426/08/12/02/06, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=11439=>011439/08/25/15/19, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11530=>011530/08/12/04/10, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=11543=>011543/08/25/17/23, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11634=>011634/08/12/06/10, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=11647=>011647/08/25/19/23, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11738=>011738/08/12/08/10, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=11751=>011751/08/25/21/23, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11842=>011842/08/12/10/14, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=11855=>011855/08/25/23/27, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11946=>011946/09/12/12/14, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=11959=>011959/09/25/25/27, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=12050=>012050/09/12/14/18, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=12063=>012063/09/25/27/31, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=12154=>012154/09/12/16/18, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[0], fn=12167=>012167/09/25/29/31, fn%104=103, rc=1, delta=13 + +*********************** +*** HALF RATE TESTS *** +*********************** + + +=========================================================== +Testing: ts[2]->lchan[0], fn=8982=>008982/06/12/06/22, fn%104=38, rc=1, delta=8982 +Testing: ts[2]->lchan[0], fn=9086=>009086/06/12/08/22, fn%104=38, rc=1, delta=104 +Testing: ts[2]->lchan[0], fn=9190=>009190/06/12/10/22, fn%104=38, rc=1, delta=104 +Testing: ts[2]->lchan[0], fn=9294=>009294/07/12/12/26, fn%104=38, rc=1, delta=104 +Testing: ts[2]->lchan[0], fn=9398=>009398/07/12/14/26, fn%104=38, rc=1, delta=104 +Testing: ts[2]->lchan[1], fn=9411=>009411/07/25/27/39, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=9502=>009502/07/12/16/30, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=9515=>009515/07/25/29/43, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=9606=>009606/07/12/18/30, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=9619=>009619/07/25/31/43, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=9710=>009710/07/12/20/30, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=9723=>009723/07/25/33/43, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=9814=>009814/07/12/22/34, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=9827=>009827/07/25/35/47, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=9918=>009918/07/12/24/34, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=9931=>009931/07/25/37/47, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=10022=>010022/07/12/26/38, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=10035=>010035/07/25/39/51, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=10126=>010126/07/12/28/38, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=10139=>010139/07/25/41/51, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=10230=>010230/07/12/30/38, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=10243=>010243/07/25/43/03, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=10334=>010334/07/12/32/42, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=10347=>010347/07/25/45/03, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=10438=>010438/07/12/34/42, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=10451=>010451/07/25/47/03, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=10542=>010542/07/12/36/46, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=10555=>010555/07/25/49/07, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=10646=>010646/08/12/38/46, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=10659=>010659/08/25/00/07, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=10750=>010750/08/12/40/46, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=10763=>010763/08/25/02/11, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=10854=>010854/08/12/42/50, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=10867=>010867/08/25/04/11, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=10958=>010958/08/12/44/50, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=10971=>010971/08/25/06/11, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=11062=>011062/08/12/46/02, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=11075=>011075/08/25/08/15, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=11166=>011166/08/12/48/02, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=11179=>011179/08/25/10/15, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=11270=>011270/08/12/50/06, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=11283=>011283/08/25/12/19, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=11374=>011374/08/12/01/06, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=11387=>011387/08/25/14/19, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=11478=>011478/08/12/03/06, fn%104=38, rc=1, delta=91 +Testing: ts[2]->lchan[1], fn=11491=>011491/08/25/16/19, fn%104=51, rc=1, delta=13 +Testing: ts[2]->lchan[0], fn=11582=>011582/08/12/05/10, fn%104=38, rc=1, delta=91 + + +=========================================================== +Testing: ts[3]->lchan[0], fn=10022=>010022/07/12/26/38, fn%104=38, rc=1, delta=10022 +Testing: ts[3]->lchan[1], fn=10035=>010035/07/25/39/51, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=10126=>010126/07/12/28/38, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=10139=>010139/07/25/41/51, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=10230=>010230/07/12/30/38, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=10243=>010243/07/25/43/03, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=10334=>010334/07/12/32/42, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=10347=>010347/07/25/45/03, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=10438=>010438/07/12/34/42, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=10451=>010451/07/25/47/03, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=10542=>010542/07/12/36/46, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=10555=>010555/07/25/49/07, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=10646=>010646/08/12/38/46, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=10659=>010659/08/25/00/07, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=10750=>010750/08/12/40/46, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=10763=>010763/08/25/02/11, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=10854=>010854/08/12/42/50, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=10867=>010867/08/25/04/11, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=10958=>010958/08/12/44/50, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=10971=>010971/08/25/06/11, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=11062=>011062/08/12/46/02, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=11075=>011075/08/25/08/15, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=11166=>011166/08/12/48/02, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=11179=>011179/08/25/10/15, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=11270=>011270/08/12/50/06, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=11283=>011283/08/25/12/19, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=11374=>011374/08/12/01/06, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=11387=>011387/08/25/14/19, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=11478=>011478/08/12/03/06, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=11491=>011491/08/25/16/19, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=11582=>011582/08/12/05/10, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=11595=>011595/08/25/18/23, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=11686=>011686/08/12/07/10, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=11699=>011699/08/25/20/23, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=11790=>011790/08/12/09/14, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=11803=>011803/08/25/22/27, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=11894=>011894/08/12/11/14, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=11907=>011907/08/25/24/27, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=11998=>011998/09/12/13/14, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=12011=>012011/09/25/26/27, fn%104=51, rc=1, delta=13 +Testing: ts[3]->lchan[0], fn=12102=>012102/09/12/15/18, fn%104=38, rc=1, delta=91 +Testing: ts[3]->lchan[1], fn=12115=>012115/09/25/28/31, fn%104=51, rc=1, delta=13 + + +=========================================================== +Testing: ts[4]->lchan[0], fn=7760=>007760/05/12/08/28, fn%104=64, rc=1, delta=7760 +Testing: ts[4]->lchan[0], fn=7864=>007864/05/12/10/28, fn%104=64, rc=1, delta=104 +Testing: ts[4]->lchan[0], fn=7968=>007968/06/12/12/32, fn%104=64, rc=1, delta=104 +Testing: ts[4]->lchan[0], fn=8072=>008072/06/12/14/32, fn%104=64, rc=1, delta=104 +Testing: ts[4]->lchan[0], fn=8176=>008176/06/12/16/32, fn%104=64, rc=1, delta=104 +Testing: ts[4]->lchan[1], fn=8189=>008189/06/25/29/45, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8280=>008280/06/12/18/36, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=8293=>008293/06/25/31/49, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8384=>008384/06/12/20/36, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=8397=>008397/06/25/33/49, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8488=>008488/06/12/22/40, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=8501=>008501/06/25/35/01, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8592=>008592/06/12/24/40, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=8605=>008605/06/25/37/01, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8696=>008696/06/12/26/40, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=8709=>008709/06/25/39/05, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8800=>008800/06/12/28/44, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=8813=>008813/06/25/41/05, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=8904=>008904/06/12/30/44, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=8917=>008917/06/25/43/05, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9008=>009008/06/12/32/48, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=9021=>009021/06/25/45/09, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9112=>009112/06/12/34/48, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=9125=>009125/06/25/47/09, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9216=>009216/06/12/36/00, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=9229=>009229/06/25/49/13, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9320=>009320/07/12/38/00, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=9333=>009333/07/25/00/13, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9424=>009424/07/12/40/00, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=9437=>009437/07/25/02/13, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9528=>009528/07/12/42/04, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=9541=>009541/07/25/04/17, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9632=>009632/07/12/44/04, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=9645=>009645/07/25/06/17, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9736=>009736/07/12/46/08, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=9749=>009749/07/25/08/21, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9840=>009840/07/12/48/08, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=9853=>009853/07/25/10/21, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=9944=>009944/07/12/50/08, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=9957=>009957/07/25/12/21, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=10048=>010048/07/12/01/12, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=10061=>010061/07/25/14/25, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=10152=>010152/07/12/03/12, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=10165=>010165/07/25/16/25, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=10256=>010256/07/12/05/16, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=10269=>010269/07/25/18/29, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=10360=>010360/07/12/07/16, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=10373=>010373/07/25/20/29, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=10464=>010464/07/12/09/16, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=10477=>010477/07/25/22/29, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=10568=>010568/07/12/11/20, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=10581=>010581/07/25/24/33, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=10672=>010672/08/12/13/20, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=10685=>010685/08/25/26/33, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=10776=>010776/08/12/15/24, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=10789=>010789/08/25/28/37, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=10880=>010880/08/12/17/24, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=10893=>010893/08/25/30/37, fn%104=77, rc=1, delta=13 +Testing: ts[4]->lchan[0], fn=10984=>010984/08/12/19/24, fn%104=64, rc=1, delta=91 +Testing: ts[4]->lchan[1], fn=10997=>010997/08/25/32/37, fn%104=77, rc=1, delta=13 + + +=========================================================== +Testing: ts[5]->lchan[0], fn=5264=>005264/03/12/11/40, fn%104=64, rc=1, delta=5264 +Testing: ts[5]->lchan[0], fn=5368=>005368/04/12/13/40, fn%104=64, rc=1, delta=104 +Testing: ts[5]->lchan[0], fn=5472=>005472/04/12/15/44, fn%104=64, rc=1, delta=104 +Testing: ts[5]->lchan[0], fn=5576=>005576/04/12/17/44, fn%104=64, rc=1, delta=104 +Testing: ts[5]->lchan[0], fn=5680=>005680/04/12/19/48, fn%104=64, rc=1, delta=104 +Testing: ts[5]->lchan[1], fn=5693=>005693/04/25/32/09, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=5784=>005784/04/12/21/48, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=5797=>005797/04/25/34/09, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=5888=>005888/04/12/23/00, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=5901=>005901/04/25/36/13, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=5992=>005992/04/12/25/00, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=6005=>006005/04/25/38/13, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=6096=>006096/04/12/27/00, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=6109=>006109/04/25/40/13, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=6200=>006200/04/12/29/04, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=6213=>006213/04/25/42/17, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=6304=>006304/04/12/31/04, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=6317=>006317/04/25/44/17, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=6408=>006408/04/12/33/08, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=6421=>006421/04/25/46/21, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=6512=>006512/04/12/35/08, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=6525=>006525/04/25/48/21, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=6616=>006616/04/12/37/08, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=6629=>006629/04/25/50/21, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=6720=>006720/05/12/39/12, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=6733=>006733/05/25/01/25, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=6824=>006824/05/12/41/12, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=6837=>006837/05/25/03/25, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=6928=>006928/05/12/43/16, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=6941=>006941/05/25/05/29, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=7032=>007032/05/12/45/16, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=7045=>007045/05/25/07/29, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=7136=>007136/05/12/47/16, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=7149=>007149/05/25/09/29, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=7240=>007240/05/12/49/20, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=7253=>007253/05/25/11/33, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=7344=>007344/05/12/00/20, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=7357=>007357/05/25/13/33, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=7448=>007448/05/12/02/24, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=7461=>007461/05/25/15/37, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=7552=>007552/05/12/04/24, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=7565=>007565/05/25/17/37, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=7656=>007656/05/12/06/24, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=7669=>007669/05/25/19/37, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=7760=>007760/05/12/08/28, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=7773=>007773/05/25/21/41, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=7864=>007864/05/12/10/28, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=7877=>007877/05/25/23/41, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=7968=>007968/06/12/12/32, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=7981=>007981/06/25/25/45, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=8072=>008072/06/12/14/32, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=8085=>008085/06/25/27/45, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=8176=>008176/06/12/16/32, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=8189=>008189/06/25/29/45, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=8280=>008280/06/12/18/36, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=8293=>008293/06/25/31/49, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=8384=>008384/06/12/20/36, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=8397=>008397/06/25/33/49, fn%104=77, rc=1, delta=13 +Testing: ts[5]->lchan[0], fn=8488=>008488/06/12/22/40, fn%104=64, rc=1, delta=91 +Testing: ts[5]->lchan[1], fn=8501=>008501/06/25/35/01, fn%104=77, rc=1, delta=13 + + +=========================================================== +Testing: ts[6]->lchan[0], fn=8098=>008098/06/12/40/06, fn%104=90, rc=1, delta=8098 +Testing: ts[6]->lchan[0], fn=8202=>008202/06/12/42/10, fn%104=90, rc=1, delta=104 +Testing: ts[6]->lchan[0], fn=8306=>008306/06/12/44/10, fn%104=90, rc=1, delta=104 +Testing: ts[6]->lchan[0], fn=8410=>008410/06/12/46/10, fn%104=90, rc=1, delta=104 +Testing: ts[6]->lchan[1], fn=8423=>008423/06/25/08/23, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=8514=>008514/06/12/48/14, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=8527=>008527/06/25/10/27, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=8618=>008618/06/12/50/14, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=8631=>008631/06/25/12/27, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=8722=>008722/06/12/01/18, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=8735=>008735/06/25/14/31, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=8826=>008826/06/12/03/18, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=8839=>008839/06/25/16/31, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=8930=>008930/06/12/05/18, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=8943=>008943/06/25/18/31, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9034=>009034/06/12/07/22, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=9047=>009047/06/25/20/35, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9138=>009138/06/12/09/22, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=9151=>009151/06/25/22/35, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9242=>009242/06/12/11/26, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=9255=>009255/06/25/24/39, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9346=>009346/07/12/13/26, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=9359=>009359/07/25/26/39, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9450=>009450/07/12/15/26, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=9463=>009463/07/25/28/39, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9554=>009554/07/12/17/30, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=9567=>009567/07/25/30/43, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9658=>009658/07/12/19/30, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=9671=>009671/07/25/32/43, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9762=>009762/07/12/21/34, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=9775=>009775/07/25/34/47, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9866=>009866/07/12/23/34, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=9879=>009879/07/25/36/47, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=9970=>009970/07/12/25/34, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=9983=>009983/07/25/38/47, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10074=>010074/07/12/27/38, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=10087=>010087/07/25/40/51, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10178=>010178/07/12/29/38, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=10191=>010191/07/25/42/51, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10282=>010282/07/12/31/42, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=10295=>010295/07/25/44/03, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10386=>010386/07/12/33/42, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=10399=>010399/07/25/46/03, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10490=>010490/07/12/35/42, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=10503=>010503/07/25/48/07, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10594=>010594/07/12/37/46, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=10607=>010607/07/25/50/07, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10698=>010698/08/12/39/46, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=10711=>010711/08/25/01/07, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10802=>010802/08/12/41/50, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=10815=>010815/08/25/03/11, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=10906=>010906/08/12/43/50, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=10919=>010919/08/25/05/11, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11010=>011010/08/12/45/02, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=11023=>011023/08/25/07/15, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11114=>011114/08/12/47/02, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=11127=>011127/08/25/09/15, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11218=>011218/08/12/49/02, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=11231=>011231/08/25/11/15, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11322=>011322/08/12/00/06, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=11335=>011335/08/25/13/19, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11426=>011426/08/12/02/06, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=11439=>011439/08/25/15/19, fn%104=103, rc=1, delta=13 +Testing: ts[6]->lchan[0], fn=11530=>011530/08/12/04/10, fn%104=90, rc=1, delta=91 +Testing: ts[6]->lchan[1], fn=11543=>011543/08/25/17/23, fn%104=103, rc=1, delta=13 + + +=========================================================== +Testing: ts[7]->lchan[0], fn=11738=>011738/08/12/08/10, fn%104=90, rc=1, delta=11738 +Testing: ts[7]->lchan[0], fn=11842=>011842/08/12/10/14, fn%104=90, rc=1, delta=104 +Testing: ts[7]->lchan[0], fn=11946=>011946/09/12/12/14, fn%104=90, rc=1, delta=104 +Testing: ts[7]->lchan[0], fn=12050=>012050/09/12/14/18, fn%104=90, rc=1, delta=104 +Testing: ts[7]->lchan[1], fn=12063=>012063/09/25/27/31, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=12154=>012154/09/12/16/18, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=12167=>012167/09/25/29/31, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=12258=>012258/09/12/18/18, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=12271=>012271/09/25/31/31, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=12362=>012362/09/12/20/22, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=12375=>012375/09/25/33/35, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=12466=>012466/09/12/22/22, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=12479=>012479/09/25/35/35, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=12570=>012570/09/12/24/26, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=12583=>012583/09/25/37/39, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=12674=>012674/09/12/26/26, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=12687=>012687/09/25/39/39, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=12778=>012778/09/12/28/26, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=12791=>012791/09/25/41/39, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=12882=>012882/09/12/30/30, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=12895=>012895/09/25/43/43, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=12986=>012986/09/12/32/30, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=12999=>012999/09/25/45/43, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=13090=>013090/09/12/34/34, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=13103=>013103/09/25/47/47, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=13194=>013194/09/12/36/34, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=13207=>013207/09/25/49/47, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=13298=>013298/10/12/38/34, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=13311=>013311/10/25/00/47, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=13402=>013402/10/12/40/38, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=13415=>013415/10/25/02/51, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=13506=>013506/10/12/42/38, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=13519=>013519/10/25/04/51, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=13610=>013610/10/12/44/42, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=13623=>013623/10/25/06/03, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=13714=>013714/10/12/46/42, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=13727=>013727/10/25/08/03, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=13818=>013818/10/12/48/42, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=13831=>013831/10/25/10/07, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=13922=>013922/10/12/50/46, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=13935=>013935/10/25/12/07, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=14026=>014026/10/12/01/46, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=14039=>014039/10/25/14/07, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=14130=>014130/10/12/03/50, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=14143=>014143/10/25/16/11, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=14234=>014234/10/12/05/50, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=14247=>014247/10/25/18/11, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=14338=>014338/10/12/07/02, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=14351=>014351/10/25/20/15, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=14442=>014442/10/12/09/02, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=14455=>014455/10/25/22/15, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=14546=>014546/10/12/11/02, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=14559=>014559/10/25/24/15, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=14650=>014650/11/12/13/06, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=14663=>014663/11/25/26/19, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=14754=>014754/11/12/15/06, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=14767=>014767/11/25/28/19, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=14858=>014858/11/12/17/10, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=14871=>014871/11/25/30/23, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=14962=>014962/11/12/19/10, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=14975=>014975/11/25/32/23, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=15066=>015066/11/12/21/10, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=15079=>015079/11/25/34/23, fn%104=103, rc=1, delta=13 +Testing: ts[7]->lchan[0], fn=15170=>015170/11/12/23/14, fn%104=90, rc=1, delta=91 +Testing: ts[7]->lchan[1], fn=15183=>015183/11/25/36/27, fn%104=103, rc=1, delta=13 +Success diff --git a/tests/meas/sysmobts_fr_samples.h b/tests/meas/sysmobts_fr_samples.h new file mode 100644 index 0000000..ee70bd7 --- /dev/null +++ b/tests/meas/sysmobts_fr_samples.h @@ -0,0 +1,2601 @@ +/* The following dataset was generated using a sysmobts in order to have + * some real data from a real phy to test against. */ + +/* Frame number data sampled from measurement.c:lchan_meas_check_compute() + * Call was made between to phones on full rate channels TS2 and TS3 */ +struct fn_sample test_fn_tch_f_ts_2_3[] = { +{10954,2,0,-1},{10959,2,0,-1},{10972,2,0,-1},{10976,2,0,-1},{10980,2,0,-1}, +{10985,2,0,-1},{10989,2,0,-1},{10993,2,0,-1},{10998,2,0,-1},{11002,2,0,-1}, +{11006,2,0,-1},{11011,2,0,-1},{11015,2,0,-1},{11019,2,0,-1},{11024,2,0,-1}, +{11028,2,0,-1},{10958,2,0,-1},{11032,2,0,-1},{11037,2,0,-1},{11041,2,0,-1}, +{11045,2,0,-1},{11050,2,0,-1},{11054,2,0,-1},{11058,2,0,-1},{11063,2,0,-1}, +{11067,2,0,-1},{11071,2,0,-1},{11076,2,0,-1},{11080,2,0,-1},{11084,2,0,-1}, +{11089,2,0,-1},{11093,2,0,-1},{11097,2,0,-1},{11102,2,0,-1},{11106,2,0,-1}, +{11110,2,0,-1},{11115,2,0,-1},{11119,2,0,-1},{11123,2,0,-1},{11128,2,0,-1}, +{11132,2,0,-1},{11062,2,0,-1},{11136,2,0,-1},{11141,2,0,-1},{11145,2,0,-1}, +{11149,2,0,-1},{11154,2,0,-1},{11158,2,0,-1},{11162,2,0,-1},{11167,2,0,-1}, +{11171,2,0,-1},{11175,2,0,-1},{11180,2,0,-1},{11184,2,0,-1},{11188,2,0,-1}, +{11193,2,0,-1},{11197,2,0,-1},{11201,2,0,-1},{11206,2,0,-1},{11210,2,0,-1}, +{11214,2,0,-1},{11219,2,0,-1},{11223,2,0,-1},{11227,2,0,-1},{11232,2,0,-1}, +{11236,2,0,-1},{11166,2,0,-1},{11240,2,0,-1},{11245,2,0,-1},{11249,2,0,-1}, +{11253,2,0,-1},{11258,2,0,-1},{11262,2,0,-1},{11266,2,0,-1},{11271,2,0,-1}, +{11275,2,0,-1},{11279,2,0,-1},{11284,2,0,-1},{11288,2,0,-1},{11292,2,0,-1}, +{11297,2,0,-1},{11301,2,0,-1},{11305,2,0,-1},{11310,2,0,-1},{11314,2,0,-1}, +{11318,2,0,-1},{11323,2,0,-1},{11327,2,0,-1},{11331,2,0,-1},{11336,2,0,-1}, +{11340,2,0,-1},{11270,2,0,-1},{11344,2,0,-1},{11349,2,0,-1},{11353,2,0,-1}, +{11357,2,0,-1},{11362,2,0,-1},{11366,2,0,-1},{11370,2,0,-1},{11375,2,0,-1}, +{11379,2,0,-1},{11383,2,0,-1},{11388,2,0,-1},{11392,2,0,-1},{11396,2,0,-1}, +{11401,2,0,-1},{11405,2,0,-1},{11409,2,0,-1},{11414,2,0,-1},{11418,2,0,-1}, +{11422,2,0,-1},{11427,2,0,-1},{11431,2,0,-1},{11431,3,0,-1},{11435,2,0,-1}, +{11435,3,0,-1},{11440,2,0,-1},{11440,3,0,-1},{11444,2,0,-1},{11444,3,0,-1}, +{11374,2,0,-1},{11448,2,0,-1},{11448,3,0,-1},{11453,2,0,-1},{11453,3,0,-1}, +{11457,2,0,-1},{11457,3,0,-1},{11461,2,0,-1},{11461,3,0,-1},{11466,2,0,-1}, +{11466,3,0,-1},{11470,2,0,-1},{11470,3,0,-1},{11474,2,0,-1},{11474,3,0,-1}, +{11479,2,0,-1},{11479,3,0,-1},{11483,2,0,-1},{11483,3,0,-1},{11487,2,0,-1}, +{11487,3,0,-1},{11492,2,0,-1},{11492,3,0,-1},{11496,2,0,-1},{11496,3,0,-1}, +{11500,2,0,-1},{11500,3,0,-1},{11505,2,0,-1},{11505,3,0,-1},{11509,2,0,-1}, +{11509,3,0,-1},{11513,2,0,-1},{11513,3,0,-1},{11518,2,0,-1},{11518,3,0,-1}, +{11522,2,0,-1},{11522,3,0,-1},{11526,2,0,-1},{11526,3,0,-1},{11531,2,0,-1}, +{11531,3,0,-1},{11535,2,0,-1},{11535,3,0,-1},{11539,2,0,-1},{11539,3,0,-1}, +{11544,2,0,-1},{11544,3,0,-1},{11548,2,0,-1},{11548,3,0,-1},{11478,2,0,-1}, +{11552,2,0,-1},{11552,3,0,-1},{11557,2,0,-1},{11557,3,0,-1},{11561,2,0,-1}, +{11561,3,0,-1},{11491,3,0,-1},{11565,2,0,-1},{11565,3,0,-1},{11570,2,0,-1}, +{11570,3,0,-1},{11574,2,0,-1},{11574,3,0,-1},{11578,2,0,-1},{11578,3,0,-1}, +{11583,2,0,-1},{11583,3,0,-1},{11587,2,0,-1},{11587,3,0,-1},{11591,2,0,-1}, +{11591,3,0,-1},{11596,2,0,-1},{11596,3,0,-1},{11600,2,0,-1},{11600,3,0,-1}, +{11604,2,0,-1},{11604,3,0,-1},{11609,2,0,-1},{11609,3,0,-1},{11613,2,0,-1}, +{11613,3,0,-1},{11617,2,0,-1},{11617,3,0,-1},{11622,2,0,-1},{11622,3,0,-1}, +{11626,2,0,-1},{11626,3,0,-1},{11630,2,0,-1},{11630,3,0,-1},{11635,2,0,-1}, +{11635,3,0,-1},{11639,2,0,-1},{11639,3,0,-1},{11643,2,0,-1},{11643,3,0,-1}, +{11648,2,0,-1},{11648,3,0,-1},{11652,2,0,-1},{11652,3,0,-1},{11582,2,0,-1}, +{11656,2,0,-1},{11656,3,0,-1},{11661,2,0,-1},{11661,3,0,-1},{11665,2,0,-1}, +{11665,3,0,-1},{11595,3,0,-1},{11669,2,0,-1},{11669,3,0,-1},{11674,2,0,-1}, +{11674,3,0,-1},{11678,2,0,-1},{11678,3,0,-1},{11682,2,0,-1},{11682,3,0,-1}, +{11687,2,0,-1},{11687,3,0,-1},{11691,2,0,-1},{11691,3,0,-1},{11695,2,0,-1}, +{11695,3,0,-1},{11700,2,0,-1},{11700,3,0,-1},{11704,2,0,-1},{11704,3,0,-1}, +{11708,2,0,-1},{11708,3,0,-1},{11713,2,0,-1},{11713,3,0,-1},{11717,2,0,-1}, +{11717,3,0,-1},{11721,2,0,-1},{11721,3,0,-1},{11726,2,0,-1},{11726,3,0,-1}, +{11730,2,0,-1},{11730,3,0,-1},{11734,2,0,-1},{11734,3,0,-1},{11739,2,0,-1}, +{11739,3,0,-1},{11743,2,0,-1},{11743,3,0,-1},{11747,2,0,-1},{11747,3,0,-1}, +{11752,2,0,-1},{11752,3,0,-1},{11756,2,0,-1},{11756,3,0,-1},{11686,2,0,-1}, +{11760,2,0,-1},{11760,3,0,-1},{11765,2,0,-1},{11765,3,0,-1},{11769,2,0,-1}, +{11769,3,0,-1},{11699,3,0,-1},{11773,2,0,-1},{11773,3,0,-1},{11778,2,0,-1}, +{11778,3,0,-1},{11782,2,0,-1},{11782,3,0,-1},{11786,2,0,-1},{11786,3,0,-1}, +{11791,2,0,-1},{11791,3,0,-1},{11795,2,0,-1},{11795,3,0,-1},{11799,2,0,-1}, +{11799,3,0,-1},{11804,2,0,-1},{11804,3,0,-1},{11808,2,0,-1},{11808,3,0,-1}, +{11812,2,0,-1},{11812,3,0,-1},{11817,2,0,-1},{11817,3,0,-1},{11821,2,0,-1}, +{11821,3,0,-1},{11825,2,0,-1},{11825,3,0,-1},{11830,2,0,-1},{11830,3,0,-1}, +{11834,2,0,-1},{11834,3,0,-1},{11838,2,0,-1},{11838,3,0,-1},{11843,2,0,-1}, +{11843,3,0,-1},{11847,2,0,-1},{11847,3,0,-1},{11851,2,0,-1},{11851,3,0,-1}, +{11856,2,0,-1},{11856,3,0,-1},{11860,2,0,-1},{11860,3,0,-1},{11790,2,0,-1}, +{11864,2,0,-1},{11864,3,0,-1},{11869,2,0,-1},{11869,3,0,-1},{11873,2,0,-1}, +{11873,3,0,-1},{11803,3,0,-1},{11877,2,0,-1},{11877,3,0,-1},{11882,2,0,-1}, +{11882,3,0,-1},{11886,2,0,-1},{11886,3,0,-1},{11890,2,0,-1},{11890,3,0,-1}, +{11895,2,0,-1},{11895,3,0,-1},{11899,2,0,-1},{11899,3,0,-1},{11903,2,0,-1}, +{11903,3,0,-1},{11908,2,0,-1},{11908,3,0,-1},{11912,2,0,-1},{11912,3,0,-1}, +{11916,2,0,-1},{11916,3,0,-1},{11921,2,0,-1},{11921,3,0,-1},{11925,2,0,-1}, +{11925,3,0,-1},{11929,2,0,-1},{11929,3,0,-1},{11934,2,0,-1},{11934,3,0,-1}, +{11938,2,0,-1},{11938,3,0,-1},{11942,2,0,-1},{11942,3,0,-1},{11947,2,0,-1}, +{11947,3,0,-1},{11951,2,0,-1},{11951,3,0,-1},{11955,2,0,-1},{11955,3,0,-1}, +{11960,2,0,-1},{11960,3,0,-1},{11964,2,0,-1},{11964,3,0,-1},{11894,2,0,-1}, +{11968,2,0,-1},{11968,3,0,-1},{11973,2,0,-1},{11973,3,0,-1},{11977,2,0,-1}, +{11977,3,0,-1},{11907,3,0,-1},{11981,2,0,-1},{11981,3,0,-1},{11986,2,0,-1}, +{11986,3,0,-1},{11990,2,0,-1},{11990,3,0,-1},{11994,2,0,-1},{11994,3,0,-1}, +{11999,2,0,-1},{11999,3,0,-1},{12003,2,0,-1},{12003,3,0,-1},{12007,2,0,-1}, +{12007,3,0,-1},{12012,2,0,-1},{12012,3,0,-1},{12016,2,0,-1},{12016,3,0,-1}, +{12020,2,0,-1},{12020,3,0,-1},{12025,2,0,-1},{12025,3,0,-1},{12029,2,0,-1}, +{12029,3,0,-1},{12033,2,0,-1},{12033,3,0,-1},{12038,2,0,-1},{12038,3,0,-1}, +{12042,2,0,-1},{12042,3,0,-1},{12046,2,0,-1},{12046,3,0,-1},{12051,2,0,-1}, +{12051,3,0,-1},{12055,2,0,-1},{12055,3,0,-1},{12059,2,0,-1},{12059,3,0,-1}, +{12064,2,0,-1},{12064,3,0,-1},{12068,2,0,-1},{12068,3,0,-1},{11998,2,0,-1}, +{12072,2,0,-1},{12072,3,0,-1},{12077,2,0,-1},{12077,3,0,-1},{12081,2,0,-1}, +{12081,3,0,-1},{12011,3,0,-1},{12085,2,0,-1},{12085,3,0,-1},{12090,2,0,-1}, +{12090,3,0,-1},{12094,2,0,-1},{12094,3,0,-1},{12098,2,0,-1},{12098,3,0,-1}, +{12103,2,0,-1},{12103,3,0,-1},{12107,2,0,-1},{12107,3,0,-1},{12111,2,0,-1}, +{12111,3,0,-1},{12116,2,0,-1},{12116,3,0,-1},{12120,2,0,-1},{12120,3,0,-1}, +{12124,2,0,-1},{12124,3,0,-1},{12129,2,0,-1},{12129,3,0,-1},{12133,2,0,-1}, +{12133,3,0,-1},{12137,2,0,-1},{12137,3,0,-1},{12142,2,0,-1},{12142,3,0,-1}, +{12146,2,0,-1},{12146,3,0,-1},{12150,2,0,-1},{12150,3,0,-1},{12155,2,0,-1}, +{12155,3,0,-1},{12159,2,0,-1},{12159,3,0,-1},{12163,2,0,-1},{12163,3,0,-1}, +{12168,2,0,-1},{12168,3,0,-1},{12172,2,0,-1},{12172,3,0,-1},{12102,2,0,-1}, +{12176,2,0,-1},{12176,3,0,-1},{12181,2,0,-1},{12181,3,0,-1},{12185,2,0,-1}, +{12185,3,0,-1},{12115,3,0,-1},{12189,2,0,-1},{12189,3,0,-1},{12194,2,0,-1}, +{12194,3,0,-1},{12198,2,0,-1},{12198,3,0,-1},{12202,2,0,-1},{12202,3,0,-1}, +{12207,2,0,-1},{12207,3,0,-1},{12211,2,0,-1},{12211,3,0,-1},{12215,2,0,-1}, +{12215,3,0,-1},{12220,2,0,-1},{12220,3,0,-1},{12224,2,0,-1},{12224,3,0,-1}, +{12228,2,0,-1},{12228,3,0,-1},{12233,2,0,-1},{12233,3,0,-1},{12237,2,0,-1}, +{12237,3,0,-1},{12241,2,0,-1},{12241,3,0,-1},{12246,2,0,-1},{12246,3,0,-1}, +{12250,2,0,-1},{12250,3,0,-1},{12254,2,0,-1},{12254,3,0,-1},{12259,2,0,-1}, +{12259,3,0,-1},{12263,2,0,-1},{12263,3,0,-1},{12267,2,0,-1},{12267,3,0,-1}, +{12272,2,0,-1},{12272,3,0,-1},{12276,2,0,-1},{12276,3,0,-1},{12206,2,0,-1}, +{12280,2,0,-1},{12280,3,0,-1},{12285,2,0,-1},{12285,3,0,-1},{12289,2,0,-1}, +{12289,3,0,-1},{12219,3,0,-1},{12293,2,0,-1},{12293,3,0,-1},{12298,2,0,-1}, +{12298,3,0,-1},{12302,2,0,-1},{12302,3,0,-1},{12306,2,0,-1},{12306,3,0,-1}, +{12311,2,0,-1},{12311,3,0,-1},{12315,2,0,-1},{12315,3,0,-1},{12319,2,0,-1}, +{12319,3,0,-1},{12324,2,0,-1},{12324,3,0,-1},{12328,2,0,-1},{12328,3,0,-1}, +{12332,2,0,-1},{12332,3,0,-1},{12337,2,0,-1},{12337,3,0,-1},{12341,2,0,-1}, +{12341,3,0,-1},{12345,2,0,-1},{12345,3,0,-1},{12350,2,0,-1},{12350,3,0,-1}, +{12354,2,0,-1},{12354,3,0,-1},{12358,2,0,-1},{12358,3,0,-1},{12363,2,0,-1}, +{12363,3,0,-1},{12367,2,0,-1},{12367,3,0,-1},{12371,2,0,-1},{12371,3,0,-1}, +{12376,2,0,-1},{12376,3,0,-1},{12380,2,0,-1},{12380,3,0,-1},{12310,2,0,-1}, +{12384,2,0,-1},{12384,3,0,-1},{12389,2,0,-1},{12389,3,0,-1},{12393,2,0,-1}, +{12393,3,0,-1},{12323,3,0,-1},{12397,2,0,-1},{12397,3,0,-1},{12402,2,0,-1}, +{12402,3,0,-1},{12406,2,0,-1},{12406,3,0,-1},{12410,2,0,-1},{12410,3,0,-1}, +{12415,2,0,-1},{12415,3,0,-1},{12419,2,0,-1},{12419,3,0,-1},{12423,2,0,-1}, +{12423,3,0,-1},{12428,2,0,-1},{12428,3,0,-1},{12432,2,0,-1},{12432,3,0,-1}, +{12436,2,0,-1},{12436,3,0,-1},{12441,2,0,-1},{12441,3,0,-1},{12445,2,0,-1}, +{12445,3,0,-1},{12449,2,0,-1},{12449,3,0,-1},{12454,2,0,-1},{12454,3,0,-1}, +{12458,2,0,-1},{12458,3,0,-1},{12462,2,0,-1},{12462,3,0,-1},{12467,2,0,-1}, +{12467,3,0,-1},{12471,2,0,-1},{12471,3,0,-1},{12475,2,0,-1},{12475,3,0,-1}, +{12480,2,0,-1},{12480,3,0,-1},{12484,2,0,-1},{12484,3,0,-1},{12414,2,0,-1}, +{12488,2,0,-1},{12488,3,0,-1},{12493,2,0,-1},{12493,3,0,-1},{12497,2,0,-1}, +{12497,3,0,-1},{12427,3,0,-1},{12501,2,0,-1},{12501,3,0,-1},{12506,2,0,-1}, +{12506,3,0,-1},{12510,2,0,-1},{12510,3,0,-1},{12514,2,0,-1},{12514,3,0,-1}, +{12519,2,0,-1},{12519,3,0,-1},{12523,2,0,-1},{12523,3,0,-1},{12527,2,0,-1}, +{12527,3,0,-1},{12532,2,0,-1},{12532,3,0,-1},{12536,2,0,-1},{12536,3,0,-1}, +{12540,2,0,-1},{12540,3,0,-1},{12545,2,0,-1},{12545,3,0,-1},{12549,2,0,-1}, +{12549,3,0,-1},{12553,2,0,-1},{12553,3,0,-1},{12558,2,0,-1},{12558,3,0,-1}, +{12562,2,0,-1},{12562,3,0,-1},{12566,2,0,-1},{12566,3,0,-1},{12571,2,0,-1}, +{12571,3,0,-1},{12575,2,0,-1},{12575,3,0,-1},{12579,2,0,-1},{12579,3,0,-1}, +{12584,2,0,-1},{12584,3,0,-1},{12588,2,0,-1},{12588,3,0,-1},{12518,2,0,-1}, +{12592,2,0,-1},{12592,3,0,-1},{12597,2,0,-1},{12597,3,0,-1},{12601,2,0,-1}, +{12601,3,0,-1},{12531,3,0,-1},{12605,2,0,-1},{12605,3,0,-1},{12610,2,0,-1}, +{12610,3,0,-1},{12614,2,0,-1},{12614,3,0,-1},{12618,2,0,-1},{12618,3,0,-1}, +{12623,2,0,-1},{12623,3,0,-1},{12627,2,0,-1},{12627,3,0,-1},{12631,2,0,-1}, +{12631,3,0,-1},{12636,2,0,-1},{12636,3,0,-1},{12640,2,0,-1},{12640,3,0,-1}, +{12644,2,0,-1},{12644,3,0,-1},{12649,2,0,-1},{12649,3,0,-1},{12653,2,0,-1}, +{12653,3,0,-1},{12657,2,0,-1},{12657,3,0,-1},{12662,2,0,-1},{12662,3,0,-1}, +{12666,2,0,-1},{12666,3,0,-1},{12670,2,0,-1},{12670,3,0,-1},{12675,2,0,-1}, +{12675,3,0,-1},{12679,2,0,-1},{12679,3,0,-1},{12683,2,0,-1},{12683,3,0,-1}, +{12688,2,0,-1},{12688,3,0,-1},{12692,2,0,-1},{12692,3,0,-1},{12622,2,0,-1}, +{12696,2,0,-1},{12696,3,0,-1},{12701,2,0,-1},{12701,3,0,-1},{12705,2,0,-1}, +{12705,3,0,-1},{12635,3,0,-1},{12709,2,0,-1},{12709,3,0,-1},{12714,2,0,-1}, +{12714,3,0,-1},{12718,2,0,-1},{12718,3,0,-1},{12722,2,0,-1},{12722,3,0,-1}, +{12727,2,0,-1},{12727,3,0,-1},{12731,2,0,-1},{12731,3,0,-1},{12735,2,0,-1}, +{12735,3,0,-1},{12740,2,0,-1},{12740,3,0,-1},{12744,2,0,-1},{12744,3,0,-1}, +{12748,2,0,-1},{12748,3,0,-1},{12753,2,0,-1},{12753,3,0,-1},{12757,2,0,-1}, +{12757,3,0,-1},{12761,2,0,-1},{12761,3,0,-1},{12766,2,0,-1},{12766,3,0,-1}}; + +/* Frame number data sampled from measurement.c:lchan_meas_check_compute() + * Call was made between to phones on full rate channels TS4 and TS5 */ +struct fn_sample test_fn_tch_f_ts_4_5[] = { +{3407,0,1,-1},{3427,0,1,-1},{3458,0,1,-1},{3509,0,1,-1},{3529,0,1,-1}, +{3560,0,1,-1},{3611,0,1,-1},{3662,0,1,-1},{3713,0,1,-1},{3764,0,1,-1}, +{3780,0,2,-1},{3821,0,2,-1},{3872,0,2,-1},{3882,0,2,-1},{3923,0,2,-1}, +{3974,0,2,-1},{3984,0,2,-1},{4025,0,2,-1},{4076,0,2,-1},{4127,0,2,-1}, +{4178,0,2,-1},{5871,4,0,-1},{5876,4,0,-1},{5880,4,0,-1},{5884,4,0,-1}, +{5889,4,0,-1},{5893,4,0,-1},{5897,4,0,-1},{5902,4,0,-1},{5906,4,0,-1}, +{5910,4,0,-1},{5915,4,0,-1},{5919,4,0,-1},{5923,4,0,-1},{5928,4,0,-1}, +{5932,4,0,-1},{5936,4,0,-1},{5941,4,0,-1},{5945,4,0,-1},{5949,4,0,-1}, +{5954,4,0,-1},{5958,4,0,-1},{5888,4,0,-1},{5962,4,0,-1},{5967,4,0,-1}, +{5971,4,0,-1},{5975,4,0,-1},{5980,4,0,-1},{5984,4,0,-1},{5988,4,0,-1}, +{5993,4,0,-1},{5997,4,0,-1},{6001,4,0,-1},{6006,4,0,-1},{6010,4,0,-1}, +{6014,4,0,-1},{6019,4,0,-1},{6023,4,0,-1},{6027,4,0,-1},{6032,4,0,-1}, +{6036,4,0,-1},{6040,4,0,-1},{6045,4,0,-1},{6049,4,0,-1},{6053,4,0,-1}, +{6058,4,0,-1},{6062,4,0,-1},{5992,4,0,-1},{6066,4,0,-1},{6071,4,0,-1}, +{6075,4,0,-1},{6079,4,0,-1},{6084,4,0,-1},{6088,4,0,-1},{6092,4,0,-1}, +{6097,4,0,-1},{6101,4,0,-1},{6105,4,0,-1},{6110,4,0,-1},{6114,4,0,-1}, +{6118,4,0,-1},{6123,4,0,-1},{6127,4,0,-1},{6131,4,0,-1},{6136,4,0,-1}, +{6140,4,0,-1},{6144,4,0,-1},{6149,4,0,-1},{6153,4,0,-1},{6157,4,0,-1}, +{6162,4,0,-1},{6166,4,0,-1},{6096,4,0,-1},{6170,4,0,-1},{6175,4,0,-1}, +{6175,5,0,-1},{6179,4,0,-1},{6179,5,0,-1},{6183,4,0,-1},{6183,5,0,-1}, +{6188,4,0,-1},{6188,5,0,-1},{6192,4,0,-1},{6192,5,0,-1},{6196,4,0,-1}, +{6196,5,0,-1},{6201,4,0,-1},{6201,5,0,-1},{6205,4,0,-1},{6205,5,0,-1}, +{6209,4,0,-1},{6209,5,0,-1},{6214,4,0,-1},{6214,5,0,-1},{6218,4,0,-1}, +{6218,5,0,-1},{6222,4,0,-1},{6222,5,0,-1},{6227,4,0,-1},{6227,5,0,-1}, +{6231,4,0,-1},{6231,5,0,-1},{6235,4,0,-1},{6235,5,0,-1},{6240,4,0,-1}, +{6240,5,0,-1},{6244,4,0,-1},{6244,5,0,-1},{6248,4,0,-1},{6248,5,0,-1}, +{6253,4,0,-1},{6253,5,0,-1},{6257,4,0,-1},{6257,5,0,-1},{6261,4,0,-1}, +{6261,5,0,-1},{6266,4,0,-1},{6266,5,0,-1},{6270,4,0,-1},{6270,5,0,-1}, +{6200,4,0,-1},{6274,4,0,-1},{6274,5,0,-1},{6279,4,0,-1},{6279,5,0,-1}, +{6283,4,0,-1},{6283,5,0,-1},{6213,5,0,-1},{6287,4,0,-1},{6287,5,0,-1}, +{6292,4,0,-1},{6292,5,0,-1},{6296,4,0,-1},{6296,5,0,-1},{6300,4,0,-1}, +{6300,5,0,-1},{6305,4,0,-1},{6305,5,0,-1},{6309,4,0,-1},{6309,5,0,-1}, +{6313,4,0,-1},{6313,5,0,-1},{6318,4,0,-1},{6318,5,0,-1},{6322,4,0,-1}, +{6322,5,0,-1},{6326,4,0,-1},{6326,5,0,-1},{6331,4,0,-1},{6331,5,0,-1}, +{6335,4,0,-1},{6335,5,0,-1},{6339,4,0,-1},{6339,5,0,-1},{6344,4,0,-1}, +{6344,5,0,-1},{6348,4,0,-1},{6348,5,0,-1},{6352,4,0,-1},{6352,5,0,-1}, +{6357,4,0,-1},{6357,5,0,-1},{6361,4,0,-1},{6361,5,0,-1},{6365,4,0,-1}, +{6365,5,0,-1},{6370,4,0,-1},{6370,5,0,-1},{6374,4,0,-1},{6374,5,0,-1}, +{6304,4,0,-1},{6378,4,0,-1},{6378,5,0,-1},{6383,4,0,-1},{6383,5,0,-1}, +{6387,4,0,-1},{6387,5,0,-1},{6317,5,0,-1},{6391,4,0,-1},{6391,5,0,-1}, +{6396,4,0,-1},{6396,5,0,-1},{6400,4,0,-1},{6400,5,0,-1},{6404,4,0,-1}, +{6404,5,0,-1},{6409,4,0,-1},{6409,5,0,-1},{6413,4,0,-1},{6413,5,0,-1}, +{6417,4,0,-1},{6417,5,0,-1},{6422,4,0,-1},{6422,5,0,-1},{6426,4,0,-1}, +{6426,5,0,-1},{6430,4,0,-1},{6430,5,0,-1},{6435,4,0,-1},{6435,5,0,-1}, +{6439,4,0,-1},{6439,5,0,-1},{6443,4,0,-1},{6443,5,0,-1},{6448,4,0,-1}, +{6448,5,0,-1},{6452,4,0,-1},{6452,5,0,-1},{6456,4,0,-1},{6456,5,0,-1}, +{6461,4,0,-1},{6461,5,0,-1},{6465,4,0,-1},{6465,5,0,-1},{6469,4,0,-1}, +{6469,5,0,-1},{6474,4,0,-1},{6474,5,0,-1},{6478,4,0,-1},{6478,5,0,-1}, +{6408,4,0,-1},{6482,4,0,-1},{6482,5,0,-1},{6487,4,0,-1},{6487,5,0,-1}, +{6491,4,0,-1},{6491,5,0,-1},{6421,5,0,-1},{6495,4,0,-1},{6495,5,0,-1}, +{6500,4,0,-1},{6500,5,0,-1},{6504,4,0,-1},{6504,5,0,-1},{6508,4,0,-1}, +{6508,5,0,-1},{6513,4,0,-1},{6513,5,0,-1},{6517,4,0,-1},{6517,5,0,-1}, +{6521,4,0,-1},{6521,5,0,-1},{6526,4,0,-1},{6526,5,0,-1},{6530,4,0,-1}, +{6530,5,0,-1},{6534,4,0,-1},{6534,5,0,-1},{6539,4,0,-1},{6539,5,0,-1}, +{6543,4,0,-1},{6543,5,0,-1},{6547,4,0,-1},{6547,5,0,-1},{6552,4,0,-1}, +{6552,5,0,-1},{6556,4,0,-1},{6556,5,0,-1},{6560,4,0,-1},{6560,5,0,-1}, +{6565,4,0,-1},{6565,5,0,-1},{6569,4,0,-1},{6569,5,0,-1},{6573,4,0,-1}, +{6573,5,0,-1},{6578,4,0,-1},{6578,5,0,-1},{6582,4,0,-1},{6582,5,0,-1}, +{6512,4,0,-1},{6586,4,0,-1},{6586,5,0,-1},{6591,4,0,-1},{6591,5,0,-1}, +{6595,4,0,-1},{6595,5,0,-1},{6525,5,0,-1},{6599,4,0,-1},{6599,5,0,-1}, +{6604,4,0,-1},{6604,5,0,-1},{6608,4,0,-1},{6608,5,0,-1},{6612,4,0,-1}, +{6612,5,0,-1},{6617,4,0,-1},{6617,5,0,-1},{6621,4,0,-1},{6621,5,0,-1}, +{6625,4,0,-1},{6625,5,0,-1},{6630,4,0,-1},{6630,5,0,-1},{6634,4,0,-1}, +{6634,5,0,-1},{6638,4,0,-1},{6638,5,0,-1},{6643,4,0,-1},{6643,5,0,-1}, +{6647,4,0,-1},{6647,5,0,-1},{6651,4,0,-1},{6651,5,0,-1},{6656,4,0,-1}, +{6656,5,0,-1},{6660,4,0,-1},{6660,5,0,-1},{6664,4,0,-1},{6664,5,0,-1}, +{6669,4,0,-1},{6669,5,0,-1},{6673,4,0,-1},{6673,5,0,-1},{6677,4,0,-1}, +{6677,5,0,-1},{6682,4,0,-1},{6682,5,0,-1},{6686,4,0,-1},{6686,5,0,-1}, +{6616,4,0,-1},{6690,4,0,-1},{6690,5,0,-1},{6695,4,0,-1},{6695,5,0,-1}, +{6699,4,0,-1},{6699,5,0,-1},{6629,5,0,-1},{6703,4,0,-1},{6703,5,0,-1}, +{6708,4,0,-1},{6708,5,0,-1},{6712,4,0,-1},{6712,5,0,-1},{6716,4,0,-1}, +{6716,5,0,-1},{6721,4,0,-1},{6721,5,0,-1},{6725,4,0,-1},{6725,5,0,-1}, +{6729,4,0,-1},{6729,5,0,-1},{6734,4,0,-1},{6734,5,0,-1},{6738,4,0,-1}, +{6738,5,0,-1},{6742,4,0,-1},{6742,5,0,-1},{6747,4,0,-1},{6747,5,0,-1}, +{6751,4,0,-1},{6751,5,0,-1},{6755,4,0,-1},{6755,5,0,-1},{6760,4,0,-1}, +{6760,5,0,-1},{6764,4,0,-1},{6764,5,0,-1},{6768,4,0,-1},{6768,5,0,-1}, +{6773,4,0,-1},{6773,5,0,-1},{6777,4,0,-1},{6777,5,0,-1},{6781,4,0,-1}, +{6781,5,0,-1},{6786,4,0,-1},{6786,5,0,-1},{6790,4,0,-1},{6790,5,0,-1}, +{6720,4,0,-1},{6794,4,0,-1},{6794,5,0,-1},{6799,4,0,-1},{6799,5,0,-1}, +{6803,4,0,-1},{6803,5,0,-1},{6733,5,0,-1},{6807,4,0,-1},{6807,5,0,-1}, +{6812,4,0,-1},{6812,5,0,-1},{6816,4,0,-1},{6816,5,0,-1},{6820,4,0,-1}, +{6820,5,0,-1},{6825,4,0,-1},{6825,5,0,-1},{6829,4,0,-1},{6829,5,0,-1}, +{6833,4,0,-1},{6833,5,0,-1},{6838,4,0,-1},{6838,5,0,-1},{6842,4,0,-1}, +{6842,5,0,-1},{6846,4,0,-1},{6846,5,0,-1},{6851,4,0,-1},{6851,5,0,-1}, +{6855,4,0,-1},{6855,5,0,-1},{6859,4,0,-1},{6859,5,0,-1},{6864,4,0,-1}, +{6864,5,0,-1},{6868,4,0,-1},{6868,5,0,-1},{6872,4,0,-1},{6872,5,0,-1}, +{6877,4,0,-1},{6877,5,0,-1},{6881,4,0,-1},{6881,5,0,-1},{6885,4,0,-1}, +{6885,5,0,-1},{6890,4,0,-1},{6890,5,0,-1},{6894,4,0,-1},{6894,5,0,-1}, +{6824,4,0,-1},{6898,4,0,-1},{6898,5,0,-1},{6903,4,0,-1},{6903,5,0,-1}, +{6907,4,0,-1},{6907,5,0,-1},{6837,5,0,-1},{6911,4,0,-1},{6911,5,0,-1}, +{6916,4,0,-1},{6916,5,0,-1},{6920,4,0,-1},{6920,5,0,-1},{6924,4,0,-1}, +{6924,5,0,-1},{6929,4,0,-1},{6929,5,0,-1},{6933,4,0,-1},{6933,5,0,-1}, +{6937,4,0,-1},{6937,5,0,-1},{6942,4,0,-1},{6942,5,0,-1},{6946,4,0,-1}, +{6946,5,0,-1},{6950,4,0,-1},{6950,5,0,-1},{6955,4,0,-1},{6955,5,0,-1}, +{6959,4,0,-1},{6959,5,0,-1},{6963,4,0,-1},{6963,5,0,-1},{6968,4,0,-1}, +{6968,5,0,-1},{6972,4,0,-1},{6972,5,0,-1},{6976,4,0,-1},{6976,5,0,-1}, +{6981,4,0,-1},{6981,5,0,-1},{6985,4,0,-1},{6985,5,0,-1},{6989,4,0,-1}, +{6989,5,0,-1},{6994,4,0,-1},{6994,5,0,-1},{6998,4,0,-1},{6998,5,0,-1}, +{6928,4,0,-1},{7002,4,0,-1},{7002,5,0,-1},{7007,4,0,-1},{7007,5,0,-1}, +{7011,4,0,-1},{7011,5,0,-1},{6941,5,0,-1},{7015,4,0,-1},{7015,5,0,-1}, +{7020,4,0,-1},{7020,5,0,-1},{7024,4,0,-1},{7024,5,0,-1},{7028,4,0,-1}, +{7028,5,0,-1},{7033,4,0,-1},{7033,5,0,-1},{7037,4,0,-1},{7037,5,0,-1}, +{7041,4,0,-1},{7041,5,0,-1},{7046,4,0,-1},{7046,5,0,-1},{7050,4,0,-1}, +{7050,5,0,-1},{7054,4,0,-1},{7054,5,0,-1},{7059,4,0,-1},{7059,5,0,-1}, +{7063,4,0,-1},{7063,5,0,-1},{7067,4,0,-1},{7067,5,0,-1},{7072,4,0,-1}, +{7072,5,0,-1},{7076,4,0,-1},{7076,5,0,-1},{7080,4,0,-1},{7080,5,0,-1}, +{7085,4,0,-1},{7085,5,0,-1},{7089,4,0,-1},{7089,5,0,-1},{7093,4,0,-1}, +{7093,5,0,-1},{7098,4,0,-1},{7098,5,0,-1},{7102,4,0,-1},{7102,5,0,-1}, +{7032,4,0,-1},{7106,4,0,-1},{7106,5,0,-1},{7111,4,0,-1},{7111,5,0,-1}, +{7115,4,0,-1},{7115,5,0,-1},{7045,5,0,-1},{7119,4,0,-1},{7119,5,0,-1}, +{7124,4,0,-1},{7124,5,0,-1},{7128,4,0,-1},{7128,5,0,-1},{7132,4,0,-1}, +{7132,5,0,-1},{7137,4,0,-1},{7137,5,0,-1},{7141,4,0,-1},{7141,5,0,-1}, +{7145,4,0,-1},{7145,5,0,-1},{7150,4,0,-1},{7150,5,0,-1},{7154,4,0,-1}, +{7154,5,0,-1},{7158,4,0,-1},{7158,5,0,-1},{7163,4,0,-1},{7163,5,0,-1}, +{7167,4,0,-1},{7167,5,0,-1},{7171,4,0,-1},{7171,5,0,-1},{7176,4,0,-1}, +{7176,5,0,-1},{7180,4,0,-1},{7180,5,0,-1},{7184,4,0,-1},{7184,5,0,-1}, +{7189,4,0,-1},{7189,5,0,-1},{7193,4,0,-1},{7193,5,0,-1},{7197,4,0,-1}, +{7197,5,0,-1},{7202,4,0,-1},{7202,5,0,-1},{7206,4,0,-1},{7206,5,0,-1}, +{7136,4,0,-1},{7210,4,0,-1},{7210,5,0,-1},{7215,4,0,-1},{7215,5,0,-1}, +{7219,4,0,-1},{7219,5,0,-1},{7149,5,0,-1},{7223,4,0,-1},{7223,5,0,-1}, +{7228,4,0,-1},{7228,5,0,-1},{7232,4,0,-1},{7232,5,0,-1},{7236,4,0,-1}, +{7236,5,0,-1},{7241,4,0,-1},{7241,5,0,-1},{7245,4,0,-1},{7245,5,0,-1}, +{7249,4,0,-1},{7249,5,0,-1},{7254,4,0,-1},{7254,5,0,-1},{7258,4,0,-1}, +{7258,5,0,-1},{7262,4,0,-1},{7262,5,0,-1},{7267,4,0,-1},{7267,5,0,-1}, +{7271,4,0,-1},{7271,5,0,-1},{7275,4,0,-1},{7275,5,0,-1},{7280,4,0,-1}, +{7280,5,0,-1},{7284,4,0,-1},{7284,5,0,-1},{7288,4,0,-1},{7288,5,0,-1}, +{7293,4,0,-1},{7293,5,0,-1},{7297,4,0,-1},{7297,5,0,-1},{7301,4,0,-1}, +{7301,5,0,-1},{7306,4,0,-1},{7306,5,0,-1},{7310,4,0,-1},{7310,5,0,-1}, +{7240,4,0,-1},{7314,4,0,-1},{7314,5,0,-1},{7319,4,0,-1},{7319,5,0,-1}, +{7323,4,0,-1},{7323,5,0,-1},{7253,5,0,-1},{7327,4,0,-1},{7327,5,0,-1}, +{7332,4,0,-1},{7332,5,0,-1},{7336,4,0,-1},{7336,5,0,-1},{7340,4,0,-1}, +{7340,5,0,-1},{7345,4,0,-1},{7345,5,0,-1},{7349,4,0,-1},{7349,5,0,-1}, +{7353,4,0,-1},{7353,5,0,-1},{7358,4,0,-1},{7358,5,0,-1},{7362,4,0,-1}, +{7362,5,0,-1},{7366,4,0,-1},{7366,5,0,-1},{7371,4,0,-1},{7371,5,0,-1}, +{7375,4,0,-1},{7375,5,0,-1},{7379,4,0,-1},{7379,5,0,-1},{7384,4,0,-1}, +{7384,5,0,-1},{7388,4,0,-1},{7388,5,0,-1},{7392,4,0,-1},{7392,5,0,-1}, +{7397,4,0,-1},{7397,5,0,-1},{7401,4,0,-1},{7401,5,0,-1},{7405,4,0,-1}, +{7405,5,0,-1},{7410,4,0,-1},{7410,5,0,-1},{7414,4,0,-1},{7414,5,0,-1}, +{7344,4,0,-1},{7418,4,0,-1},{7418,5,0,-1},{7423,4,0,-1},{7423,5,0,-1}, +{7427,4,0,-1},{7427,5,0,-1},{7357,5,0,-1},{7431,4,0,-1},{7431,5,0,-1}, +{7436,4,0,-1},{7436,5,0,-1},{7440,4,0,-1},{7440,5,0,-1},{7444,4,0,-1}, +{7444,5,0,-1},{7449,4,0,-1},{7449,5,0,-1},{7453,4,0,-1},{7453,5,0,-1}, +{7457,4,0,-1},{7457,5,0,-1},{7462,4,0,-1},{7462,5,0,-1},{7466,4,0,-1}, +{7466,5,0,-1},{7470,4,0,-1},{7470,5,0,-1},{7475,4,0,-1},{7475,5,0,-1}, +{7479,4,0,-1},{7479,5,0,-1},{7483,4,0,-1},{7483,5,0,-1},{7488,4,0,-1}, +{7488,5,0,-1},{7492,4,0,-1},{7492,5,0,-1},{7496,4,0,-1},{7496,5,0,-1}, +{7501,4,0,-1},{7501,5,0,-1},{7505,4,0,-1},{7505,5,0,-1},{7509,4,0,-1}, +{7509,5,0,-1},{7514,4,0,-1},{7514,5,0,-1},{7518,4,0,-1},{7518,5,0,-1}, +{7448,4,0,-1},{7522,4,0,-1},{7522,5,0,-1},{7527,4,0,-1},{7527,5,0,-1}, +{7531,4,0,-1},{7531,5,0,-1},{7461,5,0,-1},{7535,4,0,-1},{7535,5,0,-1}, +{7540,4,0,-1},{7540,5,0,-1},{7544,4,0,-1},{7544,5,0,-1},{7548,4,0,-1}, +{7548,5,0,-1},{7553,4,0,-1},{7553,5,0,-1},{7557,4,0,-1},{7557,5,0,-1}, +{7561,4,0,-1},{7561,5,0,-1},{7566,4,0,-1},{7566,5,0,-1},{7570,4,0,-1}, +{7570,5,0,-1},{7574,4,0,-1},{7574,5,0,-1},{7579,4,0,-1},{7579,5,0,-1}, +{7583,4,0,-1},{7583,5,0,-1},{7587,4,0,-1},{7587,5,0,-1},{7592,4,0,-1}, +{7592,5,0,-1},{7596,4,0,-1},{7596,5,0,-1},{7600,4,0,-1},{7600,5,0,-1}, +{7605,4,0,-1},{7605,5,0,-1},{7609,4,0,-1},{7609,5,0,-1},{7613,4,0,-1}, +{7613,5,0,-1},{7618,4,0,-1},{7618,5,0,-1},{7622,4,0,-1},{7622,5,0,-1}, +{7552,4,0,-1},{7626,4,0,-1},{7626,5,0,-1},{7631,4,0,-1},{7631,5,0,-1}, +{7635,4,0,-1},{7635,5,0,-1},{7565,5,0,-1},{7639,4,0,-1},{7639,5,0,-1}, +{7644,4,0,-1},{7644,5,0,-1},{7648,4,0,-1},{7648,5,0,-1},{7652,4,0,-1}, +{7652,5,0,-1},{7657,4,0,-1},{7657,5,0,-1},{7661,4,0,-1},{7661,5,0,-1}, +{7665,4,0,-1},{7665,5,0,-1},{7670,4,0,-1},{7670,5,0,-1},{7674,4,0,-1}, +{7674,5,0,-1},{7678,4,0,-1},{7678,5,0,-1},{7683,4,0,-1},{7683,5,0,-1}, +{7687,4,0,-1},{7687,5,0,-1},{7691,4,0,-1},{7691,5,0,-1},{7696,4,0,-1}, +{7696,5,0,-1},{7700,4,0,-1},{7700,5,0,-1},{7704,4,0,-1},{7704,5,0,-1}, +{7709,4,0,-1},{7709,5,0,-1},{7713,4,0,-1},{7713,5,0,-1},{7717,4,0,-1}, +{7717,5,0,-1},{7722,4,0,-1},{7722,5,0,-1},{7726,4,0,-1},{7726,5,0,-1}, +{7656,4,0,-1},{7730,4,0,-1},{7730,5,0,-1},{7735,4,0,-1},{7735,5,0,-1}, +{7739,4,0,-1},{7739,5,0,-1},{7669,5,0,-1},{7743,4,0,-1},{7743,5,0,-1}, +{7748,4,0,-1},{7748,5,0,-1},{7752,4,0,-1},{7752,5,0,-1},{7756,4,0,-1}, +{7756,5,0,-1},{7761,4,0,-1},{7761,5,0,-1},{7765,4,0,-1},{7765,5,0,-1}, +{7769,4,0,-1},{7769,5,0,-1},{7774,4,0,-1},{7774,5,0,-1},{7778,4,0,-1}, +{7778,5,0,-1},{7782,4,0,-1},{7782,5,0,-1},{7787,4,0,-1},{7787,5,0,-1}, +{7791,4,0,-1},{7791,5,0,-1},{7795,4,0,-1},{7795,5,0,-1},{7800,4,0,-1}, +{7800,5,0,-1},{7804,4,0,-1},{7804,5,0,-1},{7808,4,0,-1},{7808,5,0,-1}, +{7813,4,0,-1},{7813,5,0,-1},{7817,4,0,-1},{7817,5,0,-1},{7821,4,0,-1}, +{7821,5,0,-1},{7826,4,0,-1},{7826,5,0,-1},{7830,4,0,-1},{7830,5,0,-1}, +{7760,4,0,-1},{7834,4,0,-1},{7834,5,0,-1},{7839,4,0,-1},{7839,5,0,-1}, +{7843,4,0,-1},{7843,5,0,-1},{7773,5,0,-1},{7847,4,0,-1},{7847,5,0,-1}, +{7852,4,0,-1},{7852,5,0,-1},{7856,4,0,-1},{7856,5,0,-1},{7860,4,0,-1}, +{7860,5,0,-1},{7865,4,0,-1},{7865,5,0,-1},{7869,4,0,-1},{7869,5,0,-1}, +{7873,4,0,-1},{7873,5,0,-1},{7878,4,0,-1},{7878,5,0,-1},{7882,4,0,-1}, +{7882,5,0,-1},{7886,4,0,-1},{7886,5,0,-1},{7891,4,0,-1},{7891,5,0,-1}, +{7895,4,0,-1},{7895,5,0,-1},{7899,4,0,-1},{7899,5,0,-1},{7904,4,0,-1}, +{7904,5,0,-1},{7908,4,0,-1},{7908,5,0,-1},{7912,4,0,-1},{7912,5,0,-1}, +{7917,4,0,-1},{7917,5,0,-1},{7921,4,0,-1},{7921,5,0,-1},{7925,4,0,-1}, +{7925,5,0,-1},{7930,4,0,-1},{7930,5,0,-1},{7934,4,0,-1},{7934,5,0,-1}, +{7864,4,0,-1},{7938,4,0,-1},{7938,5,0,-1},{7943,4,0,-1},{7943,5,0,-1}, +{7947,4,0,-1},{7947,5,0,-1},{7877,5,0,-1},{7951,4,0,-1},{7951,5,0,-1}, +{7956,4,0,-1},{7956,5,0,-1},{7960,4,0,-1},{7960,5,0,-1},{7964,4,0,-1}, +{7964,5,0,-1},{7969,4,0,-1},{7969,5,0,-1},{7973,4,0,-1},{7973,5,0,-1}, +{7977,4,0,-1},{7977,5,0,-1},{7982,4,0,-1},{7982,5,0,-1},{7986,4,0,-1}, +{7986,5,0,-1},{7990,4,0,-1},{7990,5,0,-1},{7995,4,0,-1},{7995,5,0,-1}, +{7999,4,0,-1},{7999,5,0,-1},{8003,4,0,-1},{8003,5,0,-1},{8008,4,0,-1}, +{8008,5,0,-1},{8012,4,0,-1},{8012,5,0,-1},{8016,4,0,-1},{8016,5,0,-1}, +{8021,4,0,-1},{8021,5,0,-1},{8025,4,0,-1},{8025,5,0,-1},{8029,4,0,-1}, +{8029,5,0,-1},{8034,4,0,-1},{8034,5,0,-1},{8038,4,0,-1},{8038,5,0,-1}, +{7968,4,0,-1},{8042,4,0,-1},{8042,5,0,-1},{8047,4,0,-1},{8047,5,0,-1}, +{8051,4,0,-1},{8051,5,0,-1},{7981,5,0,-1},{8055,4,0,-1},{8055,5,0,-1}, +{8060,4,0,-1},{8060,5,0,-1},{8064,4,0,-1},{8064,5,0,-1},{8068,4,0,-1}, +{8068,5,0,-1},{8073,4,0,-1},{8073,5,0,-1},{8077,4,0,-1},{8077,5,0,-1}, +{8081,4,0,-1},{8081,5,0,-1},{8086,4,0,-1},{8086,5,0,-1},{8090,4,0,-1}, +{8090,5,0,-1},{8094,4,0,-1},{8094,5,0,-1},{8099,4,0,-1},{8099,5,0,-1}, +{8103,4,0,-1},{8103,5,0,-1},{8107,4,0,-1},{8107,5,0,-1},{8112,4,0,-1}, +{8112,5,0,-1},{8116,4,0,-1},{8116,5,0,-1},{8120,4,0,-1},{8120,5,0,-1}, +{8125,4,0,-1},{8125,5,0,-1},{8129,4,0,-1},{8129,5,0,-1},{8133,4,0,-1}, +{8133,5,0,-1},{8138,4,0,-1},{8138,5,0,-1},{8142,4,0,-1},{8142,5,0,-1}, +{8072,4,0,-1},{8146,4,0,-1},{8146,5,0,-1},{8151,4,0,-1},{8151,5,0,-1}, +{8155,4,0,-1},{8155,5,0,-1},{8085,5,0,-1},{8159,4,0,-1},{8159,5,0,-1}, +{8164,4,0,-1},{8164,5,0,-1},{8168,4,0,-1},{8168,5,0,-1},{8172,4,0,-1}, +{8172,5,0,-1},{8177,4,0,-1},{8177,5,0,-1},{8181,4,0,-1},{8181,5,0,-1}, +{8185,4,0,-1},{8185,5,0,-1},{8190,4,0,-1},{8190,5,0,-1},{8194,4,0,-1}, +{8194,5,0,-1},{8198,4,0,-1},{8198,5,0,-1},{8203,4,0,-1},{8203,5,0,-1}, +{8207,4,0,-1},{8207,5,0,-1},{8211,4,0,-1},{8211,5,0,-1},{8216,4,0,-1}, +{8216,5,0,-1},{8220,4,0,-1},{8220,5,0,-1},{8224,4,0,-1},{8224,5,0,-1}, +{8229,4,0,-1},{8229,5,0,-1},{8233,4,0,-1},{8233,5,0,-1},{8237,4,0,-1}, +{8237,5,0,-1},{8242,4,0,-1},{8242,5,0,-1},{8246,4,0,-1},{8246,5,0,-1}, +{8176,4,0,-1},{8250,4,0,-1},{8250,5,0,-1},{8255,4,0,-1},{8255,5,0,-1}, +{8259,4,0,-1},{8259,5,0,-1},{8189,5,0,-1},{8263,4,0,-1},{8263,5,0,-1}, +{8268,4,0,-1},{8268,5,0,-1},{8272,4,0,-1},{8272,5,0,-1},{8276,4,0,-1}, +{8276,5,0,-1},{8281,4,0,-1},{8281,5,0,-1},{8285,4,0,-1},{8285,5,0,-1}, +{8289,4,0,-1},{8289,5,0,-1},{8294,4,0,-1},{8294,5,0,-1},{8298,4,0,-1}, +{8298,5,0,-1},{8302,4,0,-1},{8302,5,0,-1},{8307,4,0,-1},{8307,5,0,-1}, +{8311,4,0,-1},{8311,5,0,-1},{8315,4,0,-1},{8315,5,0,-1},{8320,4,0,-1}, +{8320,5,0,-1},{8324,4,0,-1},{8324,5,0,-1},{8328,4,0,-1},{8328,5,0,-1}, +{8333,4,0,-1},{8333,5,0,-1},{8337,4,0,-1},{8337,5,0,-1},{8341,4,0,-1}, +{8341,5,0,-1},{8346,4,0,-1},{8346,5,0,-1},{8350,4,0,-1},{8350,5,0,-1}, +{8280,4,0,-1},{8354,4,0,-1},{8354,5,0,-1},{8359,4,0,-1},{8359,5,0,-1}, +{8363,4,0,-1},{8363,5,0,-1},{8293,5,0,-1},{8367,4,0,-1},{8367,5,0,-1}, +{8372,4,0,-1},{8372,5,0,-1},{8376,4,0,-1},{8376,5,0,-1},{8380,4,0,-1}, +{8380,5,0,-1},{8385,4,0,-1},{8385,5,0,-1},{8389,4,0,-1},{8389,5,0,-1}, +{8393,4,0,-1},{8393,5,0,-1},{8398,4,0,-1},{8398,5,0,-1},{8402,4,0,-1}, +{8402,5,0,-1},{8406,4,0,-1},{8406,5,0,-1},{8411,4,0,-1},{8411,5,0,-1}, +{8415,4,0,-1},{8415,5,0,-1},{8419,4,0,-1},{8419,5,0,-1},{8424,4,0,-1}, +{8424,5,0,-1},{8428,4,0,-1},{8428,5,0,-1},{8432,4,0,-1},{8432,5,0,-1}, +{8437,4,0,-1},{8437,5,0,-1},{8441,4,0,-1},{8441,5,0,-1},{8445,4,0,-1}, +{8445,5,0,-1},{8450,4,0,-1},{8450,5,0,-1},{8454,4,0,-1},{8454,5,0,-1}, +{8384,4,0,-1},{8458,4,0,-1},{8458,5,0,-1},{8463,4,0,-1},{8463,5,0,-1}, +{8467,4,0,-1},{8467,5,0,-1},{8397,5,0,-1},{8471,4,0,-1},{8471,5,0,-1}, +{8476,4,0,-1},{8476,5,0,-1},{8480,4,0,-1},{8480,5,0,-1},{8484,4,0,-1}, +{8484,5,0,-1},{8489,4,0,-1},{8489,5,0,-1},{8493,4,0,-1},{8493,5,0,-1}, +{8497,4,0,-1},{8497,5,0,-1},{8502,4,0,-1},{8502,5,0,-1},{8506,4,0,-1}, +{8506,5,0,-1},{8510,4,0,-1},{8510,5,0,-1},{8515,4,0,-1},{8515,5,0,-1}, +{8519,4,0,-1},{8519,5,0,-1},{8523,4,0,-1},{8523,5,0,-1},{8528,4,0,-1}, +{8528,5,0,-1},{8532,4,0,-1},{8532,5,0,-1},{8536,4,0,-1},{8536,5,0,-1}, +{8541,4,0,-1},{8541,5,0,-1},{8545,4,0,-1},{8545,5,0,-1},{8549,4,0,-1}, +{8549,5,0,-1},{8554,4,0,-1},{8554,5,0,-1},{8558,4,0,-1},{8558,5,0,-1}, +{8488,4,0,-1},{8562,4,0,-1},{8562,5,0,-1},{8567,4,0,-1},{8567,5,0,-1}, +{8571,4,0,-1},{8571,5,0,-1},{8501,5,0,-1},{8575,4,0,-1},{8575,5,0,-1}, +{8580,4,0,-1},{8580,5,0,-1},{8584,4,0,-1},{8584,5,0,-1},{8588,4,0,-1}, +{8588,5,0,-1},{8593,4,0,-1},{8593,5,0,-1},{8597,4,0,-1},{8597,5,0,-1}, +{8601,4,0,-1},{8601,5,0,-1},{8606,4,0,-1},{8606,5,0,-1},{8610,4,0,-1}, +{8610,5,0,-1},{8614,4,0,-1},{8614,5,0,-1},{8619,4,0,-1},{8619,5,0,-1}, +{8623,4,0,-1},{8623,5,0,-1},{8627,4,0,-1},{8627,5,0,-1},{8632,4,0,-1}, +{8632,5,0,-1},{8636,4,0,-1},{8636,5,0,-1},{8640,4,0,-1},{8640,5,0,-1}, +{8645,4,0,-1},{8645,5,0,-1},{8649,4,0,-1},{8649,5,0,-1},{8653,4,0,-1}, +{8653,5,0,-1},{8658,4,0,-1},{8658,5,0,-1},{8662,4,0,-1},{8662,5,0,-1}, +{8592,4,0,-1},{8666,4,0,-1},{8666,5,0,-1},{8671,4,0,-1},{8671,5,0,-1}, +{8675,4,0,-1},{8675,5,0,-1},{8605,5,0,-1},{8679,4,0,-1},{8679,5,0,-1}, +{8684,4,0,-1},{8684,5,0,-1},{8688,4,0,-1},{8688,5,0,-1},{8692,4,0,-1}, +{8692,5,0,-1},{8697,4,0,-1},{8697,5,0,-1},{8701,4,0,-1},{8701,5,0,-1}, +{8705,4,0,-1},{8705,5,0,-1},{8710,4,0,-1},{8710,5,0,-1},{8714,4,0,-1}, +{8714,5,0,-1},{8718,4,0,-1},{8718,5,0,-1},{8723,4,0,-1},{8723,5,0,-1}, +{8727,4,0,-1},{8727,5,0,-1},{8731,4,0,-1},{8731,5,0,-1},{8736,4,0,-1}, +{8736,5,0,-1},{8740,4,0,-1},{8740,5,0,-1},{8744,4,0,-1},{8744,5,0,-1}, +{8749,4,0,-1},{8749,5,0,-1},{8753,4,0,-1},{8753,5,0,-1},{8757,4,0,-1}, +{8757,5,0,-1},{8762,4,0,-1},{8762,5,0,-1},{8766,4,0,-1},{8766,5,0,-1}, +{8696,4,0,-1},{8770,4,0,-1},{8770,5,0,-1},{8775,4,0,-1},{8775,5,0,-1}, +{8779,4,0,-1},{8779,5,0,-1},{8709,5,0,-1},{8783,4,0,-1},{8783,5,0,-1}, +{8788,4,0,-1},{8788,5,0,-1},{8792,4,0,-1},{8792,5,0,-1},{8796,4,0,-1}, +{8796,5,0,-1},{8801,4,0,-1},{8801,5,0,-1},{8805,4,0,-1},{8805,5,0,-1}, +{8809,4,0,-1},{8809,5,0,-1},{8814,4,0,-1},{8814,5,0,-1},{8818,4,0,-1}, +{8818,5,0,-1},{8822,4,0,-1},{8822,5,0,-1},{8827,4,0,-1},{8827,5,0,-1}, +{8831,4,0,-1},{8831,5,0,-1},{8835,4,0,-1},{8835,5,0,-1},{8840,4,0,-1}, +{8840,5,0,-1},{8844,4,0,-1},{8844,5,0,-1},{8848,4,0,-1},{8848,5,0,-1}, +{8853,4,0,-1},{8853,5,0,-1},{8857,4,0,-1},{8857,5,0,-1},{8861,4,0,-1}, +{8861,5,0,-1},{8866,4,0,-1},{8866,5,0,-1},{8870,4,0,-1},{8870,5,0,-1}, +{8800,4,0,-1},{8874,4,0,-1},{8874,5,0,-1},{8879,4,0,-1},{8879,5,0,-1}, +{8883,4,0,-1},{8883,5,0,-1},{8813,5,0,-1},{8887,4,0,-1},{8887,5,0,-1}, +{8892,4,0,-1},{8892,5,0,-1},{8896,4,0,-1},{8896,5,0,-1},{8900,4,0,-1}, +{8900,5,0,-1},{8905,4,0,-1},{8905,5,0,-1},{8909,4,0,-1},{8909,5,0,-1}, +{8913,4,0,-1},{8913,5,0,-1},{8918,4,0,-1},{8918,5,0,-1},{8922,4,0,-1}, +{8922,5,0,-1},{8926,4,0,-1},{8926,5,0,-1},{8931,4,0,-1},{8931,5,0,-1}, +{8935,4,0,-1},{8935,5,0,-1},{8939,4,0,-1},{8939,5,0,-1},{8944,4,0,-1}, +{8944,5,0,-1},{8948,4,0,-1},{8948,5,0,-1},{8952,4,0,-1},{8952,5,0,-1}, +{8957,4,0,-1},{8957,5,0,-1},{8961,4,0,-1},{8961,5,0,-1},{8965,4,0,-1}, +{8965,5,0,-1},{8970,4,0,-1},{8970,5,0,-1},{8974,4,0,-1},{8974,5,0,-1}, +{8904,4,0,-1},{8978,4,0,-1},{8978,5,0,-1},{8983,4,0,-1},{8983,5,0,-1}, +{8987,4,0,-1},{8987,5,0,-1},{8917,5,0,-1},{8991,4,0,-1},{8991,5,0,-1}, +{8996,4,0,-1},{8996,5,0,-1},{9000,4,0,-1},{9000,5,0,-1},{9004,4,0,-1}, +{9004,5,0,-1},{9009,4,0,-1},{9009,5,0,-1},{9013,4,0,-1},{9013,5,0,-1}, +{9017,4,0,-1},{9017,5,0,-1},{9022,4,0,-1},{9022,5,0,-1},{9026,4,0,-1}, +{9026,5,0,-1},{9030,4,0,-1},{9030,5,0,-1},{9035,4,0,-1},{9035,5,0,-1}, +{9039,4,0,-1},{9039,5,0,-1},{9043,4,0,-1},{9043,5,0,-1},{9048,4,0,-1}, +{9048,5,0,-1},{9052,4,0,-1},{9052,5,0,-1},{9056,4,0,-1},{9056,5,0,-1}, +{9061,4,0,-1},{9061,5,0,-1},{9065,4,0,-1},{9065,5,0,-1},{9069,4,0,-1}, +{9069,5,0,-1},{9074,4,0,-1},{9074,5,0,-1},{9078,4,0,-1},{9078,5,0,-1}, +{9008,4,0,-1},{9082,4,0,-1},{9082,5,0,-1},{9087,4,0,-1},{9087,5,0,-1}, +{9091,4,0,-1},{9091,5,0,-1},{9021,5,0,-1},{9095,4,0,-1},{9095,5,0,-1}, +{9100,4,0,-1},{9100,5,0,-1},{9104,4,0,-1},{9104,5,0,-1},{9108,4,0,-1}, +{9108,5,0,-1},{9113,4,0,-1},{9113,5,0,-1},{9117,4,0,-1},{9117,5,0,-1}, +{9121,4,0,-1},{9121,5,0,-1},{9126,4,0,-1},{9126,5,0,-1},{9130,4,0,-1}, +{9130,5,0,-1},{9134,4,0,-1},{9134,5,0,-1},{9139,4,0,-1},{9139,5,0,-1}, +{9143,4,0,-1},{9143,5,0,-1},{9147,4,0,-1},{9147,5,0,-1},{9152,4,0,-1}, +{9152,5,0,-1},{9156,4,0,-1},{9156,5,0,-1},{9160,4,0,-1},{9160,5,0,-1}, +{9165,4,0,-1},{9165,5,0,-1},{9169,4,0,-1},{9169,5,0,-1},{9173,4,0,-1}, +{9173,5,0,-1},{9178,4,0,-1},{9178,5,0,-1},{9182,4,0,-1},{9182,5,0,-1}, +{9112,4,0,-1},{9186,4,0,-1},{9186,5,0,-1},{9191,4,0,-1},{9191,5,0,-1}, +{9195,4,0,-1},{9195,5,0,-1},{9125,5,0,-1},{9199,4,0,-1},{9199,5,0,-1}, +{9204,4,0,-1},{9204,5,0,-1},{9208,4,0,-1},{9208,5,0,-1},{9212,4,0,-1}, +{9212,5,0,-1},{9217,4,0,-1},{9217,5,0,-1},{9221,4,0,-1},{9221,5,0,-1}, +{9225,4,0,-1},{9225,5,0,-1},{9230,4,0,-1},{9230,5,0,-1},{9234,4,0,-1}, +{9234,5,0,-1},{9238,4,0,-1},{9238,5,0,-1},{9243,4,0,-1},{9243,5,0,-1}, +{9247,4,0,-1},{9247,5,0,-1},{9251,4,0,-1},{9251,5,0,-1},{9256,4,0,-1}, +{9256,5,0,-1},{9260,4,0,-1},{9260,5,0,-1},{9264,4,0,-1},{9264,5,0,-1}, +{9269,4,0,-1},{9269,5,0,-1},{9273,4,0,-1},{9273,5,0,-1},{9277,4,0,-1}, +{9277,5,0,-1},{9282,4,0,-1},{9282,5,0,-1},{9286,4,0,-1},{9286,5,0,-1}, +{9216,4,0,-1},{9290,4,0,-1},{9290,5,0,-1},{9295,4,0,-1},{9295,5,0,-1}, +{9299,4,0,-1},{9299,5,0,-1},{9229,5,0,-1},{9303,4,0,-1},{9303,5,0,-1}, +{9308,4,0,-1},{9308,5,0,-1},{9312,4,0,-1},{9312,5,0,-1},{9316,4,0,-1}, +{9316,5,0,-1},{9321,4,0,-1},{9321,5,0,-1},{9325,4,0,-1},{9325,5,0,-1}, +{9329,4,0,-1},{9329,5,0,-1},{9334,4,0,-1},{9334,5,0,-1},{9338,4,0,-1}, +{9338,5,0,-1},{9342,4,0,-1},{9342,5,0,-1},{9347,4,0,-1},{9347,5,0,-1}, +{9351,4,0,-1},{9351,5,0,-1},{9355,4,0,-1},{9355,5,0,-1},{9360,4,0,-1}, +{9360,5,0,-1},{9364,4,0,-1},{9364,5,0,-1},{9368,4,0,-1},{9368,5,0,-1}, +{9373,4,0,-1},{9373,5,0,-1},{9377,4,0,-1},{9377,5,0,-1},{9381,4,0,-1}, +{9381,5,0,-1},{9386,4,0,-1},{9386,5,0,-1},{9390,4,0,-1},{9390,5,0,-1}, +{9320,4,0,-1},{9394,4,0,-1},{9394,5,0,-1},{9399,4,0,-1},{9399,5,0,-1}, +{9403,4,0,-1},{9403,5,0,-1},{9333,5,0,-1},{9407,4,0,-1},{9407,5,0,-1}, +{9412,4,0,-1},{9412,5,0,-1},{9416,4,0,-1},{9416,5,0,-1},{9420,4,0,-1}, +{9420,5,0,-1},{9425,4,0,-1},{9425,5,0,-1},{9429,4,0,-1},{9429,5,0,-1}, +{9433,4,0,-1},{9433,5,0,-1},{9438,4,0,-1},{9438,5,0,-1},{9442,4,0,-1}, +{9442,5,0,-1},{9446,4,0,-1},{9446,5,0,-1},{9451,4,0,-1},{9451,5,0,-1}, +{9455,4,0,-1},{9455,5,0,-1},{9459,4,0,-1},{9459,5,0,-1},{9464,4,0,-1}, +{9464,5,0,-1},{9468,4,0,-1},{9468,5,0,-1},{9472,4,0,-1},{9472,5,0,-1}, +{9477,4,0,-1},{9477,5,0,-1},{9481,4,0,-1},{9481,5,0,-1},{9485,4,0,-1}, +{9485,5,0,-1},{9490,4,0,-1},{9490,5,0,-1},{9494,4,0,-1},{9494,5,0,-1}, +{9424,4,0,-1},{9498,4,0,-1},{9498,5,0,-1},{9503,4,0,-1},{9503,5,0,-1}, +{9507,4,0,-1},{9507,5,0,-1},{9437,5,0,-1},{9511,4,0,-1},{9511,5,0,-1}, +{9516,4,0,-1},{9516,5,0,-1},{9520,4,0,-1},{9520,5,0,-1},{9524,4,0,-1}, +{9524,5,0,-1},{9529,4,0,-1},{9529,5,0,-1},{9533,4,0,-1},{9533,5,0,-1}, +{9537,4,0,-1},{9537,5,0,-1},{9542,4,0,-1},{9542,5,0,-1},{9546,4,0,-1}, +{9546,5,0,-1},{9550,4,0,-1},{9550,5,0,-1},{9555,4,0,-1},{9555,5,0,-1}, +{9559,4,0,-1},{9559,5,0,-1},{9563,4,0,-1},{9563,5,0,-1},{9568,4,0,-1}, +{9568,5,0,-1},{9572,4,0,-1},{9572,5,0,-1},{9576,4,0,-1},{9576,5,0,-1}, +{9581,4,0,-1},{9581,5,0,-1},{9585,4,0,-1},{9585,5,0,-1},{9589,4,0,-1}, +{9589,5,0,-1},{9594,4,0,-1},{9594,5,0,-1},{9598,4,0,-1},{9598,5,0,-1}, +{9528,4,0,-1},{9602,4,0,-1},{9602,5,0,-1},{9607,4,0,-1},{9607,5,0,-1}, +{9611,4,0,-1},{9611,5,0,-1},{9541,5,0,-1},{9615,4,0,-1},{9615,5,0,-1}, +{9620,4,0,-1},{9620,5,0,-1},{9624,4,0,-1},{9624,5,0,-1},{9628,4,0,-1}, +{9628,5,0,-1},{9633,4,0,-1},{9633,5,0,-1},{9637,4,0,-1},{9637,5,0,-1}, +{9641,4,0,-1},{9641,5,0,-1},{9646,4,0,-1},{9646,5,0,-1},{9650,4,0,-1}, +{9650,5,0,-1},{9654,4,0,-1},{9654,5,0,-1},{9659,4,0,-1},{9659,5,0,-1}, +{9663,4,0,-1},{9663,5,0,-1},{9667,4,0,-1},{9667,5,0,-1},{9672,4,0,-1}, +{9672,5,0,-1},{9676,4,0,-1},{9676,5,0,-1},{9680,4,0,-1},{9680,5,0,-1}, +{9685,4,0,-1},{9689,4,0,-1},{9689,5,0,-1},{9693,4,0,-1},{9698,4,0,-1}, +{9702,4,0,-1},{9632,4,0,-1},{9706,4,0,-1},{9706,5,0,-1},{9711,4,0,-1}, +{9711,5,0,-1},{9715,4,0,-1},{9715,5,0,-1},{9645,5,0,-1},{9719,4,0,-1}, +{9719,5,0,-1},{9724,4,0,-1},{9724,5,0,-1},{9728,4,0,-1},{9728,5,0,-1}, +{9732,4,0,-1},{9737,4,0,-1},{9741,4,0,-1},{9741,5,0,-1},{9745,4,0,-1}, +{9745,5,0,-1},{9750,4,0,-1},{9754,4,0,-1},{9758,4,0,-1},{9763,4,0,-1}}; + +/* Frame number data sampled from measurement.c:lchan_meas_check_compute() + * Call was made between to phones on full rate channels TS6 and TS7 */ +struct fn_sample test_fn_tch_f_ts_6_7[] = { +{4753,0,1,-1},{4784,0,1,-1},{4835,0,1,-1},{4855,0,1,-1},{4886,0,1,-1}, +{4937,0,1,-1},{4957,0,1,-1},{4988,0,1,-1},{5039,0,1,-1},{5090,0,1,-1}, +{5141,0,1,-1},{5198,0,2,-1},{5208,0,2,-1},{5249,0,2,-1},{5300,0,2,-1}, +{5310,0,2,-1},{5351,0,2,-1},{5402,0,2,-1},{5453,0,2,-1},{5504,0,2,-1}, +{5555,0,2,-1},{8597,6,0,-1},{8627,6,0,-1},{8632,6,0,-1},{8636,6,0,-1}, +{8640,6,0,-1},{8645,6,0,-1},{8649,6,0,-1},{8653,6,0,-1},{8658,6,0,-1}, +{8662,6,0,-1},{8666,6,0,-1},{8671,6,0,-1},{8675,6,0,-1},{8679,6,0,-1}, +{8684,6,0,-1},{8688,6,0,-1},{8618,6,0,-1},{8692,6,0,-1},{8697,6,0,-1}, +{8701,6,0,-1},{8705,6,0,-1},{8710,6,0,-1},{8714,6,0,-1},{8718,6,0,-1}, +{8723,6,0,-1},{8727,6,0,-1},{8731,6,0,-1},{8736,6,0,-1},{8740,6,0,-1}, +{8744,6,0,-1},{8749,6,0,-1},{8753,6,0,-1},{8757,6,0,-1},{8762,6,0,-1}, +{8766,6,0,-1},{8770,6,0,-1},{8775,6,0,-1},{8779,6,0,-1},{8783,6,0,-1}, +{8788,6,0,-1},{8792,6,0,-1},{8722,6,0,-1},{8796,6,0,-1},{8801,6,0,-1}, +{8805,6,0,-1},{8809,6,0,-1},{8814,6,0,-1},{8818,6,0,-1},{8822,6,0,-1}, +{8827,6,0,-1},{8831,6,0,-1},{8835,6,0,-1},{8840,6,0,-1},{8844,6,0,-1}, +{8848,6,0,-1},{8853,6,0,-1},{8857,6,0,-1},{8861,6,0,-1},{8866,6,0,-1}, +{8870,6,0,-1},{8874,6,0,-1},{8874,7,0,-1},{8879,6,0,-1},{8879,7,0,-1}, +{8883,6,0,-1},{8883,7,0,-1},{8887,6,0,-1},{8887,7,0,-1},{8892,6,0,-1}, +{8892,7,0,-1},{8896,6,0,-1},{8896,7,0,-1},{8826,6,0,-1},{8900,6,0,-1}, +{8900,7,0,-1},{8905,6,0,-1},{8905,7,0,-1},{8909,6,0,-1},{8909,7,0,-1}, +{8913,6,0,-1},{8913,7,0,-1},{8918,6,0,-1},{8918,7,0,-1},{8922,6,0,-1}, +{8922,7,0,-1},{8926,6,0,-1},{8926,7,0,-1},{8931,6,0,-1},{8931,7,0,-1}, +{8935,6,0,-1},{8935,7,0,-1},{8939,6,0,-1},{8939,7,0,-1},{8944,6,0,-1}, +{8944,7,0,-1},{8948,6,0,-1},{8948,7,0,-1},{8952,6,0,-1},{8952,7,0,-1}, +{8957,6,0,-1},{8957,7,0,-1},{8961,6,0,-1},{8961,7,0,-1},{8965,6,0,-1}, +{8965,7,0,-1},{8970,6,0,-1},{8970,7,0,-1},{8974,6,0,-1},{8974,7,0,-1}, +{8978,6,0,-1},{8978,7,0,-1},{8983,6,0,-1},{8983,7,0,-1},{8987,6,0,-1}, +{8987,7,0,-1},{8991,6,0,-1},{8991,7,0,-1},{8996,6,0,-1},{8996,7,0,-1}, +{9000,6,0,-1},{9000,7,0,-1},{8930,6,0,-1},{9004,6,0,-1},{9004,7,0,-1}, +{9009,6,0,-1},{9009,7,0,-1},{9013,6,0,-1},{9013,7,0,-1},{8943,7,0,-1}, +{9017,6,0,-1},{9017,7,0,-1},{9022,6,0,-1},{9022,7,0,-1},{9026,6,0,-1}, +{9026,7,0,-1},{9030,6,0,-1},{9030,7,0,-1},{9035,6,0,-1},{9035,7,0,-1}, +{9039,6,0,-1},{9039,7,0,-1},{9043,6,0,-1},{9043,7,0,-1},{9048,6,0,-1}, +{9048,7,0,-1},{9052,6,0,-1},{9052,7,0,-1},{9056,6,0,-1},{9056,7,0,-1}, +{9061,6,0,-1},{9061,7,0,-1},{9065,6,0,-1},{9065,7,0,-1},{9069,6,0,-1}, +{9069,7,0,-1},{9074,6,0,-1},{9074,7,0,-1},{9078,6,0,-1},{9078,7,0,-1}, +{9082,6,0,-1},{9082,7,0,-1},{9087,6,0,-1},{9087,7,0,-1},{9091,6,0,-1}, +{9091,7,0,-1},{9095,6,0,-1},{9095,7,0,-1},{9100,6,0,-1},{9100,7,0,-1}, +{9104,6,0,-1},{9104,7,0,-1},{9034,6,0,-1},{9108,6,0,-1},{9108,7,0,-1}, +{9113,6,0,-1},{9113,7,0,-1},{9117,6,0,-1},{9117,7,0,-1},{9047,7,0,-1}, +{9121,6,0,-1},{9121,7,0,-1},{9126,6,0,-1},{9126,7,0,-1},{9130,6,0,-1}, +{9130,7,0,-1},{9134,6,0,-1},{9134,7,0,-1},{9139,6,0,-1},{9139,7,0,-1}, +{9143,6,0,-1},{9143,7,0,-1},{9147,6,0,-1},{9147,7,0,-1},{9152,6,0,-1}, +{9152,7,0,-1},{9156,6,0,-1},{9156,7,0,-1},{9160,6,0,-1},{9160,7,0,-1}, +{9165,6,0,-1},{9165,7,0,-1},{9169,6,0,-1},{9169,7,0,-1},{9173,6,0,-1}, +{9173,7,0,-1},{9178,6,0,-1},{9178,7,0,-1},{9182,6,0,-1},{9182,7,0,-1}, +{9186,6,0,-1},{9186,7,0,-1},{9191,6,0,-1},{9191,7,0,-1},{9195,6,0,-1}, +{9195,7,0,-1},{9199,6,0,-1},{9199,7,0,-1},{9204,6,0,-1},{9204,7,0,-1}, +{9208,6,0,-1},{9208,7,0,-1},{9138,6,0,-1},{9212,6,0,-1},{9212,7,0,-1}, +{9217,6,0,-1},{9217,7,0,-1},{9221,6,0,-1},{9221,7,0,-1},{9151,7,0,-1}, +{9225,6,0,-1},{9225,7,0,-1},{9230,6,0,-1},{9230,7,0,-1},{9234,6,0,-1}, +{9234,7,0,-1},{9238,6,0,-1},{9238,7,0,-1},{9243,6,0,-1},{9243,7,0,-1}, +{9247,6,0,-1},{9247,7,0,-1},{9251,6,0,-1},{9251,7,0,-1},{9256,6,0,-1}, +{9256,7,0,-1},{9260,6,0,-1},{9260,7,0,-1},{9264,6,0,-1},{9264,7,0,-1}, +{9269,6,0,-1},{9269,7,0,-1},{9273,6,0,-1},{9273,7,0,-1},{9277,6,0,-1}, +{9277,7,0,-1},{9282,6,0,-1},{9282,7,0,-1},{9286,6,0,-1},{9286,7,0,-1}, +{9290,6,0,-1},{9290,7,0,-1},{9295,6,0,-1},{9295,7,0,-1},{9299,6,0,-1}, +{9299,7,0,-1},{9303,6,0,-1},{9303,7,0,-1},{9308,6,0,-1},{9308,7,0,-1}, +{9312,6,0,-1},{9312,7,0,-1},{9242,6,0,-1},{9316,6,0,-1},{9316,7,0,-1}, +{9321,6,0,-1},{9321,7,0,-1},{9325,6,0,-1},{9325,7,0,-1},{9255,7,0,-1}, +{9329,6,0,-1},{9329,7,0,-1},{9334,6,0,-1},{9334,7,0,-1},{9338,6,0,-1}, +{9338,7,0,-1},{9342,6,0,-1},{9342,7,0,-1},{9347,6,0,-1},{9347,7,0,-1}, +{9351,6,0,-1},{9351,7,0,-1},{9355,6,0,-1},{9355,7,0,-1},{9360,6,0,-1}, +{9360,7,0,-1},{9364,6,0,-1},{9364,7,0,-1},{9368,6,0,-1},{9368,7,0,-1}, +{9373,6,0,-1},{9373,7,0,-1},{9377,6,0,-1},{9377,7,0,-1},{9381,6,0,-1}, +{9381,7,0,-1},{9386,6,0,-1},{9386,7,0,-1},{9390,6,0,-1},{9390,7,0,-1}, +{9394,6,0,-1},{9394,7,0,-1},{9399,6,0,-1},{9399,7,0,-1},{9403,6,0,-1}, +{9403,7,0,-1},{9407,6,0,-1},{9407,7,0,-1},{9412,6,0,-1},{9412,7,0,-1}, +{9416,6,0,-1},{9416,7,0,-1},{9346,6,0,-1},{9420,6,0,-1},{9420,7,0,-1}, +{9425,6,0,-1},{9425,7,0,-1},{9429,6,0,-1},{9429,7,0,-1},{9359,7,0,-1}, +{9433,6,0,-1},{9433,7,0,-1},{9438,6,0,-1},{9438,7,0,-1},{9442,6,0,-1}, +{9442,7,0,-1},{9446,6,0,-1},{9446,7,0,-1},{9451,6,0,-1},{9451,7,0,-1}, +{9455,6,0,-1},{9455,7,0,-1},{9459,6,0,-1},{9459,7,0,-1},{9464,6,0,-1}, +{9464,7,0,-1},{9468,6,0,-1},{9468,7,0,-1},{9472,6,0,-1},{9472,7,0,-1}, +{9477,6,0,-1},{9477,7,0,-1},{9481,6,0,-1},{9481,7,0,-1},{9485,6,0,-1}, +{9485,7,0,-1},{9490,6,0,-1},{9490,7,0,-1},{9494,6,0,-1},{9494,7,0,-1}, +{9498,6,0,-1},{9498,7,0,-1},{9503,6,0,-1},{9503,7,0,-1},{9507,6,0,-1}, +{9507,7,0,-1},{9511,6,0,-1},{9511,7,0,-1},{9516,6,0,-1},{9516,7,0,-1}, +{9520,6,0,-1},{9520,7,0,-1},{9450,6,0,-1},{9524,6,0,-1},{9524,7,0,-1}, +{9529,6,0,-1},{9529,7,0,-1},{9533,6,0,-1},{9533,7,0,-1},{9463,7,0,-1}, +{9537,6,0,-1},{9537,7,0,-1},{9542,6,0,-1},{9542,7,0,-1},{9546,6,0,-1}, +{9546,7,0,-1},{9550,6,0,-1},{9550,7,0,-1},{9555,6,0,-1},{9555,7,0,-1}, +{9559,6,0,-1},{9559,7,0,-1},{9563,6,0,-1},{9563,7,0,-1},{9568,6,0,-1}, +{9568,7,0,-1},{9572,6,0,-1},{9572,7,0,-1},{9576,6,0,-1},{9576,7,0,-1}, +{9581,6,0,-1},{9581,7,0,-1},{9585,6,0,-1},{9585,7,0,-1},{9589,6,0,-1}, +{9589,7,0,-1},{9594,6,0,-1},{9594,7,0,-1},{9598,6,0,-1},{9598,7,0,-1}, +{9602,6,0,-1},{9602,7,0,-1},{9607,6,0,-1},{9607,7,0,-1},{9611,6,0,-1}, +{9611,7,0,-1},{9615,6,0,-1},{9615,7,0,-1},{9620,6,0,-1},{9620,7,0,-1}, +{9624,6,0,-1},{9624,7,0,-1},{9554,6,0,-1},{9628,6,0,-1},{9628,7,0,-1}, +{9633,6,0,-1},{9633,7,0,-1},{9637,6,0,-1},{9637,7,0,-1},{9567,7,0,-1}, +{9641,6,0,-1},{9641,7,0,-1},{9646,6,0,-1},{9646,7,0,-1},{9650,6,0,-1}, +{9650,7,0,-1},{9654,6,0,-1},{9654,7,0,-1},{9659,6,0,-1},{9659,7,0,-1}, +{9663,6,0,-1},{9663,7,0,-1},{9667,6,0,-1},{9667,7,0,-1},{9672,6,0,-1}, +{9672,7,0,-1},{9676,6,0,-1},{9676,7,0,-1},{9680,6,0,-1},{9680,7,0,-1}, +{9685,6,0,-1},{9685,7,0,-1},{9689,6,0,-1},{9689,7,0,-1},{9693,6,0,-1}, +{9693,7,0,-1},{9698,6,0,-1},{9698,7,0,-1},{9702,6,0,-1},{9702,7,0,-1}, +{9706,6,0,-1},{9706,7,0,-1},{9711,6,0,-1},{9711,7,0,-1},{9715,6,0,-1}, +{9715,7,0,-1},{9719,6,0,-1},{9719,7,0,-1},{9724,6,0,-1},{9724,7,0,-1}, +{9728,6,0,-1},{9728,7,0,-1},{9658,6,0,-1},{9732,6,0,-1},{9732,7,0,-1}, +{9737,6,0,-1},{9737,7,0,-1},{9741,6,0,-1},{9741,7,0,-1},{9671,7,0,-1}, +{9745,6,0,-1},{9745,7,0,-1},{9750,6,0,-1},{9750,7,0,-1},{9754,6,0,-1}, +{9754,7,0,-1},{9758,6,0,-1},{9758,7,0,-1},{9763,6,0,-1},{9763,7,0,-1}, +{9767,6,0,-1},{9767,7,0,-1},{9771,6,0,-1},{9771,7,0,-1},{9776,6,0,-1}, +{9776,7,0,-1},{9780,6,0,-1},{9780,7,0,-1},{9784,6,0,-1},{9784,7,0,-1}, +{9789,6,0,-1},{9789,7,0,-1},{9793,6,0,-1},{9793,7,0,-1},{9797,6,0,-1}, +{9797,7,0,-1},{9802,6,0,-1},{9802,7,0,-1},{9806,6,0,-1},{9806,7,0,-1}, +{9810,6,0,-1},{9810,7,0,-1},{9815,6,0,-1},{9815,7,0,-1},{9819,6,0,-1}, +{9819,7,0,-1},{9823,6,0,-1},{9823,7,0,-1},{9828,6,0,-1},{9828,7,0,-1}, +{9832,6,0,-1},{9832,7,0,-1},{9762,6,0,-1},{9836,6,0,-1},{9836,7,0,-1}, +{9841,6,0,-1},{9841,7,0,-1},{9845,6,0,-1},{9845,7,0,-1},{9775,7,0,-1}, +{9849,6,0,-1},{9849,7,0,-1},{9854,6,0,-1},{9854,7,0,-1},{9858,6,0,-1}, +{9858,7,0,-1},{9862,6,0,-1},{9862,7,0,-1},{9867,6,0,-1},{9867,7,0,-1}, +{9871,6,0,-1},{9871,7,0,-1},{9875,6,0,-1},{9875,7,0,-1},{9880,6,0,-1}, +{9880,7,0,-1},{9884,6,0,-1},{9884,7,0,-1},{9888,6,0,-1},{9888,7,0,-1}, +{9893,6,0,-1},{9893,7,0,-1},{9897,6,0,-1},{9897,7,0,-1},{9901,6,0,-1}, +{9901,7,0,-1},{9906,6,0,-1},{9906,7,0,-1},{9910,6,0,-1},{9910,7,0,-1}, +{9914,6,0,-1},{9914,7,0,-1},{9919,6,0,-1},{9919,7,0,-1},{9923,6,0,-1}, +{9923,7,0,-1},{9927,6,0,-1},{9927,7,0,-1},{9932,6,0,-1},{9932,7,0,-1}, +{9936,6,0,-1},{9936,7,0,-1},{9866,6,0,-1},{9940,6,0,-1},{9940,7,0,-1}, +{9945,6,0,-1},{9945,7,0,-1},{9949,6,0,-1},{9949,7,0,-1},{9879,7,0,-1}, +{9953,6,0,-1},{9953,7,0,-1},{9958,6,0,-1},{9958,7,0,-1},{9962,6,0,-1}, +{9962,7,0,-1},{9966,6,0,-1},{9966,7,0,-1},{9971,6,0,-1},{9971,7,0,-1}, +{9975,6,0,-1},{9975,7,0,-1},{9979,6,0,-1},{9979,7,0,-1},{9984,6,0,-1}, +{9984,7,0,-1},{9988,6,0,-1},{9988,7,0,-1},{9992,6,0,-1},{9992,7,0,-1}, +{9997,6,0,-1},{9997,7,0,-1},{10001,6,0,-1},{10001,7,0,-1},{10005,6,0,-1}, +{10005,7,0,-1},{10010,6,0,-1},{10010,7,0,-1},{10014,6,0,-1},{10014,7,0,-1}, +{10018,6,0,-1},{10018,7,0,-1},{10023,6,0,-1},{10023,7,0,-1},{10027,6,0,-1}, +{10027,7,0,-1},{10031,6,0,-1},{10031,7,0,-1},{10036,6,0,-1},{10036,7,0,-1}, +{10040,6,0,-1},{10040,7,0,-1},{9970,6,0,-1},{10044,6,0,-1},{10044,7,0,-1}, +{10049,6,0,-1},{10049,7,0,-1},{10053,6,0,-1},{10053,7,0,-1},{9983,7,0,-1}, +{10057,6,0,-1},{10057,7,0,-1},{10062,6,0,-1},{10062,7,0,-1},{10066,6,0,-1}, +{10066,7,0,-1},{10070,6,0,-1},{10070,7,0,-1},{10075,6,0,-1},{10075,7,0,-1}, +{10079,6,0,-1},{10079,7,0,-1},{10083,6,0,-1},{10083,7,0,-1},{10088,6,0,-1}, +{10088,7,0,-1},{10092,6,0,-1},{10092,7,0,-1},{10096,6,0,-1},{10096,7,0,-1}, +{10101,6,0,-1},{10101,7,0,-1},{10105,6,0,-1},{10105,7,0,-1},{10109,6,0,-1}, +{10109,7,0,-1},{10114,6,0,-1},{10114,7,0,-1},{10118,6,0,-1},{10118,7,0,-1}, +{10122,6,0,-1},{10122,7,0,-1},{10127,6,0,-1},{10127,7,0,-1},{10131,6,0,-1}, +{10131,7,0,-1},{10135,6,0,-1},{10135,7,0,-1},{10140,6,0,-1},{10140,7,0,-1}, +{10144,6,0,-1},{10144,7,0,-1},{10074,6,0,-1},{10148,6,0,-1},{10148,7,0,-1}, +{10153,6,0,-1},{10153,7,0,-1},{10157,6,0,-1},{10157,7,0,-1},{10087,7,0,-1}, +{10161,6,0,-1},{10161,7,0,-1},{10166,6,0,-1},{10166,7,0,-1},{10170,6,0,-1}, +{10170,7,0,-1},{10174,6,0,-1},{10174,7,0,-1},{10179,6,0,-1},{10179,7,0,-1}, +{10183,6,0,-1},{10183,7,0,-1},{10187,6,0,-1},{10187,7,0,-1},{10192,6,0,-1}, +{10192,7,0,-1},{10196,6,0,-1},{10196,7,0,-1},{10200,6,0,-1},{10200,7,0,-1}, +{10205,6,0,-1},{10205,7,0,-1},{10209,6,0,-1},{10209,7,0,-1},{10213,6,0,-1}, +{10213,7,0,-1},{10218,6,0,-1},{10218,7,0,-1},{10222,6,0,-1},{10222,7,0,-1}, +{10226,6,0,-1},{10226,7,0,-1},{10231,6,0,-1},{10231,7,0,-1},{10235,6,0,-1}, +{10235,7,0,-1},{10239,6,0,-1},{10239,7,0,-1},{10244,6,0,-1},{10244,7,0,-1}, +{10248,6,0,-1},{10248,7,0,-1},{10178,6,0,-1},{10252,6,0,-1},{10252,7,0,-1}, +{10257,6,0,-1},{10257,7,0,-1},{10261,6,0,-1},{10261,7,0,-1},{10191,7,0,-1}, +{10265,6,0,-1},{10265,7,0,-1},{10270,6,0,-1},{10270,7,0,-1},{10274,6,0,-1}, +{10274,7,0,-1},{10278,6,0,-1},{10278,7,0,-1},{10283,6,0,-1},{10283,7,0,-1}, +{10287,6,0,-1},{10287,7,0,-1},{10291,6,0,-1},{10291,7,0,-1},{10296,6,0,-1}, +{10296,7,0,-1},{10300,6,0,-1},{10300,7,0,-1},{10304,6,0,-1},{10304,7,0,-1}, +{10309,6,0,-1},{10309,7,0,-1},{10313,6,0,-1},{10313,7,0,-1},{10317,6,0,-1}, +{10317,7,0,-1},{10322,6,0,-1},{10322,7,0,-1},{10326,6,0,-1},{10326,7,0,-1}, +{10330,6,0,-1},{10330,7,0,-1},{10335,6,0,-1},{10335,7,0,-1},{10339,6,0,-1}, +{10339,7,0,-1},{10343,6,0,-1},{10343,7,0,-1},{10348,6,0,-1},{10348,7,0,-1}, +{10352,6,0,-1},{10352,7,0,-1},{10282,6,0,-1},{10356,6,0,-1},{10356,7,0,-1}, +{10361,6,0,-1},{10361,7,0,-1},{10365,6,0,-1},{10365,7,0,-1},{10295,7,0,-1}, +{10369,6,0,-1},{10369,7,0,-1},{10374,6,0,-1},{10374,7,0,-1},{10378,6,0,-1}, +{10378,7,0,-1},{10382,6,0,-1},{10382,7,0,-1},{10387,6,0,-1},{10387,7,0,-1}, +{10391,6,0,-1},{10391,7,0,-1},{10395,6,0,-1},{10395,7,0,-1},{10400,6,0,-1}, +{10400,7,0,-1},{10404,6,0,-1},{10404,7,0,-1},{10408,6,0,-1},{10408,7,0,-1}, +{10413,6,0,-1},{10413,7,0,-1},{10417,6,0,-1},{10417,7,0,-1},{10421,6,0,-1}, +{10421,7,0,-1},{10426,6,0,-1},{10426,7,0,-1},{10430,6,0,-1},{10430,7,0,-1}, +{10434,6,0,-1},{10434,7,0,-1},{10439,6,0,-1},{10439,7,0,-1},{10443,6,0,-1}, +{10443,7,0,-1},{10447,6,0,-1},{10447,7,0,-1},{10452,6,0,-1},{10452,7,0,-1}, +{10456,6,0,-1},{10456,7,0,-1},{10386,6,0,-1},{10460,6,0,-1},{10460,7,0,-1}, +{10465,6,0,-1},{10465,7,0,-1},{10469,6,0,-1},{10469,7,0,-1},{10399,7,0,-1}, +{10473,6,0,-1},{10473,7,0,-1},{10478,6,0,-1},{10478,7,0,-1},{10482,6,0,-1}, +{10482,7,0,-1},{10486,6,0,-1},{10486,7,0,-1},{10491,6,0,-1},{10491,7,0,-1}, +{10495,6,0,-1},{10495,7,0,-1},{10499,6,0,-1},{10499,7,0,-1},{10504,6,0,-1}, +{10504,7,0,-1},{10508,6,0,-1},{10508,7,0,-1},{10512,6,0,-1},{10512,7,0,-1}, +{10517,6,0,-1},{10517,7,0,-1},{10521,6,0,-1},{10521,7,0,-1},{10525,6,0,-1}, +{10525,7,0,-1},{10530,6,0,-1},{10530,7,0,-1},{10534,6,0,-1},{10534,7,0,-1}, +{10538,6,0,-1},{10538,7,0,-1},{10543,6,0,-1},{10543,7,0,-1},{10547,6,0,-1}, +{10547,7,0,-1},{10551,6,0,-1},{10551,7,0,-1},{10556,6,0,-1},{10556,7,0,-1}, +{10560,6,0,-1},{10560,7,0,-1},{10490,6,0,-1},{10564,6,0,-1},{10564,7,0,-1}, +{10569,6,0,-1},{10569,7,0,-1},{10573,6,0,-1},{10573,7,0,-1},{10503,7,0,-1}, +{10577,6,0,-1},{10577,7,0,-1},{10582,6,0,-1},{10582,7,0,-1},{10586,6,0,-1}, +{10586,7,0,-1},{10590,6,0,-1},{10590,7,0,-1},{10595,6,0,-1},{10595,7,0,-1}, +{10599,6,0,-1},{10599,7,0,-1},{10603,6,0,-1},{10603,7,0,-1},{10608,6,0,-1}, +{10608,7,0,-1},{10612,6,0,-1},{10612,7,0,-1},{10616,6,0,-1},{10616,7,0,-1}, +{10621,6,0,-1},{10621,7,0,-1},{10625,6,0,-1},{10625,7,0,-1},{10629,6,0,-1}, +{10629,7,0,-1},{10634,6,0,-1},{10634,7,0,-1},{10638,6,0,-1},{10638,7,0,-1}, +{10642,6,0,-1},{10642,7,0,-1},{10647,6,0,-1},{10647,7,0,-1},{10651,6,0,-1}, +{10651,7,0,-1},{10655,6,0,-1},{10655,7,0,-1},{10660,6,0,-1},{10660,7,0,-1}, +{10664,6,0,-1},{10664,7,0,-1},{10594,6,0,-1},{10668,6,0,-1},{10668,7,0,-1}, +{10673,6,0,-1},{10673,7,0,-1},{10677,6,0,-1},{10677,7,0,-1},{10607,7,0,-1}, +{10681,6,0,-1},{10681,7,0,-1},{10686,6,0,-1},{10686,7,0,-1},{10690,6,0,-1}, +{10690,7,0,-1},{10694,6,0,-1},{10694,7,0,-1},{10699,6,0,-1},{10699,7,0,-1}, +{10703,6,0,-1},{10703,7,0,-1},{10707,6,0,-1},{10707,7,0,-1},{10712,6,0,-1}, +{10712,7,0,-1},{10716,6,0,-1},{10716,7,0,-1},{10720,6,0,-1},{10720,7,0,-1}, +{10725,6,0,-1},{10725,7,0,-1},{10729,6,0,-1},{10729,7,0,-1},{10733,6,0,-1}, +{10733,7,0,-1},{10738,6,0,-1},{10738,7,0,-1},{10742,6,0,-1},{10742,7,0,-1}, +{10746,6,0,-1},{10746,7,0,-1},{10751,6,0,-1},{10751,7,0,-1},{10755,6,0,-1}, +{10755,7,0,-1},{10759,6,0,-1},{10759,7,0,-1},{10764,6,0,-1},{10764,7,0,-1}, +{10768,6,0,-1},{10768,7,0,-1},{10698,6,0,-1},{10772,6,0,-1},{10772,7,0,-1}, +{10777,6,0,-1},{10777,7,0,-1},{10781,6,0,-1},{10781,7,0,-1},{10711,7,0,-1}, +{10785,6,0,-1},{10785,7,0,-1},{10790,6,0,-1},{10790,7,0,-1},{10794,6,0,-1}, +{10794,7,0,-1},{10798,6,0,-1},{10798,7,0,-1},{10803,6,0,-1},{10803,7,0,-1}, +{10807,6,0,-1},{10807,7,0,-1},{10811,6,0,-1},{10811,7,0,-1},{10816,6,0,-1}, +{10816,7,0,-1},{10820,6,0,-1},{10820,7,0,-1},{10824,6,0,-1},{10824,7,0,-1}, +{10829,6,0,-1},{10829,7,0,-1},{10833,6,0,-1},{10833,7,0,-1},{10837,6,0,-1}, +{10837,7,0,-1},{10842,6,0,-1},{10842,7,0,-1},{10846,6,0,-1},{10846,7,0,-1}, +{10850,6,0,-1},{10850,7,0,-1},{10855,6,0,-1},{10855,7,0,-1},{10859,6,0,-1}, +{10859,7,0,-1},{10863,6,0,-1},{10863,7,0,-1},{10868,6,0,-1},{10868,7,0,-1}, +{10872,6,0,-1},{10872,7,0,-1},{10802,6,0,-1},{10876,6,0,-1},{10876,7,0,-1}, +{10881,6,0,-1},{10881,7,0,-1},{10885,6,0,-1},{10885,7,0,-1},{10815,7,0,-1}, +{10889,6,0,-1},{10889,7,0,-1},{10894,6,0,-1},{10894,7,0,-1},{10898,6,0,-1}, +{10898,7,0,-1},{10902,6,0,-1},{10902,7,0,-1},{10907,6,0,-1},{10907,7,0,-1}, +{10911,6,0,-1},{10911,7,0,-1},{10915,6,0,-1},{10915,7,0,-1},{10920,6,0,-1}, +{10920,7,0,-1},{10924,6,0,-1},{10924,7,0,-1},{10928,6,0,-1},{10928,7,0,-1}, +{10933,6,0,-1},{10933,7,0,-1},{10937,6,0,-1},{10937,7,0,-1},{10941,6,0,-1}, +{10941,7,0,-1},{10946,6,0,-1},{10946,7,0,-1},{10950,6,0,-1},{10950,7,0,-1}, +{10954,6,0,-1},{10954,7,0,-1},{10959,6,0,-1},{10959,7,0,-1},{10963,6,0,-1}, +{10963,7,0,-1},{10967,6,0,-1},{10967,7,0,-1},{10972,6,0,-1},{10972,7,0,-1}, +{10976,6,0,-1},{10976,7,0,-1},{10906,6,0,-1},{10980,6,0,-1},{10980,7,0,-1}, +{10985,6,0,-1},{10985,7,0,-1},{10989,6,0,-1},{10989,7,0,-1},{10919,7,0,-1}, +{10993,6,0,-1},{10993,7,0,-1},{10998,6,0,-1},{10998,7,0,-1},{11002,6,0,-1}, +{11002,7,0,-1},{11006,6,0,-1},{11006,7,0,-1},{11011,6,0,-1},{11011,7,0,-1}, +{11015,6,0,-1},{11015,7,0,-1},{11019,6,0,-1},{11019,7,0,-1},{11024,6,0,-1}, +{11024,7,0,-1},{11028,6,0,-1},{11028,7,0,-1},{11032,6,0,-1},{11032,7,0,-1}, +{11037,6,0,-1},{11037,7,0,-1},{11041,6,0,-1},{11041,7,0,-1},{11045,6,0,-1}, +{11045,7,0,-1},{11050,6,0,-1},{11050,7,0,-1},{11054,6,0,-1},{11054,7,0,-1}, +{11058,6,0,-1},{11058,7,0,-1},{11063,6,0,-1},{11063,7,0,-1},{11067,6,0,-1}, +{11067,7,0,-1},{11071,6,0,-1},{11071,7,0,-1},{11076,6,0,-1},{11076,7,0,-1}, +{11080,6,0,-1},{11080,7,0,-1},{11010,6,0,-1},{11084,6,0,-1},{11084,7,0,-1}, +{11089,6,0,-1},{11089,7,0,-1},{11093,6,0,-1},{11093,7,0,-1},{11023,7,0,-1}, +{11097,6,0,-1},{11097,7,0,-1},{11102,6,0,-1},{11102,7,0,-1},{11106,6,0,-1}, +{11106,7,0,-1},{11110,6,0,-1},{11110,7,0,-1},{11115,6,0,-1},{11115,7,0,-1}, +{11119,6,0,-1},{11119,7,0,-1},{11123,6,0,-1},{11123,7,0,-1},{11128,6,0,-1}, +{11128,7,0,-1},{11132,6,0,-1},{11132,7,0,-1},{11136,6,0,-1},{11136,7,0,-1}, +{11141,6,0,-1},{11141,7,0,-1},{11145,6,0,-1},{11145,7,0,-1},{11149,6,0,-1}, +{11149,7,0,-1},{11154,6,0,-1},{11154,7,0,-1},{11158,6,0,-1},{11158,7,0,-1}, +{11162,6,0,-1},{11162,7,0,-1},{11167,6,0,-1},{11167,7,0,-1},{11171,6,0,-1}, +{11171,7,0,-1},{11175,6,0,-1},{11175,7,0,-1},{11180,6,0,-1},{11180,7,0,-1}, +{11184,6,0,-1},{11184,7,0,-1},{11114,6,0,-1},{11188,6,0,-1},{11188,7,0,-1}, +{11193,6,0,-1},{11193,7,0,-1},{11197,6,0,-1},{11197,7,0,-1},{11127,7,0,-1}, +{11201,6,0,-1},{11201,7,0,-1},{11206,6,0,-1},{11206,7,0,-1},{11210,6,0,-1}, +{11210,7,0,-1},{11214,6,0,-1},{11214,7,0,-1},{11219,6,0,-1},{11219,7,0,-1}, +{11223,6,0,-1},{11223,7,0,-1},{11227,6,0,-1},{11227,7,0,-1},{11232,6,0,-1}, +{11232,7,0,-1},{11236,6,0,-1},{11236,7,0,-1},{11240,6,0,-1},{11240,7,0,-1}, +{11245,6,0,-1},{11245,7,0,-1},{11249,6,0,-1},{11249,7,0,-1},{11253,6,0,-1}, +{11253,7,0,-1},{11258,6,0,-1},{11258,7,0,-1},{11262,6,0,-1},{11262,7,0,-1}, +{11266,6,0,-1},{11266,7,0,-1},{11271,6,0,-1},{11271,7,0,-1},{11275,6,0,-1}, +{11275,7,0,-1},{11279,6,0,-1},{11279,7,0,-1},{11284,6,0,-1},{11284,7,0,-1}, +{11288,6,0,-1},{11288,7,0,-1},{11218,6,0,-1},{11292,6,0,-1},{11292,7,0,-1}, +{11297,6,0,-1},{11297,7,0,-1},{11301,6,0,-1},{11301,7,0,-1},{11231,7,0,-1}, +{11305,6,0,-1},{11305,7,0,-1},{11310,6,0,-1},{11310,7,0,-1},{11314,6,0,-1}, +{11314,7,0,-1},{11318,6,0,-1},{11318,7,0,-1},{11323,6,0,-1},{11323,7,0,-1}, +{11327,6,0,-1},{11327,7,0,-1},{11331,6,0,-1},{11331,7,0,-1},{11336,6,0,-1}, +{11336,7,0,-1},{11340,6,0,-1},{11340,7,0,-1},{11344,6,0,-1},{11344,7,0,-1}, +{11349,6,0,-1},{11349,7,0,-1},{11353,6,0,-1},{11353,7,0,-1},{11357,6,0,-1}, +{11357,7,0,-1},{11362,6,0,-1},{11362,7,0,-1},{11366,6,0,-1},{11366,7,0,-1}, +{11370,6,0,-1},{11370,7,0,-1},{11375,6,0,-1},{11375,7,0,-1},{11379,6,0,-1}, +{11379,7,0,-1},{11383,6,0,-1},{11383,7,0,-1},{11388,6,0,-1},{11388,7,0,-1}, +{11392,6,0,-1},{11392,7,0,-1},{11322,6,0,-1},{11396,6,0,-1},{11396,7,0,-1}, +{11401,6,0,-1},{11401,7,0,-1},{11405,6,0,-1},{11405,7,0,-1},{11335,7,0,-1}, +{11409,6,0,-1},{11409,7,0,-1},{11414,6,0,-1},{11414,7,0,-1},{11418,6,0,-1}, +{11418,7,0,-1},{11422,6,0,-1},{11422,7,0,-1},{11427,6,0,-1},{11427,7,0,-1}, +{11431,6,0,-1},{11431,7,0,-1},{11435,6,0,-1},{11435,7,0,-1},{11440,6,0,-1}, +{11440,7,0,-1},{11444,6,0,-1},{11444,7,0,-1},{11448,6,0,-1},{11448,7,0,-1}, +{11453,6,0,-1},{11453,7,0,-1},{11457,6,0,-1},{11457,7,0,-1},{11461,6,0,-1}, +{11461,7,0,-1},{11466,6,0,-1},{11466,7,0,-1},{11470,6,0,-1},{11470,7,0,-1}, +{11474,6,0,-1},{11474,7,0,-1},{11479,6,0,-1},{11479,7,0,-1},{11483,6,0,-1}, +{11483,7,0,-1},{11487,6,0,-1},{11487,7,0,-1},{11492,6,0,-1},{11492,7,0,-1}, +{11496,6,0,-1},{11496,7,0,-1},{11426,6,0,-1},{11500,6,0,-1},{11500,7,0,-1}, +{11505,6,0,-1},{11505,7,0,-1},{11509,6,0,-1},{11509,7,0,-1},{11439,7,0,-1}, +{11513,6,0,-1},{11513,7,0,-1},{11518,6,0,-1},{11518,7,0,-1},{11522,6,0,-1}, +{11522,7,0,-1},{11526,6,0,-1},{11526,7,0,-1},{11531,6,0,-1},{11531,7,0,-1}, +{11535,6,0,-1},{11535,7,0,-1},{11539,6,0,-1},{11539,7,0,-1},{11544,6,0,-1}, +{11544,7,0,-1},{11548,6,0,-1},{11548,7,0,-1},{11552,6,0,-1},{11552,7,0,-1}, +{11557,6,0,-1},{11557,7,0,-1},{11561,6,0,-1},{11561,7,0,-1},{11565,6,0,-1}, +{11565,7,0,-1},{11570,6,0,-1},{11570,7,0,-1},{11574,6,0,-1},{11574,7,0,-1}, +{11578,6,0,-1},{11578,7,0,-1},{11583,6,0,-1},{11583,7,0,-1},{11587,6,0,-1}, +{11587,7,0,-1},{11591,6,0,-1},{11591,7,0,-1},{11596,6,0,-1},{11596,7,0,-1}, +{11600,6,0,-1},{11600,7,0,-1},{11530,6,0,-1},{11604,6,0,-1},{11604,7,0,-1}, +{11609,6,0,-1},{11609,7,0,-1},{11613,6,0,-1},{11613,7,0,-1},{11543,7,0,-1}, +{11617,6,0,-1},{11617,7,0,-1},{11622,6,0,-1},{11622,7,0,-1},{11626,6,0,-1}, +{11626,7,0,-1},{11630,6,0,-1},{11630,7,0,-1},{11635,6,0,-1},{11635,7,0,-1}, +{11639,6,0,-1},{11639,7,0,-1},{11643,6,0,-1},{11643,7,0,-1},{11648,6,0,-1}, +{11648,7,0,-1},{11652,6,0,-1},{11652,7,0,-1},{11656,6,0,-1},{11656,7,0,-1}, +{11661,6,0,-1},{11661,7,0,-1},{11665,6,0,-1},{11665,7,0,-1},{11669,6,0,-1}, +{11669,7,0,-1},{11674,6,0,-1},{11674,7,0,-1},{11678,6,0,-1},{11678,7,0,-1}, +{11682,6,0,-1},{11682,7,0,-1},{11687,6,0,-1},{11687,7,0,-1},{11691,6,0,-1}, +{11691,7,0,-1},{11695,6,0,-1},{11695,7,0,-1},{11700,6,0,-1},{11700,7,0,-1}, +{11704,6,0,-1},{11704,7,0,-1},{11634,6,0,-1},{11708,6,0,-1},{11708,7,0,-1}, +{11713,6,0,-1},{11713,7,0,-1},{11717,6,0,-1},{11717,7,0,-1},{11647,7,0,-1}, +{11721,6,0,-1},{11721,7,0,-1},{11726,6,0,-1},{11726,7,0,-1},{11730,6,0,-1}, +{11730,7,0,-1},{11734,6,0,-1},{11734,7,0,-1},{11739,6,0,-1},{11739,7,0,-1}, +{11743,6,0,-1},{11743,7,0,-1},{11747,6,0,-1},{11747,7,0,-1},{11752,6,0,-1}, +{11752,7,0,-1},{11756,6,0,-1},{11756,7,0,-1},{11760,6,0,-1},{11760,7,0,-1}, +{11765,6,0,-1},{11765,7,0,-1},{11769,6,0,-1},{11769,7,0,-1},{11773,6,0,-1}, +{11773,7,0,-1},{11778,6,0,-1},{11778,7,0,-1},{11782,6,0,-1},{11782,7,0,-1}, +{11786,6,0,-1},{11786,7,0,-1},{11791,6,0,-1},{11791,7,0,-1},{11795,6,0,-1}, +{11795,7,0,-1},{11799,6,0,-1},{11799,7,0,-1},{11804,6,0,-1},{11804,7,0,-1}, +{11808,6,0,-1},{11808,7,0,-1},{11738,6,0,-1},{11812,6,0,-1},{11812,7,0,-1}, +{11817,6,0,-1},{11817,7,0,-1},{11821,6,0,-1},{11821,7,0,-1},{11751,7,0,-1}, +{11825,6,0,-1},{11825,7,0,-1},{11830,6,0,-1},{11830,7,0,-1},{11834,6,0,-1}, +{11834,7,0,-1},{11838,6,0,-1},{11838,7,0,-1},{11843,6,0,-1},{11843,7,0,-1}, +{11847,6,0,-1},{11847,7,0,-1},{11851,6,0,-1},{11851,7,0,-1},{11856,6,0,-1}, +{11856,7,0,-1},{11860,6,0,-1},{11860,7,0,-1},{11864,6,0,-1},{11864,7,0,-1}, +{11869,6,0,-1},{11869,7,0,-1},{11873,6,0,-1},{11873,7,0,-1},{11877,6,0,-1}, +{11877,7,0,-1},{11882,6,0,-1},{11882,7,0,-1},{11886,6,0,-1},{11886,7,0,-1}, +{11890,6,0,-1},{11890,7,0,-1},{11895,6,0,-1},{11895,7,0,-1},{11899,6,0,-1}, +{11899,7,0,-1},{11903,6,0,-1},{11903,7,0,-1},{11908,6,0,-1},{11908,7,0,-1}, +{11912,6,0,-1},{11912,7,0,-1},{11842,6,0,-1},{11916,6,0,-1},{11916,7,0,-1}, +{11921,6,0,-1},{11921,7,0,-1},{11925,6,0,-1},{11925,7,0,-1},{11855,7,0,-1}, +{11929,6,0,-1},{11929,7,0,-1},{11934,6,0,-1},{11934,7,0,-1},{11938,6,0,-1}, +{11938,7,0,-1},{11942,6,0,-1},{11942,7,0,-1},{11947,6,0,-1},{11947,7,0,-1}, +{11951,6,0,-1},{11951,7,0,-1},{11955,6,0,-1},{11955,7,0,-1},{11960,6,0,-1}, +{11960,7,0,-1},{11964,6,0,-1},{11964,7,0,-1},{11968,6,0,-1},{11968,7,0,-1}, +{11973,6,0,-1},{11973,7,0,-1},{11977,6,0,-1},{11977,7,0,-1},{11981,6,0,-1}, +{11981,7,0,-1},{11986,6,0,-1},{11986,7,0,-1},{11990,6,0,-1},{11990,7,0,-1}, +{11994,6,0,-1},{11994,7,0,-1},{11999,6,0,-1},{11999,7,0,-1},{12003,6,0,-1}, +{12003,7,0,-1},{12007,6,0,-1},{12007,7,0,-1},{12012,6,0,-1},{12012,7,0,-1}, +{12016,6,0,-1},{12016,7,0,-1},{11946,6,0,-1},{12020,6,0,-1},{12020,7,0,-1}, +{12025,6,0,-1},{12025,7,0,-1},{12029,6,0,-1},{12029,7,0,-1},{11959,7,0,-1}, +{12033,6,0,-1},{12033,7,0,-1},{12038,6,0,-1},{12038,7,0,-1},{12042,6,0,-1}, +{12042,7,0,-1},{12046,6,0,-1},{12046,7,0,-1},{12051,6,0,-1},{12051,7,0,-1}, +{12055,6,0,-1},{12055,7,0,-1},{12059,6,0,-1},{12059,7,0,-1},{12064,6,0,-1}, +{12064,7,0,-1},{12068,6,0,-1},{12068,7,0,-1},{12072,6,0,-1},{12072,7,0,-1}, +{12077,6,0,-1},{12077,7,0,-1},{12081,6,0,-1},{12081,7,0,-1},{12085,6,0,-1}, +{12085,7,0,-1},{12090,6,0,-1},{12090,7,0,-1},{12094,6,0,-1},{12094,7,0,-1}, +{12098,6,0,-1},{12098,7,0,-1},{12103,6,0,-1},{12103,7,0,-1},{12107,6,0,-1}, +{12107,7,0,-1},{12111,6,0,-1},{12111,7,0,-1},{12116,6,0,-1},{12116,7,0,-1}, +{12120,6,0,-1},{12120,7,0,-1},{12050,6,0,-1},{12124,6,0,-1},{12124,7,0,-1}, +{12129,6,0,-1},{12129,7,0,-1},{12133,6,0,-1},{12133,7,0,-1},{12063,7,0,-1}, +{12137,6,0,-1},{12137,7,0,-1},{12142,6,0,-1},{12142,7,0,-1},{12146,6,0,-1}, +{12146,7,0,-1},{12150,6,0,-1},{12150,7,0,-1},{12155,6,0,-1},{12155,7,0,-1}, +{12159,6,0,-1},{12159,7,0,-1},{12163,6,0,-1},{12163,7,0,-1},{12168,6,0,-1}, +{12168,7,0,-1},{12172,6,0,-1},{12172,7,0,-1},{12176,6,0,-1},{12176,7,0,-1}, +{12181,6,0,-1},{12181,7,0,-1},{12185,6,0,-1},{12185,7,0,-1},{12189,6,0,-1}, +{12189,7,0,-1},{12194,6,0,-1},{12194,7,0,-1},{12198,6,0,-1},{12198,7,0,-1}, +{12202,6,0,-1},{12202,7,0,-1},{12207,6,0,-1},{12207,7,0,-1},{12211,6,0,-1}, +{12211,7,0,-1},{12215,6,0,-1},{12215,7,0,-1},{12220,6,0,-1},{12220,7,0,-1}, +{12224,6,0,-1},{12224,7,0,-1},{12154,6,0,-1},{12228,6,0,-1},{12228,7,0,-1}, +{12233,6,0,-1},{12233,7,0,-1},{12237,6,0,-1},{12237,7,0,-1},{12167,7,0,-1}, +{12241,6,0,-1},{12241,7,0,-1},{12246,6,0,-1},{12246,7,0,-1},{12250,7,0,-1}, +{12254,7,0,-1},{12259,6,0,-1},{12259,7,0,-1},{12263,7,0,-1},{12267,7,0,-1}, +{12272,6,0,-1},{12272,7,0,-1},{12276,6,0,-1},{12276,7,0,-1},{12280,6,0,-1}, +{12280,7,0,-1},{12285,6,0,-1},{12285,7,0,-1},{12289,7,0,-1},{12293,7,0,-1}, +{12298,7,0,-1},{12302,7,0,-1},{12306,6,0,-1},{12306,7,0,-1},{12311,6,0,-1}, +{12311,7,0,-1},{12315,7,0,-1},{12319,7,0,-1},{12324,7,0,-1},{12328,7,0,-1}}; + +/* Frame number data sampled from measurement.c:lchan_meas_check_compute() + * Call was made between to phones on half rate channels TS2, SS0 and SS1 */ +struct fn_sample test_fn_tch_h_ts_2_ss0_ss1[] = { +{8252,0,1,-1},{8987,2,0,-1},{8996,2,0,-1},{9004,2,0,-1},{9013,2,0,-1}, +{9022,2,0,-1},{9030,2,0,-1},{9039,2,0,-1},{9043,2,0,-1},{9048,2,0,-1}, +{8982,2,0,-1},{9056,2,0,-1},{9065,2,0,-1},{9074,2,0,-1},{9082,2,0,-1}, +{9091,2,0,-1},{9100,2,0,-1},{9108,2,0,-1},{9117,2,0,-1},{9126,2,0,-1}, +{9134,2,0,-1},{9143,2,0,-1},{9152,2,0,-1},{9086,2,0,-1},{9160,2,0,-1}, +{9169,2,0,-1},{9173,2,0,-1},{9178,2,0,-1},{9182,2,0,-1},{9186,2,0,-1}, +{9191,2,0,-1},{9195,2,0,-1},{9199,2,0,-1},{9204,2,0,-1},{9208,2,0,-1}, +{9212,2,0,-1},{9217,2,0,-1},{9221,2,0,-1},{9225,2,0,-1},{9230,2,0,-1}, +{9234,2,0,-1},{9238,2,0,-1},{9243,2,0,-1},{9247,2,0,-1},{9251,2,0,-1}, +{9256,2,0,-1},{9190,2,0,-1},{9260,2,0,-1},{9264,2,0,-1},{9269,2,0,-1}, +{9273,2,0,-1},{9277,2,0,-1},{9282,2,0,-1},{9286,2,0,-1},{9290,2,0,-1}, +{9295,2,0,-1},{9299,2,0,-1},{9303,2,0,-1},{9308,2,0,-1},{9312,2,0,-1}, +{9316,2,0,-1},{9321,2,0,-1},{9325,2,0,-1},{9329,2,0,-1},{9334,2,0,-1}, +{9338,2,0,-1},{9342,2,0,-1},{9347,2,0,-1},{9351,2,0,-1},{9355,2,0,-1}, +{9360,2,0,-1},{9294,2,0,-1},{9364,2,0,-1},{9368,2,0,-1},{9369,2,1,-1}, +{9373,2,0,-1},{9377,2,0,-1},{9381,2,0,-1},{9382,2,1,-1},{9386,2,0,-1}, +{9390,2,0,-1},{9391,2,1,-1},{9394,2,0,-1},{9395,2,1,-1},{9399,2,0,-1}, +{9403,2,0,-1},{9404,2,1,-1},{9407,2,0,-1},{9412,2,0,-1},{9413,2,1,-1}, +{9416,2,0,-1},{9420,2,0,-1},{9421,2,1,-1},{9425,2,0,-1},{9429,2,0,-1}, +{9430,2,1,-1},{9433,2,0,-1},{9438,2,0,-1},{9439,2,1,-1},{9442,2,0,-1}, +{9446,2,0,-1},{9447,2,1,-1},{9451,2,0,-1},{9455,2,0,-1},{9456,2,1,-1}, +{9459,2,0,-1},{9464,2,0,-1},{9465,2,1,-1},{9398,2,0,-1},{9468,2,0,-1}, +{9472,2,0,-1},{9473,2,1,-1},{9477,2,0,-1},{9411,2,1,-1},{9481,2,0,-1}, +{9482,2,1,-1},{9485,2,0,-1},{9490,2,0,-1},{9491,2,1,-1},{9494,2,0,-1}, +{9498,2,0,-1},{9499,2,1,-1},{9503,2,0,-1},{9504,2,1,-1},{9507,2,0,-1}, +{9508,2,1,-1},{9511,2,0,-1},{9512,2,1,-1},{9516,2,0,-1},{9517,2,1,-1}, +{9520,2,0,-1},{9521,2,1,-1},{9524,2,0,-1},{9525,2,1,-1},{9529,2,0,-1}, +{9533,2,0,-1},{9534,2,1,-1},{9537,2,0,-1},{9538,2,1,-1},{9542,2,0,-1}, +{9543,2,1,-1},{9546,2,0,-1},{9547,2,1,-1},{9550,2,0,-1},{9551,2,1,-1}, +{9555,2,0,-1},{9556,2,1,-1},{9559,2,0,-1},{9560,2,1,-1},{9563,2,0,-1}, +{9564,2,1,-1},{9568,2,0,-1},{9569,2,1,-1},{9502,2,0,-1},{9573,2,1,-1}, +{9576,2,0,-1},{9577,2,1,-1},{9581,2,0,-1},{9582,2,1,-1},{9515,2,1,-1}, +{9585,2,0,-1},{9586,2,1,-1},{9589,2,0,-1},{9590,2,1,-1},{9594,2,0,-1}, +{9595,2,1,-1},{9598,2,0,-1},{9599,2,1,-1},{9602,2,0,-1},{9603,2,1,-1}, +{9607,2,0,-1},{9608,2,1,-1},{9611,2,0,-1},{9612,2,1,-1},{9615,2,0,-1}, +{9616,2,1,-1},{9620,2,0,-1},{9621,2,1,-1},{9624,2,0,-1},{9625,2,1,-1}, +{9628,2,0,-1},{9629,2,1,-1},{9633,2,0,-1},{9634,2,1,-1},{9637,2,0,-1}, +{9638,2,1,-1},{9641,2,0,-1},{9642,2,1,-1},{9646,2,0,-1},{9647,2,1,-1}, +{9650,2,0,-1},{9651,2,1,-1},{9654,2,0,-1},{9655,2,1,-1},{9659,2,0,-1}, +{9660,2,1,-1},{9663,2,0,-1},{9664,2,1,-1},{9667,2,0,-1},{9668,2,1,-1}, +{9672,2,0,-1},{9673,2,1,-1},{9606,2,0,-1},{9676,2,0,-1},{9677,2,1,-1}, +{9680,2,0,-1},{9681,2,1,-1},{9685,2,0,-1},{9686,2,1,-1},{9619,2,1,-1}, +{9689,2,0,-1},{9690,2,1,-1},{9693,2,0,-1},{9694,2,1,-1},{9698,2,0,-1}, +{9699,2,1,-1},{9702,2,0,-1},{9703,2,1,-1},{9706,2,0,-1},{9707,2,1,-1}, +{9711,2,0,-1},{9712,2,1,-1},{9715,2,0,-1},{9716,2,1,-1},{9719,2,0,-1}, +{9724,2,0,-1},{9725,2,1,-1},{9728,2,0,-1},{9729,2,1,-1},{9732,2,0,-1}, +{9733,2,1,-1},{9737,2,0,-1},{9738,2,1,-1},{9741,2,0,-1},{9742,2,1,-1}, +{9745,2,0,-1},{9746,2,1,-1},{9750,2,0,-1},{9751,2,1,-1},{9754,2,0,-1}, +{9755,2,1,-1},{9758,2,0,-1},{9759,2,1,-1},{9764,2,1,-1},{9767,2,0,-1}, +{9768,2,1,-1},{9771,2,0,-1},{9776,2,0,-1},{9777,2,1,-1},{9710,2,0,-1}, +{9780,2,0,-1},{9781,2,1,-1},{9784,2,0,-1},{9785,2,1,-1},{9789,2,0,-1}, +{9790,2,1,-1},{9723,2,1,-1},{9793,2,0,-1},{9794,2,1,-1},{9797,2,0,-1}, +{9798,2,1,-1},{9802,2,0,-1},{9803,2,1,-1},{9806,2,0,-1},{9807,2,1,-1}, +{9810,2,0,-1},{9811,2,1,-1},{9815,2,0,-1},{9816,2,1,-1},{9819,2,0,-1}, +{9820,2,1,-1},{9823,2,0,-1},{9824,2,1,-1},{9828,2,0,-1},{9829,2,1,-1}, +{9832,2,0,-1},{9833,2,1,-1},{9836,2,0,-1},{9837,2,1,-1},{9841,2,0,-1}, +{9842,2,1,-1},{9845,2,0,-1},{9846,2,1,-1},{9849,2,0,-1},{9850,2,1,-1}, +{9854,2,0,-1},{9855,2,1,-1},{9858,2,0,-1},{9859,2,1,-1},{9862,2,0,-1}, +{9863,2,1,-1},{9867,2,0,-1},{9868,2,1,-1},{9871,2,0,-1},{9872,2,1,-1}, +{9875,2,0,-1},{9876,2,1,-1},{9880,2,0,-1},{9881,2,1,-1},{9814,2,0,-1}, +{9884,2,0,-1},{9885,2,1,-1},{9888,2,0,-1},{9889,2,1,-1},{9893,2,0,-1}, +{9894,2,1,-1},{9827,2,1,-1},{9897,2,0,-1},{9898,2,1,-1},{9901,2,0,-1}, +{9902,2,1,-1},{9906,2,0,-1},{9907,2,1,-1},{9910,2,0,-1},{9911,2,1,-1}, +{9914,2,0,-1},{9915,2,1,-1},{9919,2,0,-1},{9920,2,1,-1},{9923,2,0,-1}, +{9924,2,1,-1},{9927,2,0,-1},{9928,2,1,-1},{9932,2,0,-1},{9933,2,1,-1}, +{9936,2,0,-1},{9937,2,1,-1},{9940,2,0,-1},{9941,2,1,-1},{9945,2,0,-1}, +{9946,2,1,-1},{9949,2,0,-1},{9950,2,1,-1},{9953,2,0,-1},{9954,2,1,-1}, +{9958,2,0,-1},{9959,2,1,-1},{9962,2,0,-1},{9963,2,1,-1},{9966,2,0,-1}, +{9967,2,1,-1},{9971,2,0,-1},{9972,2,1,-1},{9975,2,0,-1},{9976,2,1,-1}, +{9979,2,0,-1},{9980,2,1,-1},{9984,2,0,-1},{9985,2,1,-1},{9918,2,0,-1}, +{9988,2,0,-1},{9989,2,1,-1},{9992,2,0,-1},{9993,2,1,-1},{9997,2,0,-1}, +{9998,2,1,-1},{9931,2,1,-1},{10001,2,0,-1},{10002,2,1,-1},{10005,2,0,-1}, +{10006,2,1,-1},{10010,2,0,-1},{10011,2,1,-1},{10014,2,0,-1},{10015,2,1,-1}, +{10018,2,0,-1},{10019,2,1,-1},{10023,2,0,-1},{10024,2,1,-1},{10027,2,0,-1}, +{10028,2,1,-1},{10031,2,0,-1},{10032,2,1,-1},{10036,2,0,-1},{10037,2,1,-1}, +{10040,2,0,-1},{10041,2,1,-1},{10044,2,0,-1},{10045,2,1,-1},{10049,2,0,-1}, +{10050,2,1,-1},{10053,2,0,-1},{10054,2,1,-1},{10057,2,0,-1},{10058,2,1,-1}, +{10062,2,0,-1},{10063,2,1,-1},{10066,2,0,-1},{10067,2,1,-1},{10070,2,0,-1}, +{10071,2,1,-1},{10075,2,0,-1},{10076,2,1,-1},{10079,2,0,-1},{10080,2,1,-1}, +{10083,2,0,-1},{10084,2,1,-1},{10088,2,0,-1},{10089,2,1,-1},{10022,2,0,-1}, +{10092,2,0,-1},{10093,2,1,-1},{10096,2,0,-1},{10097,2,1,-1},{10101,2,0,-1}, +{10102,2,1,-1},{10035,2,1,-1},{10105,2,0,-1},{10106,2,1,-1},{10109,2,0,-1}, +{10110,2,1,-1},{10114,2,0,-1},{10115,2,1,-1},{10118,2,0,-1},{10119,2,1,-1}, +{10122,2,0,-1},{10123,2,1,-1},{10127,2,0,-1},{10128,2,1,-1},{10131,2,0,-1}, +{10132,2,1,-1},{10135,2,0,-1},{10136,2,1,-1},{10140,2,0,-1},{10141,2,1,-1}, +{10144,2,0,-1},{10145,2,1,-1},{10148,2,0,-1},{10149,2,1,-1},{10153,2,0,-1}, +{10154,2,1,-1},{10157,2,0,-1},{10158,2,1,-1},{10161,2,0,-1},{10162,2,1,-1}, +{10166,2,0,-1},{10167,2,1,-1},{10170,2,0,-1},{10171,2,1,-1},{10174,2,0,-1}, +{10175,2,1,-1},{10179,2,0,-1},{10180,2,1,-1},{10183,2,0,-1},{10184,2,1,-1}, +{10187,2,0,-1},{10188,2,1,-1},{10192,2,0,-1},{10193,2,1,-1},{10126,2,0,-1}, +{10196,2,0,-1},{10197,2,1,-1},{10200,2,0,-1},{10201,2,1,-1},{10205,2,0,-1}, +{10206,2,1,-1},{10139,2,1,-1},{10209,2,0,-1},{10210,2,1,-1},{10213,2,0,-1}, +{10214,2,1,-1},{10218,2,0,-1},{10219,2,1,-1},{10222,2,0,-1},{10223,2,1,-1}, +{10226,2,0,-1},{10227,2,1,-1},{10231,2,0,-1},{10232,2,1,-1},{10235,2,0,-1}, +{10236,2,1,-1},{10239,2,0,-1},{10240,2,1,-1},{10244,2,0,-1},{10245,2,1,-1}, +{10248,2,0,-1},{10249,2,1,-1},{10252,2,0,-1},{10253,2,1,-1},{10257,2,0,-1}, +{10258,2,1,-1},{10261,2,0,-1},{10262,2,1,-1},{10265,2,0,-1},{10266,2,1,-1}, +{10270,2,0,-1},{10271,2,1,-1},{10274,2,0,-1},{10275,2,1,-1},{10278,2,0,-1}, +{10279,2,1,-1},{10283,2,0,-1},{10284,2,1,-1},{10287,2,0,-1},{10288,2,1,-1}, +{10291,2,0,-1},{10292,2,1,-1},{10296,2,0,-1},{10297,2,1,-1},{10230,2,0,-1}, +{10300,2,0,-1},{10301,2,1,-1},{10304,2,0,-1},{10305,2,1,-1},{10309,2,0,-1}, +{10310,2,1,-1},{10243,2,1,-1},{10313,2,0,-1},{10314,2,1,-1},{10317,2,0,-1}, +{10318,2,1,-1},{10322,2,0,-1},{10323,2,1,-1},{10326,2,0,-1},{10327,2,1,-1}, +{10330,2,0,-1},{10331,2,1,-1},{10335,2,0,-1},{10336,2,1,-1},{10339,2,0,-1}, +{10340,2,1,-1},{10343,2,0,-1},{10344,2,1,-1},{10348,2,0,-1},{10349,2,1,-1}, +{10352,2,0,-1},{10353,2,1,-1},{10356,2,0,-1},{10357,2,1,-1},{10361,2,0,-1}, +{10362,2,1,-1},{10365,2,0,-1},{10366,2,1,-1},{10369,2,0,-1},{10370,2,1,-1}, +{10374,2,0,-1},{10375,2,1,-1},{10378,2,0,-1},{10379,2,1,-1},{10382,2,0,-1}, +{10383,2,1,-1},{10387,2,0,-1},{10388,2,1,-1},{10391,2,0,-1},{10392,2,1,-1}, +{10395,2,0,-1},{10396,2,1,-1},{10400,2,0,-1},{10401,2,1,-1},{10334,2,0,-1}, +{10404,2,0,-1},{10405,2,1,-1},{10408,2,0,-1},{10409,2,1,-1},{10413,2,0,-1}, +{10414,2,1,-1},{10347,2,1,-1},{10417,2,0,-1},{10418,2,1,-1},{10421,2,0,-1}, +{10422,2,1,-1},{10426,2,0,-1},{10427,2,1,-1},{10430,2,0,-1},{10431,2,1,-1}, +{10434,2,0,-1},{10435,2,1,-1},{10439,2,0,-1},{10440,2,1,-1},{10443,2,0,-1}, +{10444,2,1,-1},{10447,2,0,-1},{10448,2,1,-1},{10452,2,0,-1},{10453,2,1,-1}, +{10456,2,0,-1},{10457,2,1,-1},{10460,2,0,-1},{10461,2,1,-1},{10465,2,0,-1}, +{10466,2,1,-1},{10469,2,0,-1},{10470,2,1,-1},{10473,2,0,-1},{10474,2,1,-1}, +{10478,2,0,-1},{10479,2,1,-1},{10482,2,0,-1},{10483,2,1,-1},{10486,2,0,-1}, +{10487,2,1,-1},{10491,2,0,-1},{10492,2,1,-1},{10495,2,0,-1},{10496,2,1,-1}, +{10499,2,0,-1},{10500,2,1,-1},{10504,2,0,-1},{10505,2,1,-1},{10438,2,0,-1}, +{10508,2,0,-1},{10509,2,1,-1},{10512,2,0,-1},{10513,2,1,-1},{10517,2,0,-1}, +{10518,2,1,-1},{10451,2,1,-1},{10521,2,0,-1},{10522,2,1,-1},{10525,2,0,-1}, +{10526,2,1,-1},{10530,2,0,-1},{10531,2,1,-1},{10534,2,0,-1},{10535,2,1,-1}, +{10538,2,0,-1},{10539,2,1,-1},{10543,2,0,-1},{10544,2,1,-1},{10547,2,0,-1}, +{10548,2,1,-1},{10551,2,0,-1},{10552,2,1,-1},{10556,2,0,-1},{10557,2,1,-1}, +{10560,2,0,-1},{10561,2,1,-1},{10564,2,0,-1},{10565,2,1,-1},{10569,2,0,-1}, +{10570,2,1,-1},{10573,2,0,-1},{10574,2,1,-1},{10577,2,0,-1},{10578,2,1,-1}, +{10582,2,0,-1},{10583,2,1,-1},{10586,2,0,-1},{10587,2,1,-1},{10590,2,0,-1}, +{10591,2,1,-1},{10595,2,0,-1},{10596,2,1,-1},{10599,2,0,-1},{10600,2,1,-1}, +{10603,2,0,-1},{10604,2,1,-1},{10608,2,0,-1},{10609,2,1,-1},{10542,2,0,-1}, +{10612,2,0,-1},{10613,2,1,-1},{10616,2,0,-1},{10617,2,1,-1},{10621,2,0,-1}, +{10622,2,1,-1},{10555,2,1,-1},{10625,2,0,-1},{10626,2,1,-1},{10629,2,0,-1}, +{10630,2,1,-1},{10634,2,0,-1},{10635,2,1,-1},{10638,2,0,-1},{10639,2,1,-1}, +{10642,2,0,-1},{10643,2,1,-1},{10647,2,0,-1},{10648,2,1,-1},{10651,2,0,-1}, +{10652,2,1,-1},{10655,2,0,-1},{10656,2,1,-1},{10660,2,0,-1},{10661,2,1,-1}, +{10664,2,0,-1},{10665,2,1,-1},{10668,2,0,-1},{10669,2,1,-1},{10673,2,0,-1}, +{10674,2,1,-1},{10677,2,0,-1},{10678,2,1,-1},{10681,2,0,-1},{10682,2,1,-1}, +{10686,2,0,-1},{10687,2,1,-1},{10690,2,0,-1},{10691,2,1,-1},{10694,2,0,-1}, +{10695,2,1,-1},{10699,2,0,-1},{10700,2,1,-1},{10703,2,0,-1},{10704,2,1,-1}, +{10707,2,0,-1},{10708,2,1,-1},{10712,2,0,-1},{10713,2,1,-1},{10646,2,0,-1}, +{10716,2,0,-1},{10717,2,1,-1},{10720,2,0,-1},{10721,2,1,-1},{10725,2,0,-1}, +{10726,2,1,-1},{10659,2,1,-1},{10729,2,0,-1},{10730,2,1,-1},{10733,2,0,-1}, +{10734,2,1,-1},{10738,2,0,-1},{10739,2,1,-1},{10742,2,0,-1},{10743,2,1,-1}, +{10746,2,0,-1},{10747,2,1,-1},{10751,2,0,-1},{10752,2,1,-1},{10755,2,0,-1}, +{10756,2,1,-1},{10759,2,0,-1},{10760,2,1,-1},{10764,2,0,-1},{10765,2,1,-1}, +{10768,2,0,-1},{10769,2,1,-1},{10772,2,0,-1},{10773,2,1,-1},{10777,2,0,-1}, +{10778,2,1,-1},{10781,2,0,-1},{10782,2,1,-1},{10785,2,0,-1},{10786,2,1,-1}, +{10790,2,0,-1},{10791,2,1,-1},{10794,2,0,-1},{10795,2,1,-1},{10798,2,0,-1}, +{10799,2,1,-1},{10803,2,0,-1},{10804,2,1,-1},{10807,2,0,-1},{10808,2,1,-1}, +{10811,2,0,-1},{10812,2,1,-1},{10816,2,0,-1},{10817,2,1,-1},{10750,2,0,-1}, +{10820,2,0,-1},{10821,2,1,-1},{10824,2,0,-1},{10825,2,1,-1},{10829,2,0,-1}, +{10830,2,1,-1},{10763,2,1,-1},{10833,2,0,-1},{10834,2,1,-1},{10837,2,0,-1}, +{10838,2,1,-1},{10842,2,0,-1},{10843,2,1,-1},{10846,2,0,-1},{10847,2,1,-1}, +{10850,2,0,-1},{10851,2,1,-1},{10855,2,0,-1},{10856,2,1,-1},{10859,2,0,-1}, +{10860,2,1,-1},{10863,2,0,-1},{10864,2,1,-1},{10868,2,0,-1},{10869,2,1,-1}, +{10872,2,0,-1},{10873,2,1,-1},{10876,2,0,-1},{10877,2,1,-1},{10881,2,0,-1}, +{10882,2,1,-1},{10885,2,0,-1},{10886,2,1,-1},{10889,2,0,-1},{10890,2,1,-1}, +{10894,2,0,-1},{10895,2,1,-1},{10898,2,0,-1},{10899,2,1,-1},{10902,2,0,-1}, +{10903,2,1,-1},{10907,2,0,-1},{10908,2,1,-1},{10911,2,0,-1},{10912,2,1,-1}, +{10915,2,0,-1},{10916,2,1,-1},{10920,2,0,-1},{10921,2,1,-1},{10854,2,0,-1}, +{10924,2,0,-1},{10925,2,1,-1},{10928,2,0,-1},{10929,2,1,-1},{10933,2,0,-1}, +{10934,2,1,-1},{10867,2,1,-1},{10937,2,0,-1},{10938,2,1,-1},{10941,2,0,-1}, +{10942,2,1,-1},{10946,2,0,-1},{10947,2,1,-1},{10950,2,0,-1},{10951,2,1,-1}, +{10954,2,0,-1},{10955,2,1,-1},{10959,2,0,-1},{10960,2,1,-1},{10963,2,0,-1}, +{10964,2,1,-1},{10967,2,0,-1},{10968,2,1,-1},{10972,2,0,-1},{10973,2,1,-1}, +{10976,2,0,-1},{10977,2,1,-1},{10980,2,0,-1},{10981,2,1,-1},{10985,2,0,-1}, +{10986,2,1,-1},{10989,2,0,-1},{10990,2,1,-1},{10993,2,0,-1},{10994,2,1,-1}, +{10998,2,0,-1},{10999,2,1,-1},{11002,2,0,-1},{11003,2,1,-1},{11006,2,0,-1}, +{11007,2,1,-1},{11011,2,0,-1},{11012,2,1,-1},{11015,2,0,-1},{11016,2,1,-1}, +{11019,2,0,-1},{11020,2,1,-1},{11024,2,0,-1},{11025,2,1,-1},{10958,2,0,-1}, +{11028,2,0,-1},{11029,2,1,-1},{11032,2,0,-1},{11033,2,1,-1},{11037,2,0,-1}, +{11038,2,1,-1},{10971,2,1,-1},{11041,2,0,-1},{11042,2,1,-1},{11045,2,0,-1}, +{11046,2,1,-1},{11050,2,0,-1},{11051,2,1,-1},{11054,2,0,-1},{11055,2,1,-1}, +{11058,2,0,-1},{11059,2,1,-1},{11063,2,0,-1},{11064,2,1,-1},{11067,2,0,-1}, +{11068,2,1,-1},{11071,2,0,-1},{11072,2,1,-1},{11076,2,0,-1},{11077,2,1,-1}, +{11080,2,0,-1},{11081,2,1,-1},{11084,2,0,-1},{11085,2,1,-1},{11089,2,0,-1}, +{11090,2,1,-1},{11093,2,0,-1},{11094,2,1,-1},{11097,2,0,-1},{11098,2,1,-1}, +{11102,2,0,-1},{11103,2,1,-1},{11106,2,0,-1},{11107,2,1,-1},{11110,2,0,-1}, +{11111,2,1,-1},{11115,2,0,-1},{11116,2,1,-1},{11119,2,0,-1},{11120,2,1,-1}, +{11123,2,0,-1},{11124,2,1,-1},{11128,2,0,-1},{11129,2,1,-1},{11062,2,0,-1}, +{11132,2,0,-1},{11133,2,1,-1},{11136,2,0,-1},{11137,2,1,-1},{11141,2,0,-1}, +{11142,2,1,-1},{11075,2,1,-1},{11145,2,0,-1},{11146,2,1,-1},{11149,2,0,-1}, +{11150,2,1,-1},{11154,2,0,-1},{11155,2,1,-1},{11158,2,0,-1},{11159,2,1,-1}, +{11162,2,0,-1},{11163,2,1,-1},{11167,2,0,-1},{11168,2,1,-1},{11171,2,0,-1}, +{11172,2,1,-1},{11175,2,0,-1},{11176,2,1,-1},{11180,2,0,-1},{11181,2,1,-1}, +{11184,2,0,-1},{11185,2,1,-1},{11188,2,0,-1},{11189,2,1,-1},{11193,2,0,-1}, +{11194,2,1,-1},{11197,2,0,-1},{11198,2,1,-1},{11201,2,0,-1},{11202,2,1,-1}, +{11206,2,0,-1},{11207,2,1,-1},{11210,2,0,-1},{11211,2,1,-1},{11214,2,0,-1}, +{11215,2,1,-1},{11219,2,0,-1},{11220,2,1,-1},{11223,2,0,-1},{11224,2,1,-1}, +{11227,2,0,-1},{11228,2,1,-1},{11232,2,0,-1},{11233,2,1,-1},{11166,2,0,-1}, +{11236,2,0,-1},{11237,2,1,-1},{11240,2,0,-1},{11241,2,1,-1},{11245,2,0,-1}, +{11246,2,1,-1},{11179,2,1,-1},{11249,2,0,-1},{11250,2,1,-1},{11253,2,0,-1}, +{11254,2,1,-1},{11258,2,0,-1},{11259,2,1,-1},{11262,2,0,-1},{11263,2,1,-1}, +{11266,2,0,-1},{11267,2,1,-1},{11271,2,0,-1},{11272,2,1,-1},{11275,2,0,-1}, +{11276,2,1,-1},{11279,2,0,-1},{11280,2,1,-1},{11284,2,0,-1},{11285,2,1,-1}, +{11288,2,0,-1},{11289,2,1,-1},{11292,2,0,-1},{11293,2,1,-1},{11297,2,0,-1}, +{11298,2,1,-1},{11301,2,0,-1},{11302,2,1,-1},{11305,2,0,-1},{11306,2,1,-1}, +{11310,2,0,-1},{11311,2,1,-1},{11314,2,0,-1},{11315,2,1,-1},{11318,2,0,-1}, +{11319,2,1,-1},{11323,2,0,-1},{11324,2,1,-1},{11327,2,0,-1},{11328,2,1,-1}, +{11331,2,0,-1},{11332,2,1,-1},{11336,2,0,-1},{11337,2,1,-1},{11270,2,0,-1}, +{11340,2,0,-1},{11341,2,1,-1},{11344,2,0,-1},{11345,2,1,-1},{11349,2,0,-1}, +{11350,2,1,-1},{11283,2,1,-1},{11353,2,0,-1},{11354,2,1,-1},{11357,2,0,-1}, +{11358,2,1,-1},{11362,2,0,-1},{11363,2,1,-1},{11366,2,0,-1},{11367,2,1,-1}, +{11370,2,0,-1},{11371,2,1,-1},{11375,2,0,-1},{11376,2,1,-1},{11379,2,0,-1}, +{11380,2,1,-1},{11383,2,0,-1},{11384,2,1,-1},{11388,2,0,-1},{11389,2,1,-1}, +{11392,2,0,-1},{11393,2,1,-1},{11396,2,0,-1},{11397,2,1,-1},{11401,2,0,-1}, +{11402,2,1,-1},{11405,2,0,-1},{11406,2,1,-1},{11409,2,0,-1},{11410,2,1,-1}, +{11414,2,0,-1},{11415,2,1,-1},{11418,2,0,-1},{11419,2,1,-1},{11422,2,0,-1}, +{11423,2,1,-1},{11427,2,0,-1},{11428,2,1,-1},{11431,2,0,-1},{11432,2,1,-1}, +{11435,2,0,-1},{11436,2,1,-1},{11440,2,0,-1},{11441,2,1,-1},{11374,2,0,-1}, +{11444,2,0,-1},{11445,2,1,-1},{11448,2,0,-1},{11449,2,1,-1},{11453,2,0,-1}, +{11454,2,1,-1},{11387,2,1,-1},{11457,2,0,-1},{11458,2,1,-1},{11461,2,0,-1}, +{11462,2,1,-1},{11466,2,0,-1},{11467,2,1,-1},{11470,2,0,-1},{11471,2,1,-1}, +{11474,2,0,-1},{11475,2,1,-1},{11479,2,0,-1},{11480,2,1,-1},{11483,2,0,-1}, +{11484,2,1,-1},{11487,2,0,-1},{11488,2,1,-1},{11492,2,0,-1},{11493,2,1,-1}, +{11496,2,0,-1},{11497,2,1,-1},{11500,2,0,-1},{11501,2,1,-1},{11505,2,0,-1}, +{11506,2,1,-1},{11509,2,0,-1},{11510,2,1,-1},{11513,2,0,-1},{11514,2,1,-1}, +{11518,2,0,-1},{11519,2,1,-1},{11522,2,0,-1},{11523,2,1,-1},{11526,2,0,-1}, +{11527,2,1,-1},{11531,2,0,-1},{11532,2,1,-1},{11535,2,0,-1},{11536,2,1,-1}, +{11539,2,0,-1},{11540,2,1,-1},{11544,2,0,-1},{11545,2,1,-1},{11478,2,0,-1}, +{11548,2,0,-1},{11549,2,1,-1},{11552,2,0,-1},{11553,2,1,-1},{11557,2,0,-1}, +{11558,2,1,-1},{11491,2,1,-1},{11561,2,0,-1},{11562,2,1,-1},{11565,2,0,-1}, +{11566,2,1,-1},{11570,2,0,-1},{11571,2,1,-1},{11574,2,0,-1},{11575,2,1,-1}, +{11578,2,0,-1},{11579,2,1,-1},{11583,2,0,-1},{11584,2,1,-1},{11587,2,0,-1}, +{11588,2,1,-1},{11591,2,0,-1},{11596,2,0,-1},{11597,2,1,-1},{11600,2,0,-1}, +{11601,2,1,-1},{11604,2,0,-1},{11605,2,1,-1},{11609,2,0,-1},{11610,2,1,-1}, +{11613,2,0,-1},{11614,2,1,-1},{11617,2,0,-1},{11618,2,1,-1},{11622,2,0,-1}, +{11623,2,1,-1},{11626,2,0,-1},{11627,2,1,-1},{11630,2,0,-1},{11631,2,1,-1}, +{11635,2,0,-1},{11636,2,1,-1},{11639,2,0,-1},{11640,2,1,-1},{11648,2,0,-1}, +{11649,2,1,-1},{11582,2,0,-1},{11652,2,0,-1},{11653,2,1,-1},{11656,2,0,-1}, +{11657,2,1,-1},{11661,2,0,-1},{11662,2,1,-1},{11665,2,0,-1},{11666,2,1,-1}, +{11669,2,0,-1},{11670,2,1,-1},{11674,2,0,-1},{11675,2,1,-1},{11678,2,0,-1}, +{11679,2,1,-1},{11682,2,0,-1},{11683,2,1,-1},{11687,2,0,-1},{11688,2,1,-1}, +{11691,2,0,-1},{11692,2,1,-1},{11700,2,0,-1},{11704,2,0,-1},{11708,2,0,-1}, +{11713,2,0,-1},{11717,2,0,-1},{11721,2,0,-1},{11726,2,0,-1}}; + +/* Frame number data sampled from measurement.c:lchan_meas_check_compute() + * Call was made between to phones on half rate channels TS3, SS0 and SS1 */ +struct fn_sample test_fn_tch_h_ts_3_ss0_ss1[] = { +{10001,3,0,-1},{10002,3,1,-1},{10005,3,0,-1},{10006,3,1,-1},{10010,3,0,-1}, +{10011,3,1,-1},{10014,3,0,-1},{10015,3,1,-1},{10018,3,0,-1},{10019,3,1,-1}, +{10023,3,0,-1},{10024,3,1,-1},{10027,3,0,-1},{10028,3,1,-1},{10031,3,0,-1}, +{10032,3,1,-1},{10036,3,0,-1},{10037,3,1,-1},{10040,3,0,-1},{10041,3,1,-1}, +{10044,3,0,-1},{10045,3,1,-1},{10049,3,0,-1},{10050,3,1,-1},{10053,3,0,-1}, +{10054,3,1,-1},{10057,3,0,-1},{10058,3,1,-1},{10062,3,0,-1},{10063,3,1,-1}, +{10066,3,0,-1},{10067,3,1,-1},{10070,3,0,-1},{10071,3,1,-1},{10075,3,0,-1}, +{10076,3,1,-1},{10079,3,0,-1},{10080,3,1,-1},{10083,3,0,-1},{10084,3,1,-1}, +{10088,3,0,-1},{10089,3,1,-1},{10022,3,0,-1},{10092,3,0,-1},{10093,3,1,-1}, +{10096,3,0,-1},{10097,3,1,-1},{10101,3,0,-1},{10102,3,1,-1},{10035,3,1,-1}, +{10105,3,0,-1},{10106,3,1,-1},{10109,3,0,-1},{10110,3,1,-1},{10114,3,0,-1}, +{10115,3,1,-1},{10118,3,0,-1},{10119,3,1,-1},{10122,3,0,-1},{10123,3,1,-1}, +{10127,3,0,-1},{10128,3,1,-1},{10131,3,0,-1},{10132,3,1,-1},{10135,3,0,-1}, +{10136,3,1,-1},{10140,3,0,-1},{10141,3,1,-1},{10144,3,0,-1},{10145,3,1,-1}, +{10148,3,0,-1},{10149,3,1,-1},{10153,3,0,-1},{10154,3,1,-1},{10157,3,0,-1}, +{10158,3,1,-1},{10161,3,0,-1},{10162,3,1,-1},{10166,3,0,-1},{10167,3,1,-1}, +{10170,3,0,-1},{10171,3,1,-1},{10174,3,0,-1},{10175,3,1,-1},{10179,3,0,-1}, +{10180,3,1,-1},{10183,3,0,-1},{10184,3,1,-1},{10187,3,0,-1},{10188,3,1,-1}, +{10192,3,0,-1},{10193,3,1,-1},{10126,3,0,-1},{10196,3,0,-1},{10197,3,1,-1}, +{10200,3,0,-1},{10201,3,1,-1},{10205,3,0,-1},{10206,3,1,-1},{10139,3,1,-1}, +{10209,3,0,-1},{10210,3,1,-1},{10213,3,0,-1},{10214,3,1,-1},{10218,3,0,-1}, +{10219,3,1,-1},{10222,3,0,-1},{10223,3,1,-1},{10226,3,0,-1},{10227,3,1,-1}, +{10231,3,0,-1},{10232,3,1,-1},{10235,3,0,-1},{10236,3,1,-1},{10239,3,0,-1}, +{10240,3,1,-1},{10244,3,0,-1},{10245,3,1,-1},{10248,3,0,-1},{10249,3,1,-1}, +{10252,3,0,-1},{10253,3,1,-1},{10257,3,0,-1},{10258,3,1,-1},{10261,3,0,-1}, +{10262,3,1,-1},{10265,3,0,-1},{10266,3,1,-1},{10270,3,0,-1},{10271,3,1,-1}, +{10274,3,0,-1},{10275,3,1,-1},{10278,3,0,-1},{10279,3,1,-1},{10283,3,0,-1}, +{10284,3,1,-1},{10287,3,0,-1},{10288,3,1,-1},{10291,3,0,-1},{10292,3,1,-1}, +{10296,3,0,-1},{10297,3,1,-1},{10230,3,0,-1},{10300,3,0,-1},{10301,3,1,-1}, +{10304,3,0,-1},{10305,3,1,-1},{10309,3,0,-1},{10310,3,1,-1},{10243,3,1,-1}, +{10313,3,0,-1},{10314,3,1,-1},{10317,3,0,-1},{10318,3,1,-1},{10322,3,0,-1}, +{10323,3,1,-1},{10326,3,0,-1},{10327,3,1,-1},{10330,3,0,-1},{10331,3,1,-1}, +{10335,3,0,-1},{10336,3,1,-1},{10339,3,0,-1},{10340,3,1,-1},{10343,3,0,-1}, +{10344,3,1,-1},{10348,3,0,-1},{10349,3,1,-1},{10352,3,0,-1},{10353,3,1,-1}, +{10356,3,0,-1},{10357,3,1,-1},{10361,3,0,-1},{10362,3,1,-1},{10365,3,0,-1}, +{10366,3,1,-1},{10369,3,0,-1},{10370,3,1,-1},{10374,3,0,-1},{10375,3,1,-1}, +{10378,3,0,-1},{10379,3,1,-1},{10382,3,0,-1},{10383,3,1,-1},{10387,3,0,-1}, +{10388,3,1,-1},{10391,3,0,-1},{10392,3,1,-1},{10395,3,0,-1},{10396,3,1,-1}, +{10400,3,0,-1},{10401,3,1,-1},{10334,3,0,-1},{10404,3,0,-1},{10405,3,1,-1}, +{10408,3,0,-1},{10409,3,1,-1},{10413,3,0,-1},{10414,3,1,-1},{10347,3,1,-1}, +{10417,3,0,-1},{10418,3,1,-1},{10421,3,0,-1},{10422,3,1,-1},{10426,3,0,-1}, +{10427,3,1,-1},{10430,3,0,-1},{10431,3,1,-1},{10434,3,0,-1},{10435,3,1,-1}, +{10439,3,0,-1},{10440,3,1,-1},{10443,3,0,-1},{10444,3,1,-1},{10447,3,0,-1}, +{10448,3,1,-1},{10452,3,0,-1},{10453,3,1,-1},{10456,3,0,-1},{10457,3,1,-1}, +{10460,3,0,-1},{10461,3,1,-1},{10465,3,0,-1},{10466,3,1,-1},{10469,3,0,-1}, +{10470,3,1,-1},{10473,3,0,-1},{10474,3,1,-1},{10478,3,0,-1},{10479,3,1,-1}, +{10482,3,0,-1},{10483,3,1,-1},{10486,3,0,-1},{10487,3,1,-1},{10491,3,0,-1}, +{10492,3,1,-1},{10495,3,0,-1},{10496,3,1,-1},{10499,3,0,-1},{10500,3,1,-1}, +{10504,3,0,-1},{10505,3,1,-1},{10438,3,0,-1},{10508,3,0,-1},{10509,3,1,-1}, +{10512,3,0,-1},{10513,3,1,-1},{10517,3,0,-1},{10518,3,1,-1},{10451,3,1,-1}, +{10521,3,0,-1},{10522,3,1,-1},{10525,3,0,-1},{10526,3,1,-1},{10530,3,0,-1}, +{10531,3,1,-1},{10534,3,0,-1},{10535,3,1,-1},{10538,3,0,-1},{10539,3,1,-1}, +{10543,3,0,-1},{10544,3,1,-1},{10547,3,0,-1},{10548,3,1,-1},{10551,3,0,-1}, +{10552,3,1,-1},{10556,3,0,-1},{10557,3,1,-1},{10560,3,0,-1},{10561,3,1,-1}, +{10564,3,0,-1},{10565,3,1,-1},{10569,3,0,-1},{10570,3,1,-1},{10573,3,0,-1}, +{10574,3,1,-1},{10577,3,0,-1},{10578,3,1,-1},{10582,3,0,-1},{10583,3,1,-1}, +{10586,3,0,-1},{10587,3,1,-1},{10590,3,0,-1},{10591,3,1,-1},{10595,3,0,-1}, +{10596,3,1,-1},{10599,3,0,-1},{10600,3,1,-1},{10603,3,0,-1},{10604,3,1,-1}, +{10608,3,0,-1},{10609,3,1,-1},{10542,3,0,-1},{10612,3,0,-1},{10613,3,1,-1}, +{10616,3,0,-1},{10617,3,1,-1},{10621,3,0,-1},{10622,3,1,-1},{10555,3,1,-1}, +{10625,3,0,-1},{10626,3,1,-1},{10629,3,0,-1},{10630,3,1,-1},{10634,3,0,-1}, +{10635,3,1,-1},{10638,3,0,-1},{10639,3,1,-1},{10642,3,0,-1},{10643,3,1,-1}, +{10647,3,0,-1},{10648,3,1,-1},{10651,3,0,-1},{10652,3,1,-1},{10655,3,0,-1}, +{10656,3,1,-1},{10660,3,0,-1},{10661,3,1,-1},{10664,3,0,-1},{10665,3,1,-1}, +{10668,3,0,-1},{10669,3,1,-1},{10673,3,0,-1},{10674,3,1,-1},{10677,3,0,-1}, +{10678,3,1,-1},{10681,3,0,-1},{10682,3,1,-1},{10686,3,0,-1},{10687,3,1,-1}, +{10690,3,0,-1},{10691,3,1,-1},{10694,3,0,-1},{10695,3,1,-1},{10699,3,0,-1}, +{10700,3,1,-1},{10703,3,0,-1},{10704,3,1,-1},{10707,3,0,-1},{10708,3,1,-1}, +{10712,3,0,-1},{10713,3,1,-1},{10646,3,0,-1},{10716,3,0,-1},{10717,3,1,-1}, +{10720,3,0,-1},{10721,3,1,-1},{10725,3,0,-1},{10726,3,1,-1},{10659,3,1,-1}, +{10729,3,0,-1},{10730,3,1,-1},{10733,3,0,-1},{10734,3,1,-1},{10738,3,0,-1}, +{10739,3,1,-1},{10742,3,0,-1},{10743,3,1,-1},{10746,3,0,-1},{10747,3,1,-1}, +{10751,3,0,-1},{10752,3,1,-1},{10755,3,0,-1},{10756,3,1,-1},{10759,3,0,-1}, +{10760,3,1,-1},{10764,3,0,-1},{10765,3,1,-1},{10768,3,0,-1},{10769,3,1,-1}, +{10772,3,0,-1},{10773,3,1,-1},{10777,3,0,-1},{10778,3,1,-1},{10781,3,0,-1}, +{10782,3,1,-1},{10785,3,0,-1},{10786,3,1,-1},{10790,3,0,-1},{10791,3,1,-1}, +{10794,3,0,-1},{10795,3,1,-1},{10798,3,0,-1},{10799,3,1,-1},{10803,3,0,-1}, +{10804,3,1,-1},{10807,3,0,-1},{10808,3,1,-1},{10811,3,0,-1},{10812,3,1,-1}, +{10816,3,0,-1},{10817,3,1,-1},{10750,3,0,-1},{10820,3,0,-1},{10821,3,1,-1}, +{10824,3,0,-1},{10825,3,1,-1},{10829,3,0,-1},{10830,3,1,-1},{10763,3,1,-1}, +{10833,3,0,-1},{10834,3,1,-1},{10837,3,0,-1},{10838,3,1,-1},{10842,3,0,-1}, +{10843,3,1,-1},{10846,3,0,-1},{10847,3,1,-1},{10850,3,0,-1},{10851,3,1,-1}, +{10855,3,0,-1},{10856,3,1,-1},{10859,3,0,-1},{10860,3,1,-1},{10863,3,0,-1}, +{10864,3,1,-1},{10868,3,0,-1},{10869,3,1,-1},{10872,3,0,-1},{10873,3,1,-1}, +{10876,3,0,-1},{10877,3,1,-1},{10881,3,0,-1},{10882,3,1,-1},{10885,3,0,-1}, +{10886,3,1,-1},{10889,3,0,-1},{10890,3,1,-1},{10894,3,0,-1},{10895,3,1,-1}, +{10898,3,0,-1},{10899,3,1,-1},{10902,3,0,-1},{10903,3,1,-1},{10907,3,0,-1}, +{10908,3,1,-1},{10911,3,0,-1},{10912,3,1,-1},{10915,3,0,-1},{10916,3,1,-1}, +{10920,3,0,-1},{10921,3,1,-1},{10854,3,0,-1},{10924,3,0,-1},{10925,3,1,-1}, +{10928,3,0,-1},{10929,3,1,-1},{10933,3,0,-1},{10934,3,1,-1},{10867,3,1,-1}, +{10937,3,0,-1},{10938,3,1,-1},{10941,3,0,-1},{10942,3,1,-1},{10946,3,0,-1}, +{10947,3,1,-1},{10950,3,0,-1},{10951,3,1,-1},{10954,3,0,-1},{10955,3,1,-1}, +{10959,3,0,-1},{10960,3,1,-1},{10963,3,0,-1},{10964,3,1,-1},{10967,3,0,-1}, +{10968,3,1,-1},{10972,3,0,-1},{10973,3,1,-1},{10976,3,0,-1},{10977,3,1,-1}, +{10980,3,0,-1},{10981,3,1,-1},{10985,3,0,-1},{10986,3,1,-1},{10989,3,0,-1}, +{10990,3,1,-1},{10993,3,0,-1},{10994,3,1,-1},{10998,3,0,-1},{10999,3,1,-1}, +{11002,3,0,-1},{11003,3,1,-1},{11006,3,0,-1},{11007,3,1,-1},{11011,3,0,-1}, +{11012,3,1,-1},{11015,3,0,-1},{11016,3,1,-1},{11019,3,0,-1},{11020,3,1,-1}, +{11024,3,0,-1},{11025,3,1,-1},{10958,3,0,-1},{11028,3,0,-1},{11029,3,1,-1}, +{11032,3,0,-1},{11033,3,1,-1},{11037,3,0,-1},{11038,3,1,-1},{10971,3,1,-1}, +{11041,3,0,-1},{11042,3,1,-1},{11045,3,0,-1},{11046,3,1,-1},{11050,3,0,-1}, +{11051,3,1,-1},{11054,3,0,-1},{11055,3,1,-1},{11058,3,0,-1},{11059,3,1,-1}, +{11063,3,0,-1},{11064,3,1,-1},{11067,3,0,-1},{11068,3,1,-1},{11071,3,0,-1}, +{11072,3,1,-1},{11076,3,0,-1},{11077,3,1,-1},{11080,3,0,-1},{11081,3,1,-1}, +{11084,3,0,-1},{11085,3,1,-1},{11089,3,0,-1},{11090,3,1,-1},{11093,3,0,-1}, +{11094,3,1,-1},{11097,3,0,-1},{11098,3,1,-1},{11102,3,0,-1},{11103,3,1,-1}, +{11106,3,0,-1},{11107,3,1,-1},{11110,3,0,-1},{11111,3,1,-1},{11115,3,0,-1}, +{11116,3,1,-1},{11119,3,0,-1},{11120,3,1,-1},{11123,3,0,-1},{11124,3,1,-1}, +{11128,3,0,-1},{11129,3,1,-1},{11062,3,0,-1},{11132,3,0,-1},{11133,3,1,-1}, +{11136,3,0,-1},{11137,3,1,-1},{11141,3,0,-1},{11142,3,1,-1},{11075,3,1,-1}, +{11145,3,0,-1},{11146,3,1,-1},{11149,3,0,-1},{11150,3,1,-1},{11154,3,0,-1}, +{11155,3,1,-1},{11158,3,0,-1},{11159,3,1,-1},{11162,3,0,-1},{11163,3,1,-1}, +{11167,3,0,-1},{11168,3,1,-1},{11171,3,0,-1},{11172,3,1,-1},{11175,3,0,-1}, +{11176,3,1,-1},{11180,3,0,-1},{11181,3,1,-1},{11184,3,0,-1},{11185,3,1,-1}, +{11188,3,0,-1},{11189,3,1,-1},{11193,3,0,-1},{11194,3,1,-1},{11197,3,0,-1}, +{11198,3,1,-1},{11201,3,0,-1},{11202,3,1,-1},{11206,3,0,-1},{11207,3,1,-1}, +{11210,3,0,-1},{11211,3,1,-1},{11214,3,0,-1},{11215,3,1,-1},{11219,3,0,-1}, +{11220,3,1,-1},{11223,3,0,-1},{11224,3,1,-1},{11227,3,0,-1},{11228,3,1,-1}, +{11232,3,0,-1},{11233,3,1,-1},{11166,3,0,-1},{11236,3,0,-1},{11237,3,1,-1}, +{11240,3,0,-1},{11241,3,1,-1},{11245,3,0,-1},{11246,3,1,-1},{11179,3,1,-1}, +{11249,3,0,-1},{11250,3,1,-1},{11253,3,0,-1},{11254,3,1,-1},{11258,3,0,-1}, +{11259,3,1,-1},{11262,3,0,-1},{11263,3,1,-1},{11266,3,0,-1},{11267,3,1,-1}, +{11271,3,0,-1},{11272,3,1,-1},{11275,3,0,-1},{11276,3,1,-1},{11279,3,0,-1}, +{11280,3,1,-1},{11284,3,0,-1},{11285,3,1,-1},{11288,3,0,-1},{11289,3,1,-1}, +{11292,3,0,-1},{11293,3,1,-1},{11297,3,0,-1},{11298,3,1,-1},{11301,3,0,-1}, +{11302,3,1,-1},{11305,3,0,-1},{11306,3,1,-1},{11310,3,0,-1},{11311,3,1,-1}, +{11314,3,0,-1},{11315,3,1,-1},{11318,3,0,-1},{11319,3,1,-1},{11323,3,0,-1}, +{11324,3,1,-1},{11327,3,0,-1},{11328,3,1,-1},{11331,3,0,-1},{11332,3,1,-1}, +{11336,3,0,-1},{11337,3,1,-1},{11270,3,0,-1},{11340,3,0,-1},{11341,3,1,-1}, +{11344,3,0,-1},{11345,3,1,-1},{11349,3,0,-1},{11350,3,1,-1},{11283,3,1,-1}, +{11353,3,0,-1},{11354,3,1,-1},{11357,3,0,-1},{11358,3,1,-1},{11362,3,0,-1}, +{11363,3,1,-1},{11366,3,0,-1},{11367,3,1,-1},{11370,3,0,-1},{11371,3,1,-1}, +{11375,3,0,-1},{11376,3,1,-1},{11379,3,0,-1},{11380,3,1,-1},{11383,3,0,-1}, +{11384,3,1,-1},{11388,3,0,-1},{11389,3,1,-1},{11392,3,0,-1},{11393,3,1,-1}, +{11396,3,0,-1},{11397,3,1,-1},{11401,3,0,-1},{11402,3,1,-1},{11405,3,0,-1}, +{11406,3,1,-1},{11409,3,0,-1},{11410,3,1,-1},{11414,3,0,-1},{11415,3,1,-1}, +{11418,3,0,-1},{11419,3,1,-1},{11422,3,0,-1},{11423,3,1,-1},{11427,3,0,-1}, +{11428,3,1,-1},{11431,3,0,-1},{11432,3,1,-1},{11435,3,0,-1},{11436,3,1,-1}, +{11440,3,0,-1},{11441,3,1,-1},{11374,3,0,-1},{11444,3,0,-1},{11445,3,1,-1}, +{11448,3,0,-1},{11449,3,1,-1},{11453,3,0,-1},{11454,3,1,-1},{11387,3,1,-1}, +{11457,3,0,-1},{11458,3,1,-1},{11461,3,0,-1},{11462,3,1,-1},{11466,3,0,-1}, +{11467,3,1,-1},{11470,3,0,-1},{11471,3,1,-1},{11474,3,0,-1},{11475,3,1,-1}, +{11479,3,0,-1},{11480,3,1,-1},{11483,3,0,-1},{11484,3,1,-1},{11487,3,0,-1}, +{11488,3,1,-1},{11492,3,0,-1},{11493,3,1,-1},{11496,3,0,-1},{11497,3,1,-1}, +{11500,3,0,-1},{11501,3,1,-1},{11505,3,0,-1},{11506,3,1,-1},{11509,3,0,-1}, +{11510,3,1,-1},{11513,3,0,-1},{11514,3,1,-1},{11518,3,0,-1},{11519,3,1,-1}, +{11522,3,0,-1},{11523,3,1,-1},{11526,3,0,-1},{11527,3,1,-1},{11531,3,0,-1}, +{11532,3,1,-1},{11535,3,0,-1},{11536,3,1,-1},{11539,3,0,-1},{11540,3,1,-1}, +{11544,3,0,-1},{11545,3,1,-1},{11478,3,0,-1},{11548,3,0,-1},{11549,3,1,-1}, +{11552,3,0,-1},{11553,3,1,-1},{11557,3,0,-1},{11558,3,1,-1},{11491,3,1,-1}, +{11561,3,0,-1},{11562,3,1,-1},{11565,3,0,-1},{11566,3,1,-1},{11570,3,0,-1}, +{11571,3,1,-1},{11574,3,0,-1},{11575,3,1,-1},{11578,3,0,-1},{11579,3,1,-1}, +{11583,3,0,-1},{11584,3,1,-1},{11587,3,0,-1},{11588,3,1,-1},{11591,3,0,-1}, +{11592,3,1,-1},{11596,3,0,-1},{11597,3,1,-1},{11600,3,0,-1},{11601,3,1,-1}, +{11604,3,0,-1},{11605,3,1,-1},{11609,3,0,-1},{11610,3,1,-1},{11613,3,0,-1}, +{11614,3,1,-1},{11617,3,0,-1},{11618,3,1,-1},{11622,3,0,-1},{11623,3,1,-1}, +{11626,3,0,-1},{11627,3,1,-1},{11630,3,0,-1},{11631,3,1,-1},{11635,3,0,-1}, +{11636,3,1,-1},{11639,3,0,-1},{11640,3,1,-1},{11643,3,0,-1},{11644,3,1,-1}, +{11648,3,0,-1},{11649,3,1,-1},{11582,3,0,-1},{11652,3,0,-1},{11653,3,1,-1}, +{11656,3,0,-1},{11657,3,1,-1},{11661,3,0,-1},{11662,3,1,-1},{11595,3,1,-1}, +{11665,3,0,-1},{11666,3,1,-1},{11669,3,0,-1},{11670,3,1,-1},{11674,3,0,-1}, +{11675,3,1,-1},{11678,3,0,-1},{11679,3,1,-1},{11682,3,0,-1},{11683,3,1,-1}, +{11687,3,0,-1},{11688,3,1,-1},{11691,3,0,-1},{11692,3,1,-1},{11695,3,0,-1}, +{11696,3,1,-1},{11700,3,0,-1},{11701,3,1,-1},{11704,3,0,-1},{11705,3,1,-1}, +{11708,3,0,-1},{11709,3,1,-1},{11713,3,0,-1},{11714,3,1,-1},{11717,3,0,-1}, +{11718,3,1,-1},{11721,3,0,-1},{11722,3,1,-1},{11726,3,0,-1},{11727,3,1,-1}, +{11730,3,0,-1},{11731,3,1,-1},{11734,3,0,-1},{11735,3,1,-1},{11739,3,0,-1}, +{11740,3,1,-1},{11743,3,0,-1},{11744,3,1,-1},{11747,3,0,-1},{11748,3,1,-1}, +{11752,3,0,-1},{11753,3,1,-1},{11686,3,0,-1},{11756,3,0,-1},{11757,3,1,-1}, +{11760,3,0,-1},{11761,3,1,-1},{11765,3,0,-1},{11766,3,1,-1},{11699,3,1,-1}, +{11769,3,0,-1},{11770,3,1,-1},{11773,3,0,-1},{11774,3,1,-1},{11778,3,0,-1}, +{11779,3,1,-1},{11782,3,0,-1},{11783,3,1,-1},{11786,3,0,-1},{11787,3,1,-1}, +{11791,3,0,-1},{11792,3,1,-1},{11795,3,0,-1},{11796,3,1,-1},{11799,3,0,-1}, +{11800,3,1,-1},{11804,3,0,-1},{11805,3,1,-1},{11808,3,0,-1},{11809,3,1,-1}, +{11812,3,0,-1},{11813,3,1,-1},{11817,3,0,-1},{11818,3,1,-1},{11821,3,0,-1}, +{11822,3,1,-1},{11825,3,0,-1},{11826,3,1,-1},{11830,3,0,-1},{11831,3,1,-1}, +{11834,3,0,-1},{11835,3,1,-1},{11838,3,0,-1},{11839,3,1,-1},{11843,3,0,-1}, +{11844,3,1,-1},{11847,3,0,-1},{11848,3,1,-1},{11851,3,0,-1},{11852,3,1,-1}, +{11856,3,0,-1},{11857,3,1,-1},{11790,3,0,-1},{11860,3,0,-1},{11861,3,1,-1}, +{11864,3,0,-1},{11865,3,1,-1},{11869,3,0,-1},{11870,3,1,-1},{11803,3,1,-1}, +{11873,3,0,-1},{11874,3,1,-1},{11877,3,0,-1},{11878,3,1,-1},{11882,3,0,-1}, +{11883,3,1,-1},{11886,3,0,-1},{11887,3,1,-1},{11890,3,0,-1},{11891,3,1,-1}, +{11895,3,0,-1},{11896,3,1,-1},{11899,3,0,-1},{11900,3,1,-1},{11903,3,0,-1}, +{11904,3,1,-1},{11908,3,0,-1},{11909,3,1,-1},{11912,3,0,-1},{11913,3,1,-1}, +{11916,3,0,-1},{11917,3,1,-1},{11921,3,0,-1},{11922,3,1,-1},{11925,3,0,-1}, +{11926,3,1,-1},{11929,3,0,-1},{11930,3,1,-1},{11934,3,0,-1},{11935,3,1,-1}, +{11938,3,0,-1},{11939,3,1,-1},{11942,3,0,-1},{11943,3,1,-1},{11947,3,0,-1}, +{11948,3,1,-1},{11951,3,0,-1},{11952,3,1,-1},{11955,3,0,-1},{11956,3,1,-1}, +{11960,3,0,-1},{11961,3,1,-1},{11894,3,0,-1},{11964,3,0,-1},{11965,3,1,-1}, +{11968,3,0,-1},{11969,3,1,-1},{11973,3,0,-1},{11974,3,1,-1},{11907,3,1,-1}, +{11977,3,0,-1},{11978,3,1,-1},{11981,3,0,-1},{11982,3,1,-1},{11986,3,0,-1}, +{11987,3,1,-1},{11990,3,0,-1},{11991,3,1,-1},{11994,3,0,-1},{11995,3,1,-1}, +{11999,3,0,-1},{12000,3,1,-1},{12003,3,0,-1},{12004,3,1,-1},{12007,3,0,-1}, +{12008,3,1,-1},{12012,3,0,-1},{12013,3,1,-1},{12016,3,0,-1},{12017,3,1,-1}, +{12020,3,0,-1},{12021,3,1,-1},{12025,3,0,-1},{12026,3,1,-1},{12029,3,0,-1}, +{12030,3,1,-1},{12033,3,0,-1},{12034,3,1,-1},{12038,3,0,-1},{12039,3,1,-1}, +{12042,3,0,-1},{12043,3,1,-1},{12046,3,0,-1},{12047,3,1,-1},{12051,3,0,-1}, +{12052,3,1,-1},{12055,3,0,-1},{12056,3,1,-1},{12059,3,0,-1},{12060,3,1,-1}, +{12064,3,0,-1},{12065,3,1,-1},{11998,3,0,-1},{12068,3,0,-1},{12069,3,1,-1}, +{12072,3,0,-1},{12073,3,1,-1},{12077,3,0,-1},{12078,3,1,-1},{12011,3,1,-1}, +{12081,3,0,-1},{12082,3,1,-1},{12085,3,0,-1},{12086,3,1,-1},{12090,3,0,-1}, +{12091,3,1,-1},{12094,3,0,-1},{12095,3,1,-1},{12098,3,0,-1},{12099,3,1,-1}, +{12103,3,0,-1},{12104,3,1,-1},{12107,3,0,-1},{12108,3,1,-1},{12111,3,0,-1}, +{12112,3,1,-1},{12116,3,0,-1},{12117,3,1,-1},{12120,3,0,-1},{12121,3,1,-1}, +{12124,3,0,-1},{12125,3,1,-1},{12129,3,0,-1},{12130,3,1,-1},{12133,3,0,-1}, +{12134,3,1,-1},{12137,3,0,-1},{12138,3,1,-1},{12142,3,0,-1},{12143,3,1,-1}, +{12146,3,0,-1},{12147,3,1,-1},{12150,3,0,-1},{12151,3,1,-1},{12155,3,0,-1}, +{12156,3,1,-1},{12159,3,0,-1},{12160,3,1,-1},{12164,3,1,-1},{12168,3,0,-1}, +{12169,3,1,-1},{12102,3,0,-1},{12172,3,0,-1},{12173,3,1,-1},{12176,3,0,-1}, +{12177,3,1,-1},{12181,3,0,-1},{12182,3,1,-1},{12115,3,1,-1},{12185,3,0,-1}, +{12186,3,1,-1},{12189,3,0,-1},{12190,3,1,-1},{12194,3,0,-1},{12195,3,1,-1}, +{12198,3,0,-1},{12199,3,1,-1},{12202,3,0,-1},{12203,3,1,-1},{12207,3,0,-1}, +{12208,3,1,-1},{12211,3,0,-1},{12212,3,1,-1},{12216,3,1,-1},{12220,3,0,-1}, +{12221,3,1,-1},{12224,3,0,-1},{12225,3,1,-1},{12228,3,0,-1},{12229,3,1,-1}, +{12233,3,0,-1},{12234,3,1,-1},{12237,3,0,-1},{12238,3,1,-1},{12241,3,0,-1}, +{12242,3,1,-1},{12246,3,0,-1},{12247,3,1,-1},{12250,3,0,-1},{12251,3,1,-1}, +{12254,3,0,-1},{12255,3,1,-1},{12260,3,1,-1},{12264,3,1,-1},{12268,3,1,-1}, +{12273,3,1,-1},{12281,3,1,-1},{12286,3,1,-1},{12290,3,1,-1},{12294,3,1,-1}, +{12299,3,1,-1},{12303,3,1,-1},{12307,3,1,-1}}; + +/* Frame number data sampled from measurement.c:lchan_meas_check_compute() + * Call was made between to phones on half rate channels TS4, SS0 and SS1 */ +struct fn_sample test_fn_tch_h_ts_4_ss0_ss1[] = { +{7704,4,0,-1},{7713,4,0,-1},{7722,4,0,-1},{7730,4,0,-1},{7739,4,0,-1}, +{7748,4,0,-1},{7756,4,0,-1},{7765,4,0,-1},{7774,4,0,-1},{7782,4,0,-1}, +{7791,4,0,-1},{7800,4,0,-1},{7808,4,0,-1},{7817,4,0,-1},{7826,4,0,-1}, +{7760,4,0,-1},{7834,4,0,-1},{7843,4,0,-1},{7852,4,0,-1},{7860,4,0,-1}, +{7865,4,0,-1},{7869,4,0,-1},{7873,4,0,-1},{7878,4,0,-1},{7882,4,0,-1}, +{7886,4,0,-1},{7891,4,0,-1},{7895,4,0,-1},{7899,4,0,-1},{7904,4,0,-1}, +{7908,4,0,-1},{7912,4,0,-1},{7917,4,0,-1},{7921,4,0,-1},{7925,4,0,-1}, +{7930,4,0,-1},{7864,4,0,-1},{7934,4,0,-1},{7938,4,0,-1},{7943,4,0,-1}, +{7947,4,0,-1},{7951,4,0,-1},{7956,4,0,-1},{7960,4,0,-1},{7964,4,0,-1}, +{7969,4,0,-1},{7973,4,0,-1},{7977,4,0,-1},{7982,4,0,-1},{7986,4,0,-1}, +{7990,4,0,-1},{7995,4,0,-1},{7999,4,0,-1},{8003,4,0,-1},{8008,4,0,-1}, +{8012,4,0,-1},{8016,4,0,-1},{8021,4,0,-1},{8025,4,0,-1},{8029,4,0,-1}, +{8034,4,0,-1},{7968,4,0,-1},{8038,4,0,-1},{8042,4,0,-1},{8047,4,0,-1}, +{8051,4,0,-1},{8055,4,0,-1},{8060,4,0,-1},{8064,4,0,-1},{8068,4,0,-1}, +{8073,4,0,-1},{8077,4,0,-1},{8081,4,0,-1},{8086,4,0,-1},{8090,4,0,-1}, +{8094,4,0,-1},{8099,4,0,-1},{8103,4,0,-1},{8107,4,0,-1},{8112,4,0,-1}, +{8116,4,0,-1},{8120,4,0,-1},{8121,4,1,-1},{8125,4,0,-1},{8129,4,0,-1}, +{8130,4,1,-1},{8133,4,0,-1},{8138,4,0,-1},{8139,4,1,-1},{8072,4,0,-1}, +{8142,4,0,-1},{8146,4,0,-1},{8147,4,1,-1},{8151,4,0,-1},{8155,4,0,-1}, +{8156,4,1,-1},{8159,4,0,-1},{8164,4,0,-1},{8165,4,1,-1},{8168,4,0,-1}, +{8172,4,0,-1},{8173,4,1,-1},{8177,4,0,-1},{8181,4,0,-1},{8182,4,1,-1}, +{8185,4,0,-1},{8190,4,0,-1},{8191,4,1,-1},{8194,4,0,-1},{8198,4,0,-1}, +{8199,4,1,-1},{8203,4,0,-1},{8207,4,0,-1},{8208,4,1,-1},{8211,4,0,-1}, +{8216,4,0,-1},{8217,4,1,-1},{8220,4,0,-1},{8224,4,0,-1},{8225,4,1,-1}, +{8229,4,0,-1},{8230,4,1,-1},{8233,4,0,-1},{8234,4,1,-1},{8237,4,0,-1}, +{8238,4,1,-1},{8242,4,0,-1},{8243,4,1,-1},{8176,4,0,-1},{8246,4,0,-1}, +{8247,4,1,-1},{8250,4,0,-1},{8251,4,1,-1},{8255,4,0,-1},{8189,4,1,-1}, +{8259,4,0,-1},{8260,4,1,-1},{8263,4,0,-1},{8264,4,1,-1},{8268,4,0,-1}, +{8269,4,1,-1},{8272,4,0,-1},{8273,4,1,-1},{8276,4,0,-1},{8277,4,1,-1}, +{8281,4,0,-1},{8282,4,1,-1},{8285,4,0,-1},{8286,4,1,-1},{8289,4,0,-1}, +{8290,4,1,-1},{8294,4,0,-1},{8295,4,1,-1},{8299,4,1,-1},{8302,4,0,-1}, +{8303,4,1,-1},{8307,4,0,-1},{8308,4,1,-1},{8311,4,0,-1},{8312,4,1,-1}, +{8315,4,0,-1},{8316,4,1,-1},{8320,4,0,-1},{8321,4,1,-1},{8324,4,0,-1}, +{8325,4,1,-1},{8328,4,0,-1},{8329,4,1,-1},{8333,4,0,-1},{8334,4,1,-1}, +{8337,4,0,-1},{8338,4,1,-1},{8341,4,0,-1},{8342,4,1,-1},{8346,4,0,-1}, +{8347,4,1,-1},{8280,4,0,-1},{8350,4,0,-1},{8351,4,1,-1},{8354,4,0,-1}, +{8355,4,1,-1},{8359,4,0,-1},{8360,4,1,-1},{8293,4,1,-1},{8363,4,0,-1}, +{8364,4,1,-1},{8367,4,0,-1},{8368,4,1,-1},{8372,4,0,-1},{8373,4,1,-1}, +{8376,4,0,-1},{8377,4,1,-1},{8380,4,0,-1},{8381,4,1,-1},{8385,4,0,-1}, +{8386,4,1,-1},{8389,4,0,-1},{8390,4,1,-1},{8393,4,0,-1},{8394,4,1,-1}, +{8398,4,0,-1},{8399,4,1,-1},{8402,4,0,-1},{8403,4,1,-1},{8406,4,0,-1}, +{8407,4,1,-1},{8411,4,0,-1},{8412,4,1,-1},{8415,4,0,-1},{8416,4,1,-1}, +{8419,4,0,-1},{8420,4,1,-1},{8424,4,0,-1},{8425,4,1,-1},{8428,4,0,-1}, +{8429,4,1,-1},{8432,4,0,-1},{8433,4,1,-1},{8437,4,0,-1},{8438,4,1,-1}, +{8441,4,0,-1},{8442,4,1,-1},{8445,4,0,-1},{8446,4,1,-1},{8450,4,0,-1}, +{8451,4,1,-1},{8384,4,0,-1},{8454,4,0,-1},{8455,4,1,-1},{8458,4,0,-1}, +{8459,4,1,-1},{8463,4,0,-1},{8397,4,1,-1},{8467,4,0,-1},{8468,4,1,-1}, +{8471,4,0,-1},{8472,4,1,-1},{8476,4,0,-1},{8477,4,1,-1},{8480,4,0,-1}, +{8481,4,1,-1},{8484,4,0,-1},{8485,4,1,-1},{8489,4,0,-1},{8490,4,1,-1}, +{8493,4,0,-1},{8494,4,1,-1},{8497,4,0,-1},{8498,4,1,-1},{8502,4,0,-1}, +{8503,4,1,-1},{8507,4,1,-1},{8510,4,0,-1},{8511,4,1,-1},{8515,4,0,-1}, +{8519,4,0,-1},{8520,4,1,-1},{8523,4,0,-1},{8524,4,1,-1},{8528,4,0,-1}, +{8529,4,1,-1},{8532,4,0,-1},{8533,4,1,-1},{8536,4,0,-1},{8537,4,1,-1}, +{8541,4,0,-1},{8542,4,1,-1},{8545,4,0,-1},{8546,4,1,-1},{8549,4,0,-1}, +{8550,4,1,-1},{8554,4,0,-1},{8555,4,1,-1},{8488,4,0,-1},{8558,4,0,-1}, +{8559,4,1,-1},{8562,4,0,-1},{8563,4,1,-1},{8567,4,0,-1},{8568,4,1,-1}, +{8501,4,1,-1},{8571,4,0,-1},{8572,4,1,-1},{8575,4,0,-1},{8576,4,1,-1}, +{8580,4,0,-1},{8581,4,1,-1},{8584,4,0,-1},{8585,4,1,-1},{8588,4,0,-1}, +{8589,4,1,-1},{8593,4,0,-1},{8594,4,1,-1},{8597,4,0,-1},{8598,4,1,-1}, +{8601,4,0,-1},{8602,4,1,-1},{8606,4,0,-1},{8607,4,1,-1},{8610,4,0,-1}, +{8611,4,1,-1},{8614,4,0,-1},{8615,4,1,-1},{8619,4,0,-1},{8620,4,1,-1}, +{8623,4,0,-1},{8624,4,1,-1},{8627,4,0,-1},{8628,4,1,-1},{8632,4,0,-1}, +{8633,4,1,-1},{8636,4,0,-1},{8637,4,1,-1},{8640,4,0,-1},{8641,4,1,-1}, +{8645,4,0,-1},{8646,4,1,-1},{8649,4,0,-1},{8650,4,1,-1},{8653,4,0,-1}, +{8654,4,1,-1},{8658,4,0,-1},{8659,4,1,-1},{8592,4,0,-1},{8662,4,0,-1}, +{8663,4,1,-1},{8666,4,0,-1},{8667,4,1,-1},{8671,4,0,-1},{8672,4,1,-1}, +{8605,4,1,-1},{8675,4,0,-1},{8676,4,1,-1},{8679,4,0,-1},{8680,4,1,-1}, +{8684,4,0,-1},{8685,4,1,-1},{8688,4,0,-1},{8689,4,1,-1},{8692,4,0,-1}, +{8693,4,1,-1},{8697,4,0,-1},{8698,4,1,-1},{8701,4,0,-1},{8702,4,1,-1}, +{8705,4,0,-1},{8706,4,1,-1},{8710,4,0,-1},{8711,4,1,-1},{8714,4,0,-1}, +{8715,4,1,-1},{8718,4,0,-1},{8719,4,1,-1},{8723,4,0,-1},{8724,4,1,-1}, +{8727,4,0,-1},{8728,4,1,-1},{8731,4,0,-1},{8732,4,1,-1},{8736,4,0,-1}, +{8737,4,1,-1},{8740,4,0,-1},{8741,4,1,-1},{8744,4,0,-1},{8745,4,1,-1}, +{8749,4,0,-1},{8750,4,1,-1},{8753,4,0,-1},{8754,4,1,-1},{8757,4,0,-1}, +{8758,4,1,-1},{8762,4,0,-1},{8763,4,1,-1},{8696,4,0,-1},{8766,4,0,-1}, +{8767,4,1,-1},{8770,4,0,-1},{8771,4,1,-1},{8775,4,0,-1},{8776,4,1,-1}, +{8709,4,1,-1},{8779,4,0,-1},{8780,4,1,-1},{8783,4,0,-1},{8784,4,1,-1}, +{8788,4,0,-1},{8789,4,1,-1},{8792,4,0,-1},{8793,4,1,-1},{8796,4,0,-1}, +{8797,4,1,-1},{8801,4,0,-1},{8802,4,1,-1},{8805,4,0,-1},{8806,4,1,-1}, +{8809,4,0,-1},{8810,4,1,-1},{8814,4,0,-1},{8815,4,1,-1},{8818,4,0,-1}, +{8819,4,1,-1},{8822,4,0,-1},{8823,4,1,-1},{8827,4,0,-1},{8828,4,1,-1}, +{8831,4,0,-1},{8832,4,1,-1},{8835,4,0,-1},{8836,4,1,-1},{8840,4,0,-1}, +{8841,4,1,-1},{8844,4,0,-1},{8845,4,1,-1},{8848,4,0,-1},{8849,4,1,-1}, +{8853,4,0,-1},{8854,4,1,-1},{8857,4,0,-1},{8858,4,1,-1},{8861,4,0,-1}, +{8862,4,1,-1},{8866,4,0,-1},{8867,4,1,-1},{8800,4,0,-1},{8870,4,0,-1}, +{8871,4,1,-1},{8874,4,0,-1},{8875,4,1,-1},{8879,4,0,-1},{8880,4,1,-1}, +{8813,4,1,-1},{8883,4,0,-1},{8884,4,1,-1},{8887,4,0,-1},{8888,4,1,-1}, +{8892,4,0,-1},{8893,4,1,-1},{8896,4,0,-1},{8897,4,1,-1},{8900,4,0,-1}, +{8901,4,1,-1},{8905,4,0,-1},{8906,4,1,-1},{8909,4,0,-1},{8910,4,1,-1}, +{8913,4,0,-1},{8914,4,1,-1},{8918,4,0,-1},{8919,4,1,-1},{8922,4,0,-1}, +{8923,4,1,-1},{8926,4,0,-1},{8927,4,1,-1},{8931,4,0,-1},{8932,4,1,-1}, +{8935,4,0,-1},{8936,4,1,-1},{8939,4,0,-1},{8940,4,1,-1},{8944,4,0,-1}, +{8945,4,1,-1},{8948,4,0,-1},{8949,4,1,-1},{8952,4,0,-1},{8953,4,1,-1}, +{8957,4,0,-1},{8958,4,1,-1},{8961,4,0,-1},{8962,4,1,-1},{8965,4,0,-1}, +{8966,4,1,-1},{8970,4,0,-1},{8971,4,1,-1},{8904,4,0,-1},{8974,4,0,-1}, +{8975,4,1,-1},{8978,4,0,-1},{8979,4,1,-1},{8983,4,0,-1},{8984,4,1,-1}, +{8917,4,1,-1},{8987,4,0,-1},{8988,4,1,-1},{8991,4,0,-1},{8992,4,1,-1}, +{8996,4,0,-1},{8997,4,1,-1},{9000,4,0,-1},{9001,4,1,-1},{9004,4,0,-1}, +{9005,4,1,-1},{9009,4,0,-1},{9010,4,1,-1},{9013,4,0,-1},{9014,4,1,-1}, +{9017,4,0,-1},{9018,4,1,-1},{9022,4,0,-1},{9023,4,1,-1},{9026,4,0,-1}, +{9027,4,1,-1},{9030,4,0,-1},{9031,4,1,-1},{9035,4,0,-1},{9036,4,1,-1}, +{9039,4,0,-1},{9040,4,1,-1},{9043,4,0,-1},{9044,4,1,-1},{9048,4,0,-1}, +{9049,4,1,-1},{9052,4,0,-1},{9053,4,1,-1},{9056,4,0,-1},{9057,4,1,-1}, +{9061,4,0,-1},{9062,4,1,-1},{9065,4,0,-1},{9066,4,1,-1},{9069,4,0,-1}, +{9070,4,1,-1},{9074,4,0,-1},{9075,4,1,-1},{9008,4,0,-1},{9078,4,0,-1}, +{9079,4,1,-1},{9082,4,0,-1},{9083,4,1,-1},{9087,4,0,-1},{9088,4,1,-1}, +{9021,4,1,-1},{9091,4,0,-1},{9092,4,1,-1},{9095,4,0,-1},{9096,4,1,-1}, +{9100,4,0,-1},{9101,4,1,-1},{9104,4,0,-1},{9105,4,1,-1},{9108,4,0,-1}, +{9109,4,1,-1},{9113,4,0,-1},{9114,4,1,-1},{9117,4,0,-1},{9118,4,1,-1}, +{9121,4,0,-1},{9122,4,1,-1},{9126,4,0,-1},{9127,4,1,-1},{9130,4,0,-1}, +{9131,4,1,-1},{9134,4,0,-1},{9135,4,1,-1},{9139,4,0,-1},{9140,4,1,-1}, +{9143,4,0,-1},{9144,4,1,-1},{9147,4,0,-1},{9148,4,1,-1},{9152,4,0,-1}, +{9153,4,1,-1},{9156,4,0,-1},{9157,4,1,-1},{9160,4,0,-1},{9161,4,1,-1}, +{9165,4,0,-1},{9166,4,1,-1},{9169,4,0,-1},{9170,4,1,-1},{9173,4,0,-1}, +{9174,4,1,-1},{9178,4,0,-1},{9179,4,1,-1},{9112,4,0,-1},{9182,4,0,-1}, +{9183,4,1,-1},{9186,4,0,-1},{9187,4,1,-1},{9191,4,0,-1},{9192,4,1,-1}, +{9125,4,1,-1},{9195,4,0,-1},{9196,4,1,-1},{9199,4,0,-1},{9200,4,1,-1}, +{9204,4,0,-1},{9205,4,1,-1},{9208,4,0,-1},{9209,4,1,-1},{9212,4,0,-1}, +{9213,4,1,-1},{9217,4,0,-1},{9218,4,1,-1},{9221,4,0,-1},{9222,4,1,-1}, +{9225,4,0,-1},{9226,4,1,-1},{9230,4,0,-1},{9231,4,1,-1},{9234,4,0,-1}, +{9235,4,1,-1},{9238,4,0,-1},{9239,4,1,-1},{9243,4,0,-1},{9244,4,1,-1}, +{9247,4,0,-1},{9248,4,1,-1},{9251,4,0,-1},{9252,4,1,-1},{9256,4,0,-1}, +{9257,4,1,-1},{9260,4,0,-1},{9261,4,1,-1},{9264,4,0,-1},{9265,4,1,-1}, +{9269,4,0,-1},{9270,4,1,-1},{9273,4,0,-1},{9274,4,1,-1},{9277,4,0,-1}, +{9278,4,1,-1},{9282,4,0,-1},{9283,4,1,-1},{9216,4,0,-1},{9286,4,0,-1}, +{9287,4,1,-1},{9290,4,0,-1},{9291,4,1,-1},{9295,4,0,-1},{9296,4,1,-1}, +{9229,4,1,-1},{9299,4,0,-1},{9300,4,1,-1},{9303,4,0,-1},{9304,4,1,-1}, +{9308,4,0,-1},{9309,4,1,-1},{9312,4,0,-1},{9313,4,1,-1},{9316,4,0,-1}, +{9317,4,1,-1},{9321,4,0,-1},{9322,4,1,-1},{9325,4,0,-1},{9326,4,1,-1}, +{9329,4,0,-1},{9330,4,1,-1},{9334,4,0,-1},{9335,4,1,-1},{9338,4,0,-1}, +{9339,4,1,-1},{9342,4,0,-1},{9343,4,1,-1},{9347,4,0,-1},{9348,4,1,-1}, +{9351,4,0,-1},{9352,4,1,-1},{9355,4,0,-1},{9356,4,1,-1},{9360,4,0,-1}, +{9361,4,1,-1},{9364,4,0,-1},{9365,4,1,-1},{9368,4,0,-1},{9369,4,1,-1}, +{9373,4,0,-1},{9374,4,1,-1},{9377,4,0,-1},{9378,4,1,-1},{9381,4,0,-1}, +{9382,4,1,-1},{9386,4,0,-1},{9387,4,1,-1},{9320,4,0,-1},{9390,4,0,-1}, +{9391,4,1,-1},{9394,4,0,-1},{9395,4,1,-1},{9399,4,0,-1},{9400,4,1,-1}, +{9333,4,1,-1},{9403,4,0,-1},{9404,4,1,-1},{9407,4,0,-1},{9408,4,1,-1}, +{9412,4,0,-1},{9413,4,1,-1},{9416,4,0,-1},{9417,4,1,-1},{9420,4,0,-1}, +{9421,4,1,-1},{9425,4,0,-1},{9426,4,1,-1},{9429,4,0,-1},{9430,4,1,-1}, +{9433,4,0,-1},{9434,4,1,-1},{9438,4,0,-1},{9439,4,1,-1},{9442,4,0,-1}, +{9443,4,1,-1},{9446,4,0,-1},{9447,4,1,-1},{9451,4,0,-1},{9452,4,1,-1}, +{9455,4,0,-1},{9456,4,1,-1},{9459,4,0,-1},{9460,4,1,-1},{9464,4,0,-1}, +{9465,4,1,-1},{9468,4,0,-1},{9469,4,1,-1},{9472,4,0,-1},{9473,4,1,-1}, +{9477,4,0,-1},{9478,4,1,-1},{9481,4,0,-1},{9482,4,1,-1},{9485,4,0,-1}, +{9486,4,1,-1},{9490,4,0,-1},{9491,4,1,-1},{9424,4,0,-1},{9494,4,0,-1}, +{9495,4,1,-1},{9498,4,0,-1},{9499,4,1,-1},{9503,4,0,-1},{9504,4,1,-1}, +{9437,4,1,-1},{9507,4,0,-1},{9508,4,1,-1},{9511,4,0,-1},{9512,4,1,-1}, +{9516,4,0,-1},{9517,4,1,-1},{9520,4,0,-1},{9521,4,1,-1},{9524,4,0,-1}, +{9525,4,1,-1},{9529,4,0,-1},{9530,4,1,-1},{9533,4,0,-1},{9534,4,1,-1}, +{9537,4,0,-1},{9538,4,1,-1},{9542,4,0,-1},{9543,4,1,-1},{9546,4,0,-1}, +{9547,4,1,-1},{9550,4,0,-1},{9551,4,1,-1},{9555,4,0,-1},{9556,4,1,-1}, +{9559,4,0,-1},{9560,4,1,-1},{9563,4,0,-1},{9564,4,1,-1},{9568,4,0,-1}, +{9569,4,1,-1},{9572,4,0,-1},{9573,4,1,-1},{9576,4,0,-1},{9577,4,1,-1}, +{9581,4,0,-1},{9582,4,1,-1},{9585,4,0,-1},{9586,4,1,-1},{9589,4,0,-1}, +{9590,4,1,-1},{9594,4,0,-1},{9595,4,1,-1},{9528,4,0,-1},{9598,4,0,-1}, +{9599,4,1,-1},{9602,4,0,-1},{9603,4,1,-1},{9607,4,0,-1},{9608,4,1,-1}, +{9541,4,1,-1},{9611,4,0,-1},{9612,4,1,-1},{9615,4,0,-1},{9616,4,1,-1}, +{9620,4,0,-1},{9621,4,1,-1},{9624,4,0,-1},{9625,4,1,-1},{9628,4,0,-1}, +{9629,4,1,-1},{9633,4,0,-1},{9634,4,1,-1},{9637,4,0,-1},{9638,4,1,-1}, +{9641,4,0,-1},{9642,4,1,-1},{9646,4,0,-1},{9647,4,1,-1},{9650,4,0,-1}, +{9651,4,1,-1},{9654,4,0,-1},{9655,4,1,-1},{9659,4,0,-1},{9660,4,1,-1}, +{9663,4,0,-1},{9664,4,1,-1},{9667,4,0,-1},{9668,4,1,-1},{9672,4,0,-1}, +{9673,4,1,-1},{9676,4,0,-1},{9677,4,1,-1},{9680,4,0,-1},{9681,4,1,-1}, +{9685,4,0,-1},{9686,4,1,-1},{9689,4,0,-1},{9690,4,1,-1},{9693,4,0,-1}, +{9694,4,1,-1},{9698,4,0,-1},{9699,4,1,-1},{9632,4,0,-1},{9702,4,0,-1}, +{9703,4,1,-1},{9706,4,0,-1},{9707,4,1,-1},{9711,4,0,-1},{9712,4,1,-1}, +{9645,4,1,-1},{9715,4,0,-1},{9716,4,1,-1},{9719,4,0,-1},{9720,4,1,-1}, +{9724,4,0,-1},{9725,4,1,-1},{9728,4,0,-1},{9729,4,1,-1},{9732,4,0,-1}, +{9733,4,1,-1},{9737,4,0,-1},{9738,4,1,-1},{9741,4,0,-1},{9742,4,1,-1}, +{9745,4,0,-1},{9746,4,1,-1},{9750,4,0,-1},{9751,4,1,-1},{9754,4,0,-1}, +{9755,4,1,-1},{9758,4,0,-1},{9759,4,1,-1},{9763,4,0,-1},{9764,4,1,-1}, +{9767,4,0,-1},{9768,4,1,-1},{9771,4,0,-1},{9772,4,1,-1},{9776,4,0,-1}, +{9777,4,1,-1},{9780,4,0,-1},{9781,4,1,-1},{9784,4,0,-1},{9785,4,1,-1}, +{9789,4,0,-1},{9790,4,1,-1},{9793,4,0,-1},{9794,4,1,-1},{9797,4,0,-1}, +{9798,4,1,-1},{9802,4,0,-1},{9803,4,1,-1},{9736,4,0,-1},{9806,4,0,-1}, +{9807,4,1,-1},{9810,4,0,-1},{9811,4,1,-1},{9815,4,0,-1},{9816,4,1,-1}, +{9749,4,1,-1},{9819,4,0,-1},{9820,4,1,-1},{9823,4,0,-1},{9824,4,1,-1}, +{9828,4,0,-1},{9829,4,1,-1},{9832,4,0,-1},{9833,4,1,-1},{9836,4,0,-1}, +{9837,4,1,-1},{9841,4,0,-1},{9842,4,1,-1},{9845,4,0,-1},{9846,4,1,-1}, +{9849,4,0,-1},{9850,4,1,-1},{9854,4,0,-1},{9855,4,1,-1},{9858,4,0,-1}, +{9859,4,1,-1},{9862,4,0,-1},{9863,4,1,-1},{9867,4,0,-1},{9868,4,1,-1}, +{9871,4,0,-1},{9872,4,1,-1},{9875,4,0,-1},{9876,4,1,-1},{9880,4,0,-1}, +{9881,4,1,-1},{9884,4,0,-1},{9885,4,1,-1},{9888,4,0,-1},{9889,4,1,-1}, +{9893,4,0,-1},{9894,4,1,-1},{9897,4,0,-1},{9898,4,1,-1},{9901,4,0,-1}, +{9902,4,1,-1},{9906,4,0,-1},{9907,4,1,-1},{9840,4,0,-1},{9910,4,0,-1}, +{9911,4,1,-1},{9914,4,0,-1},{9915,4,1,-1},{9919,4,0,-1},{9920,4,1,-1}, +{9853,4,1,-1},{9923,4,0,-1},{9924,4,1,-1},{9927,4,0,-1},{9928,4,1,-1}, +{9932,4,0,-1},{9933,4,1,-1},{9936,4,0,-1},{9937,4,1,-1},{9940,4,0,-1}, +{9941,4,1,-1},{9945,4,0,-1},{9946,4,1,-1},{9949,4,0,-1},{9950,4,1,-1}, +{9953,4,0,-1},{9954,4,1,-1},{9958,4,0,-1},{9959,4,1,-1},{9962,4,0,-1}, +{9963,4,1,-1},{9966,4,0,-1},{9967,4,1,-1},{9971,4,0,-1},{9972,4,1,-1}, +{9975,4,0,-1},{9976,4,1,-1},{9979,4,0,-1},{9980,4,1,-1},{9984,4,0,-1}, +{9985,4,1,-1},{9988,4,0,-1},{9989,4,1,-1},{9992,4,0,-1},{9993,4,1,-1}, +{9997,4,0,-1},{9998,4,1,-1},{10001,4,0,-1},{10002,4,1,-1},{10005,4,0,-1}, +{10006,4,1,-1},{10010,4,0,-1},{10011,4,1,-1},{9944,4,0,-1},{10014,4,0,-1}, +{10015,4,1,-1},{10018,4,0,-1},{10019,4,1,-1},{10023,4,0,-1},{10024,4,1,-1}, +{9957,4,1,-1},{10027,4,0,-1},{10028,4,1,-1},{10031,4,0,-1},{10032,4,1,-1}, +{10036,4,0,-1},{10037,4,1,-1},{10040,4,0,-1},{10041,4,1,-1},{10044,4,0,-1}, +{10045,4,1,-1},{10049,4,0,-1},{10050,4,1,-1},{10053,4,0,-1},{10054,4,1,-1}, +{10057,4,0,-1},{10058,4,1,-1},{10062,4,0,-1},{10063,4,1,-1},{10066,4,0,-1}, +{10067,4,1,-1},{10070,4,0,-1},{10071,4,1,-1},{10075,4,0,-1},{10076,4,1,-1}, +{10079,4,0,-1},{10080,4,1,-1},{10083,4,0,-1},{10084,4,1,-1},{10088,4,0,-1}, +{10089,4,1,-1},{10092,4,0,-1},{10093,4,1,-1},{10096,4,0,-1},{10097,4,1,-1}, +{10101,4,0,-1},{10102,4,1,-1},{10105,4,0,-1},{10106,4,1,-1},{10109,4,0,-1}, +{10110,4,1,-1},{10114,4,0,-1},{10115,4,1,-1},{10048,4,0,-1},{10118,4,0,-1}, +{10119,4,1,-1},{10122,4,0,-1},{10123,4,1,-1},{10127,4,0,-1},{10128,4,1,-1}, +{10061,4,1,-1},{10131,4,0,-1},{10132,4,1,-1},{10135,4,0,-1},{10136,4,1,-1}, +{10140,4,0,-1},{10141,4,1,-1},{10144,4,0,-1},{10145,4,1,-1},{10148,4,0,-1}, +{10149,4,1,-1},{10153,4,0,-1},{10154,4,1,-1},{10157,4,0,-1},{10158,4,1,-1}, +{10161,4,0,-1},{10162,4,1,-1},{10166,4,0,-1},{10167,4,1,-1},{10170,4,0,-1}, +{10171,4,1,-1},{10174,4,0,-1},{10175,4,1,-1},{10179,4,0,-1},{10180,4,1,-1}, +{10183,4,0,-1},{10184,4,1,-1},{10187,4,0,-1},{10188,4,1,-1},{10192,4,0,-1}, +{10193,4,1,-1},{10196,4,0,-1},{10197,4,1,-1},{10200,4,0,-1},{10201,4,1,-1}, +{10205,4,0,-1},{10206,4,1,-1},{10209,4,0,-1},{10210,4,1,-1},{10213,4,0,-1}, +{10214,4,1,-1},{10218,4,0,-1},{10219,4,1,-1},{10152,4,0,-1},{10222,4,0,-1}, +{10223,4,1,-1},{10226,4,0,-1},{10227,4,1,-1},{10231,4,0,-1},{10232,4,1,-1}, +{10165,4,1,-1},{10235,4,0,-1},{10236,4,1,-1},{10239,4,0,-1},{10240,4,1,-1}, +{10244,4,0,-1},{10245,4,1,-1},{10248,4,0,-1},{10249,4,1,-1},{10252,4,0,-1}, +{10253,4,1,-1},{10257,4,0,-1},{10258,4,1,-1},{10261,4,0,-1},{10262,4,1,-1}, +{10265,4,0,-1},{10266,4,1,-1},{10270,4,0,-1},{10271,4,1,-1},{10274,4,0,-1}, +{10275,4,1,-1},{10278,4,0,-1},{10279,4,1,-1},{10283,4,0,-1},{10284,4,1,-1}, +{10287,4,0,-1},{10288,4,1,-1},{10291,4,0,-1},{10292,4,1,-1},{10296,4,0,-1}, +{10297,4,1,-1},{10300,4,0,-1},{10301,4,1,-1},{10304,4,0,-1},{10305,4,1,-1}, +{10309,4,0,-1},{10310,4,1,-1},{10313,4,0,-1},{10314,4,1,-1},{10317,4,0,-1}, +{10318,4,1,-1},{10322,4,0,-1},{10323,4,1,-1},{10256,4,0,-1},{10326,4,0,-1}, +{10327,4,1,-1},{10330,4,0,-1},{10331,4,1,-1},{10335,4,0,-1},{10336,4,1,-1}, +{10269,4,1,-1},{10339,4,0,-1},{10340,4,1,-1},{10343,4,0,-1},{10344,4,1,-1}, +{10348,4,0,-1},{10349,4,1,-1},{10352,4,0,-1},{10353,4,1,-1},{10356,4,0,-1}, +{10357,4,1,-1},{10361,4,0,-1},{10362,4,1,-1},{10365,4,0,-1},{10366,4,1,-1}, +{10369,4,0,-1},{10370,4,1,-1},{10374,4,0,-1},{10375,4,1,-1},{10378,4,0,-1}, +{10379,4,1,-1},{10382,4,0,-1},{10383,4,1,-1},{10387,4,0,-1},{10388,4,1,-1}, +{10391,4,0,-1},{10392,4,1,-1},{10395,4,0,-1},{10396,4,1,-1},{10400,4,0,-1}, +{10401,4,1,-1},{10404,4,0,-1},{10405,4,1,-1},{10408,4,0,-1},{10409,4,1,-1}, +{10413,4,0,-1},{10414,4,1,-1},{10417,4,0,-1},{10418,4,1,-1},{10421,4,0,-1}, +{10422,4,1,-1},{10426,4,0,-1},{10427,4,1,-1},{10360,4,0,-1},{10430,4,0,-1}, +{10431,4,1,-1},{10434,4,0,-1},{10435,4,1,-1},{10439,4,0,-1},{10440,4,1,-1}, +{10373,4,1,-1},{10443,4,0,-1},{10444,4,1,-1},{10447,4,0,-1},{10448,4,1,-1}, +{10452,4,0,-1},{10453,4,1,-1},{10456,4,0,-1},{10457,4,1,-1},{10460,4,0,-1}, +{10461,4,1,-1},{10465,4,0,-1},{10466,4,1,-1},{10469,4,0,-1},{10470,4,1,-1}, +{10473,4,0,-1},{10474,4,1,-1},{10478,4,0,-1},{10479,4,1,-1},{10482,4,0,-1}, +{10483,4,1,-1},{10486,4,0,-1},{10487,4,1,-1},{10491,4,0,-1},{10492,4,1,-1}, +{10495,4,0,-1},{10496,4,1,-1},{10499,4,0,-1},{10500,4,1,-1},{10504,4,0,-1}, +{10505,4,1,-1},{10508,4,0,-1},{10509,4,1,-1},{10512,4,0,-1},{10513,4,1,-1}, +{10517,4,0,-1},{10518,4,1,-1},{10521,4,0,-1},{10522,4,1,-1},{10525,4,0,-1}, +{10526,4,1,-1},{10530,4,0,-1},{10531,4,1,-1},{10464,4,0,-1},{10534,4,0,-1}, +{10535,4,1,-1},{10538,4,0,-1},{10539,4,1,-1},{10543,4,0,-1},{10544,4,1,-1}, +{10477,4,1,-1},{10547,4,0,-1},{10548,4,1,-1},{10551,4,0,-1},{10552,4,1,-1}, +{10556,4,0,-1},{10557,4,1,-1},{10560,4,0,-1},{10561,4,1,-1},{10564,4,0,-1}, +{10565,4,1,-1},{10569,4,0,-1},{10570,4,1,-1},{10573,4,0,-1},{10574,4,1,-1}, +{10577,4,0,-1},{10578,4,1,-1},{10582,4,0,-1},{10583,4,1,-1},{10586,4,0,-1}, +{10587,4,1,-1},{10590,4,0,-1},{10591,4,1,-1},{10595,4,0,-1},{10596,4,1,-1}, +{10599,4,0,-1},{10600,4,1,-1},{10603,4,0,-1},{10604,4,1,-1},{10608,4,0,-1}, +{10609,4,1,-1},{10612,4,0,-1},{10613,4,1,-1},{10616,4,0,-1},{10617,4,1,-1}, +{10621,4,0,-1},{10622,4,1,-1},{10625,4,0,-1},{10626,4,1,-1},{10629,4,0,-1}, +{10630,4,1,-1},{10634,4,0,-1},{10635,4,1,-1},{10568,4,0,-1},{10638,4,0,-1}, +{10639,4,1,-1},{10642,4,0,-1},{10643,4,1,-1},{10647,4,0,-1},{10648,4,1,-1}, +{10581,4,1,-1},{10651,4,0,-1},{10652,4,1,-1},{10655,4,0,-1},{10656,4,1,-1}, +{10660,4,0,-1},{10661,4,1,-1},{10664,4,0,-1},{10665,4,1,-1},{10668,4,0,-1}, +{10669,4,1,-1},{10673,4,0,-1},{10674,4,1,-1},{10677,4,0,-1},{10678,4,1,-1}, +{10681,4,0,-1},{10682,4,1,-1},{10686,4,0,-1},{10687,4,1,-1},{10690,4,0,-1}, +{10691,4,1,-1},{10694,4,0,-1},{10695,4,1,-1},{10699,4,0,-1},{10700,4,1,-1}, +{10703,4,0,-1},{10704,4,1,-1},{10707,4,0,-1},{10708,4,1,-1},{10712,4,0,-1}, +{10713,4,1,-1},{10716,4,0,-1},{10717,4,1,-1},{10720,4,0,-1},{10721,4,1,-1}, +{10725,4,0,-1},{10726,4,1,-1},{10729,4,0,-1},{10730,4,1,-1},{10733,4,0,-1}, +{10734,4,1,-1},{10738,4,0,-1},{10739,4,1,-1},{10672,4,0,-1},{10742,4,0,-1}, +{10743,4,1,-1},{10746,4,0,-1},{10747,4,1,-1},{10751,4,0,-1},{10752,4,1,-1}, +{10685,4,1,-1},{10755,4,0,-1},{10756,4,1,-1},{10759,4,0,-1},{10760,4,1,-1}, +{10764,4,0,-1},{10765,4,1,-1},{10768,4,0,-1},{10769,4,1,-1},{10772,4,0,-1}, +{10773,4,1,-1},{10777,4,0,-1},{10778,4,1,-1},{10781,4,0,-1},{10782,4,1,-1}, +{10785,4,0,-1},{10786,4,1,-1},{10790,4,0,-1},{10791,4,1,-1},{10794,4,0,-1}, +{10795,4,1,-1},{10798,4,0,-1},{10799,4,1,-1},{10803,4,0,-1},{10804,4,1,-1}, +{10807,4,0,-1},{10808,4,1,-1},{10811,4,0,-1},{10812,4,1,-1},{10816,4,0,-1}, +{10817,4,1,-1},{10820,4,0,-1},{10821,4,1,-1},{10824,4,0,-1},{10825,4,1,-1}, +{10829,4,0,-1},{10830,4,1,-1},{10833,4,0,-1},{10834,4,1,-1},{10837,4,0,-1}, +{10838,4,1,-1},{10842,4,0,-1},{10843,4,1,-1},{10776,4,0,-1},{10846,4,0,-1}, +{10847,4,1,-1},{10850,4,0,-1},{10851,4,1,-1},{10855,4,0,-1},{10856,4,1,-1}, +{10789,4,1,-1},{10859,4,0,-1},{10860,4,1,-1},{10863,4,0,-1},{10864,4,1,-1}, +{10868,4,0,-1},{10869,4,1,-1},{10872,4,0,-1},{10873,4,1,-1},{10876,4,0,-1}, +{10877,4,1,-1},{10881,4,0,-1},{10882,4,1,-1},{10885,4,0,-1},{10886,4,1,-1}, +{10889,4,0,-1},{10890,4,1,-1},{10894,4,0,-1},{10895,4,1,-1},{10898,4,0,-1}, +{10899,4,1,-1},{10902,4,0,-1},{10903,4,1,-1},{10907,4,0,-1},{10908,4,1,-1}, +{10911,4,0,-1},{10912,4,1,-1},{10915,4,0,-1},{10916,4,1,-1},{10920,4,0,-1}, +{10921,4,1,-1},{10924,4,0,-1},{10925,4,1,-1},{10928,4,0,-1},{10929,4,1,-1}, +{10933,4,0,-1},{10934,4,1,-1},{10937,4,0,-1},{10938,4,1,-1},{10941,4,0,-1}, +{10942,4,1,-1},{10946,4,0,-1},{10947,4,1,-1},{10880,4,0,-1},{10950,4,0,-1}, +{10951,4,1,-1},{10954,4,0,-1},{10955,4,1,-1},{10959,4,0,-1},{10960,4,1,-1}, +{10893,4,1,-1},{10963,4,0,-1},{10964,4,1,-1},{10967,4,0,-1},{10968,4,1,-1}, +{10972,4,0,-1},{10973,4,1,-1},{10976,4,0,-1},{10977,4,1,-1},{10980,4,0,-1}, +{10981,4,1,-1},{10985,4,0,-1},{10986,4,1,-1},{10989,4,0,-1},{10990,4,1,-1}, +{10993,4,0,-1},{10994,4,1,-1},{10998,4,0,-1},{10999,4,1,-1},{11002,4,0,-1}, +{11003,4,1,-1},{11006,4,0,-1},{11007,4,1,-1},{11012,4,1,-1},{11016,4,1,-1}, +{11020,4,1,-1},{11025,4,1,-1},{11029,4,1,-1},{11033,4,1,-1},{11038,4,1,-1}, +{11042,4,1,-1},{11046,4,1,-1},{11051,4,1,-1},{10984,4,0,-1},{11055,4,1,-1}, +{11058,4,0,-1},{11059,4,1,-1},{10997,4,1,-1},{11067,4,0,-1},{11068,4,1,-1}, +{11072,4,1,-1},{11077,4,1,-1},{11081,4,1,-1},{11085,4,1,-1},{11090,4,1,-1}, +{11094,4,1,-1},{11098,4,1,-1},{11102,4,0,-1},{11103,4,1,-1},{11107,4,1,-1}, +{11111,4,1,-1},{11116,4,1,-1},{11120,4,1,-1},{11129,4,1,-1},{11133,4,1,-1}}; + +/* Frame number data sampled from measurement.c:lchan_meas_check_compute() + * Call was made between to phones on half rate channels TS4, SS0 and SS1 */ +struct fn_sample test_fn_tch_h_ts_5_ss0_ss1[] = { +{5269,5,0,-1},{5278,5,0,-1},{5286,5,0,-1},{5295,5,0,-1},{5304,5,0,-1}, +{5312,5,0,-1},{5321,5,0,-1},{5330,5,0,-1},{5264,5,0,-1},{5338,5,0,-1}, +{5347,5,0,-1},{5351,5,0,-1},{5356,5,0,-1},{5360,5,0,-1},{5364,5,0,-1}, +{5369,5,0,-1},{5373,5,0,-1},{5377,5,0,-1},{5382,5,0,-1},{5386,5,0,-1}, +{5390,5,0,-1},{5395,5,0,-1},{5399,5,0,-1},{5403,5,0,-1},{5408,5,0,-1}, +{5412,5,0,-1},{5416,5,0,-1},{5421,5,0,-1},{5425,5,0,-1},{5429,5,0,-1}, +{5434,5,0,-1},{5368,5,0,-1},{5438,5,0,-1},{5442,5,0,-1},{5447,5,0,-1}, +{5451,5,0,-1},{5455,5,0,-1},{5460,5,0,-1},{5464,5,0,-1},{5468,5,0,-1}, +{5473,5,0,-1},{5477,5,0,-1},{5481,5,0,-1},{5486,5,0,-1},{5490,5,0,-1}, +{5494,5,0,-1},{5499,5,0,-1},{5503,5,0,-1},{5507,5,0,-1},{5512,5,0,-1}, +{5516,5,0,-1},{5520,5,0,-1},{5525,5,0,-1},{5529,5,0,-1},{5533,5,0,-1}, +{5538,5,0,-1},{5472,5,0,-1},{5542,5,0,-1},{5546,5,0,-1},{5551,5,0,-1}, +{5555,5,0,-1},{5559,5,0,-1},{5564,5,0,-1},{5568,5,0,-1},{5572,5,0,-1}, +{5577,5,0,-1},{5581,5,0,-1},{5585,5,0,-1},{5590,5,0,-1},{5594,5,0,-1}, +{5598,5,0,-1},{5603,5,0,-1},{5607,5,0,-1},{5611,5,0,-1},{5616,5,0,-1}, +{5620,5,0,-1},{5624,5,0,-1},{5629,5,0,-1},{5633,5,0,-1},{5637,5,0,-1}, +{5638,5,1,-1},{5642,5,0,-1},{5576,5,0,-1},{5646,5,0,-1},{5647,5,1,-1}, +{5650,5,0,-1},{5655,5,0,-1},{5656,5,1,-1},{5659,5,0,-1},{5663,5,0,-1}, +{5668,5,0,-1},{5669,5,1,-1},{5672,5,0,-1},{5676,5,0,-1},{5677,5,1,-1}, +{5681,5,0,-1},{5685,5,0,-1},{5686,5,1,-1},{5689,5,0,-1},{5694,5,0,-1}, +{5695,5,1,-1},{5698,5,0,-1},{5702,5,0,-1},{5703,5,1,-1},{5707,5,0,-1}, +{5711,5,0,-1},{5712,5,1,-1},{5715,5,0,-1},{5720,5,0,-1},{5721,5,1,-1}, +{5724,5,0,-1},{5728,5,0,-1},{5729,5,1,-1},{5733,5,0,-1},{5737,5,0,-1}, +{5738,5,1,-1},{5741,5,0,-1},{5746,5,0,-1},{5747,5,1,-1},{5680,5,0,-1}, +{5750,5,0,-1},{5754,5,0,-1},{5755,5,1,-1},{5759,5,0,-1},{5693,5,1,-1}, +{5763,5,0,-1},{5764,5,1,-1},{5767,5,0,-1},{5772,5,0,-1},{5773,5,1,-1}, +{5776,5,0,-1},{5780,5,0,-1},{5781,5,1,-1},{5785,5,0,-1},{5789,5,0,-1}, +{5790,5,1,-1},{5793,5,0,-1},{5798,5,0,-1},{5799,5,1,-1},{5802,5,0,-1}, +{5806,5,0,-1},{5807,5,1,-1},{5811,5,0,-1},{5815,5,0,-1},{5816,5,1,-1}, +{5819,5,0,-1},{5820,5,1,-1},{5824,5,0,-1},{5825,5,1,-1},{5828,5,0,-1}, +{5829,5,1,-1},{5832,5,0,-1},{5833,5,1,-1},{5837,5,0,-1},{5838,5,1,-1}, +{5841,5,0,-1},{5842,5,1,-1},{5845,5,0,-1},{5850,5,0,-1},{5851,5,1,-1}, +{5784,5,0,-1},{5854,5,0,-1},{5855,5,1,-1},{5858,5,0,-1},{5859,5,1,-1}, +{5863,5,0,-1},{5864,5,1,-1},{5797,5,1,-1},{5867,5,0,-1},{5868,5,1,-1}, +{5871,5,0,-1},{5872,5,1,-1},{5876,5,0,-1},{5877,5,1,-1},{5880,5,0,-1}, +{5881,5,1,-1},{5884,5,0,-1},{5885,5,1,-1},{5890,5,1,-1},{5893,5,0,-1}, +{5894,5,1,-1},{5897,5,0,-1},{5898,5,1,-1},{5902,5,0,-1},{5903,5,1,-1}, +{5906,5,0,-1},{5907,5,1,-1},{5910,5,0,-1},{5911,5,1,-1},{5915,5,0,-1}, +{5916,5,1,-1},{5919,5,0,-1},{5920,5,1,-1},{5923,5,0,-1},{5924,5,1,-1}, +{5928,5,0,-1},{5929,5,1,-1},{5932,5,0,-1},{5933,5,1,-1},{5936,5,0,-1}, +{5937,5,1,-1},{5941,5,0,-1},{5942,5,1,-1},{5945,5,0,-1},{5946,5,1,-1}, +{5949,5,0,-1},{5950,5,1,-1},{5954,5,0,-1},{5955,5,1,-1},{5888,5,0,-1}, +{5958,5,0,-1},{5959,5,1,-1},{5962,5,0,-1},{5963,5,1,-1},{5967,5,0,-1}, +{5968,5,1,-1},{5901,5,1,-1},{5971,5,0,-1},{5972,5,1,-1},{5975,5,0,-1}, +{5976,5,1,-1},{5980,5,0,-1},{5981,5,1,-1},{5984,5,0,-1},{5988,5,0,-1}, +{5989,5,1,-1},{5993,5,0,-1},{5994,5,1,-1},{5997,5,0,-1},{5998,5,1,-1}, +{6001,5,0,-1},{6002,5,1,-1},{6006,5,0,-1},{6007,5,1,-1},{6010,5,0,-1}, +{6011,5,1,-1},{6014,5,0,-1},{6015,5,1,-1},{6019,5,0,-1},{6020,5,1,-1}, +{6023,5,0,-1},{6024,5,1,-1},{6028,5,1,-1},{6032,5,0,-1},{6033,5,1,-1}, +{6036,5,0,-1},{6040,5,0,-1},{6041,5,1,-1},{6045,5,0,-1},{6046,5,1,-1}, +{6049,5,0,-1},{6050,5,1,-1},{6053,5,0,-1},{6054,5,1,-1},{6058,5,0,-1}, +{6059,5,1,-1},{5992,5,0,-1},{6062,5,0,-1},{6063,5,1,-1},{6066,5,0,-1}, +{6067,5,1,-1},{6071,5,0,-1},{6072,5,1,-1},{6005,5,1,-1},{6075,5,0,-1}, +{6076,5,1,-1},{6079,5,0,-1},{6080,5,1,-1},{6084,5,0,-1},{6085,5,1,-1}, +{6088,5,0,-1},{6089,5,1,-1},{6092,5,0,-1},{6093,5,1,-1},{6097,5,0,-1}, +{6098,5,1,-1},{6101,5,0,-1},{6102,5,1,-1},{6105,5,0,-1},{6106,5,1,-1}, +{6110,5,0,-1},{6111,5,1,-1},{6114,5,0,-1},{6115,5,1,-1},{6118,5,0,-1}, +{6119,5,1,-1},{6123,5,0,-1},{6124,5,1,-1},{6127,5,0,-1},{6128,5,1,-1}, +{6131,5,0,-1},{6132,5,1,-1},{6136,5,0,-1},{6137,5,1,-1},{6140,5,0,-1}, +{6141,5,1,-1},{6144,5,0,-1},{6145,5,1,-1},{6149,5,0,-1},{6150,5,1,-1}, +{6153,5,0,-1},{6154,5,1,-1},{6157,5,0,-1},{6158,5,1,-1},{6162,5,0,-1}, +{6163,5,1,-1},{6096,5,0,-1},{6166,5,0,-1},{6167,5,1,-1},{6170,5,0,-1}, +{6171,5,1,-1},{6175,5,0,-1},{6176,5,1,-1},{6109,5,1,-1},{6179,5,0,-1}, +{6180,5,1,-1},{6183,5,0,-1},{6184,5,1,-1},{6188,5,0,-1},{6189,5,1,-1}, +{6192,5,0,-1},{6193,5,1,-1},{6196,5,0,-1},{6197,5,1,-1},{6201,5,0,-1}, +{6202,5,1,-1},{6205,5,0,-1},{6206,5,1,-1},{6209,5,0,-1},{6210,5,1,-1}, +{6214,5,0,-1},{6215,5,1,-1},{6218,5,0,-1},{6219,5,1,-1},{6222,5,0,-1}, +{6223,5,1,-1},{6227,5,0,-1},{6228,5,1,-1},{6231,5,0,-1},{6232,5,1,-1}, +{6235,5,0,-1},{6236,5,1,-1},{6240,5,0,-1},{6241,5,1,-1},{6244,5,0,-1}, +{6245,5,1,-1},{6248,5,0,-1},{6249,5,1,-1},{6253,5,0,-1},{6254,5,1,-1}, +{6257,5,0,-1},{6258,5,1,-1},{6261,5,0,-1},{6262,5,1,-1},{6266,5,0,-1}, +{6267,5,1,-1},{6200,5,0,-1},{6270,5,0,-1},{6271,5,1,-1},{6274,5,0,-1}, +{6275,5,1,-1},{6279,5,0,-1},{6280,5,1,-1},{6213,5,1,-1},{6283,5,0,-1}, +{6284,5,1,-1},{6287,5,0,-1},{6288,5,1,-1},{6292,5,0,-1},{6293,5,1,-1}, +{6296,5,0,-1},{6297,5,1,-1},{6300,5,0,-1},{6301,5,1,-1},{6305,5,0,-1}, +{6306,5,1,-1},{6309,5,0,-1},{6310,5,1,-1},{6313,5,0,-1},{6314,5,1,-1}, +{6318,5,0,-1},{6319,5,1,-1},{6322,5,0,-1},{6323,5,1,-1},{6326,5,0,-1}, +{6327,5,1,-1},{6331,5,0,-1},{6332,5,1,-1},{6335,5,0,-1},{6336,5,1,-1}, +{6339,5,0,-1},{6340,5,1,-1},{6344,5,0,-1},{6345,5,1,-1},{6348,5,0,-1}, +{6349,5,1,-1},{6352,5,0,-1},{6353,5,1,-1},{6357,5,0,-1},{6358,5,1,-1}, +{6361,5,0,-1},{6362,5,1,-1},{6365,5,0,-1},{6366,5,1,-1},{6370,5,0,-1}, +{6371,5,1,-1},{6304,5,0,-1},{6374,5,0,-1},{6375,5,1,-1},{6378,5,0,-1}, +{6379,5,1,-1},{6383,5,0,-1},{6384,5,1,-1},{6317,5,1,-1},{6387,5,0,-1}, +{6388,5,1,-1},{6391,5,0,-1},{6392,5,1,-1},{6396,5,0,-1},{6397,5,1,-1}, +{6400,5,0,-1},{6401,5,1,-1},{6404,5,0,-1},{6405,5,1,-1},{6409,5,0,-1}, +{6410,5,1,-1},{6413,5,0,-1},{6414,5,1,-1},{6417,5,0,-1},{6418,5,1,-1}, +{6422,5,0,-1},{6423,5,1,-1},{6426,5,0,-1},{6427,5,1,-1},{6430,5,0,-1}, +{6431,5,1,-1},{6435,5,0,-1},{6436,5,1,-1},{6439,5,0,-1},{6440,5,1,-1}, +{6443,5,0,-1},{6444,5,1,-1},{6448,5,0,-1},{6449,5,1,-1},{6452,5,0,-1}, +{6453,5,1,-1},{6456,5,0,-1},{6457,5,1,-1},{6461,5,0,-1},{6462,5,1,-1}, +{6465,5,0,-1},{6466,5,1,-1},{6469,5,0,-1},{6470,5,1,-1},{6474,5,0,-1}, +{6475,5,1,-1},{6408,5,0,-1},{6478,5,0,-1},{6479,5,1,-1},{6482,5,0,-1}, +{6483,5,1,-1},{6487,5,0,-1},{6488,5,1,-1},{6421,5,1,-1},{6491,5,0,-1}, +{6492,5,1,-1},{6495,5,0,-1},{6496,5,1,-1},{6500,5,0,-1},{6501,5,1,-1}, +{6504,5,0,-1},{6505,5,1,-1},{6508,5,0,-1},{6509,5,1,-1},{6513,5,0,-1}, +{6514,5,1,-1},{6517,5,0,-1},{6518,5,1,-1},{6521,5,0,-1},{6522,5,1,-1}, +{6526,5,0,-1},{6527,5,1,-1},{6530,5,0,-1},{6531,5,1,-1},{6534,5,0,-1}, +{6535,5,1,-1},{6539,5,0,-1},{6540,5,1,-1},{6543,5,0,-1},{6544,5,1,-1}, +{6547,5,0,-1},{6548,5,1,-1},{6552,5,0,-1},{6553,5,1,-1},{6556,5,0,-1}, +{6557,5,1,-1},{6560,5,0,-1},{6561,5,1,-1},{6565,5,0,-1},{6566,5,1,-1}, +{6569,5,0,-1},{6570,5,1,-1},{6573,5,0,-1},{6574,5,1,-1},{6578,5,0,-1}, +{6579,5,1,-1},{6512,5,0,-1},{6582,5,0,-1},{6583,5,1,-1},{6586,5,0,-1}, +{6587,5,1,-1},{6591,5,0,-1},{6592,5,1,-1},{6525,5,1,-1},{6595,5,0,-1}, +{6596,5,1,-1},{6599,5,0,-1},{6600,5,1,-1},{6604,5,0,-1},{6605,5,1,-1}, +{6608,5,0,-1},{6609,5,1,-1},{6612,5,0,-1},{6613,5,1,-1},{6617,5,0,-1}, +{6618,5,1,-1},{6621,5,0,-1},{6622,5,1,-1},{6625,5,0,-1},{6626,5,1,-1}, +{6630,5,0,-1},{6631,5,1,-1},{6634,5,0,-1},{6635,5,1,-1},{6638,5,0,-1}, +{6639,5,1,-1},{6643,5,0,-1},{6644,5,1,-1},{6647,5,0,-1},{6648,5,1,-1}, +{6651,5,0,-1},{6652,5,1,-1},{6656,5,0,-1},{6657,5,1,-1},{6660,5,0,-1}, +{6661,5,1,-1},{6664,5,0,-1},{6665,5,1,-1},{6669,5,0,-1},{6670,5,1,-1}, +{6673,5,0,-1},{6674,5,1,-1},{6677,5,0,-1},{6678,5,1,-1},{6682,5,0,-1}, +{6683,5,1,-1},{6616,5,0,-1},{6686,5,0,-1},{6687,5,1,-1},{6690,5,0,-1}, +{6691,5,1,-1},{6695,5,0,-1},{6696,5,1,-1},{6629,5,1,-1},{6699,5,0,-1}, +{6700,5,1,-1},{6703,5,0,-1},{6704,5,1,-1},{6708,5,0,-1},{6709,5,1,-1}, +{6712,5,0,-1},{6713,5,1,-1},{6716,5,0,-1},{6717,5,1,-1},{6721,5,0,-1}, +{6722,5,1,-1},{6725,5,0,-1},{6726,5,1,-1},{6729,5,0,-1},{6730,5,1,-1}, +{6734,5,0,-1},{6735,5,1,-1},{6738,5,0,-1},{6739,5,1,-1},{6742,5,0,-1}, +{6743,5,1,-1},{6747,5,0,-1},{6748,5,1,-1},{6751,5,0,-1},{6752,5,1,-1}, +{6755,5,0,-1},{6756,5,1,-1},{6760,5,0,-1},{6761,5,1,-1},{6764,5,0,-1}, +{6765,5,1,-1},{6768,5,0,-1},{6769,5,1,-1},{6773,5,0,-1},{6774,5,1,-1}, +{6777,5,0,-1},{6778,5,1,-1},{6781,5,0,-1},{6782,5,1,-1},{6786,5,0,-1}, +{6787,5,1,-1},{6720,5,0,-1},{6790,5,0,-1},{6791,5,1,-1},{6794,5,0,-1}, +{6795,5,1,-1},{6799,5,0,-1},{6800,5,1,-1},{6733,5,1,-1},{6803,5,0,-1}, +{6804,5,1,-1},{6807,5,0,-1},{6808,5,1,-1},{6812,5,0,-1},{6813,5,1,-1}, +{6816,5,0,-1},{6817,5,1,-1},{6820,5,0,-1},{6821,5,1,-1},{6825,5,0,-1}, +{6826,5,1,-1},{6829,5,0,-1},{6830,5,1,-1},{6833,5,0,-1},{6834,5,1,-1}, +{6838,5,0,-1},{6839,5,1,-1},{6842,5,0,-1},{6843,5,1,-1},{6846,5,0,-1}, +{6847,5,1,-1},{6851,5,0,-1},{6852,5,1,-1},{6855,5,0,-1},{6856,5,1,-1}, +{6859,5,0,-1},{6860,5,1,-1},{6864,5,0,-1},{6865,5,1,-1},{6868,5,0,-1}, +{6869,5,1,-1},{6872,5,0,-1},{6873,5,1,-1},{6877,5,0,-1},{6878,5,1,-1}, +{6881,5,0,-1},{6882,5,1,-1},{6885,5,0,-1},{6886,5,1,-1},{6890,5,0,-1}, +{6891,5,1,-1},{6824,5,0,-1},{6894,5,0,-1},{6895,5,1,-1},{6898,5,0,-1}, +{6899,5,1,-1},{6903,5,0,-1},{6904,5,1,-1},{6837,5,1,-1},{6907,5,0,-1}, +{6908,5,1,-1},{6911,5,0,-1},{6912,5,1,-1},{6916,5,0,-1},{6917,5,1,-1}, +{6920,5,0,-1},{6921,5,1,-1},{6924,5,0,-1},{6925,5,1,-1},{6929,5,0,-1}, +{6930,5,1,-1},{6933,5,0,-1},{6934,5,1,-1},{6937,5,0,-1},{6938,5,1,-1}, +{6942,5,0,-1},{6943,5,1,-1},{6946,5,0,-1},{6947,5,1,-1},{6950,5,0,-1}, +{6951,5,1,-1},{6955,5,0,-1},{6956,5,1,-1},{6959,5,0,-1},{6960,5,1,-1}, +{6963,5,0,-1},{6964,5,1,-1},{6968,5,0,-1},{6969,5,1,-1},{6972,5,0,-1}, +{6973,5,1,-1},{6976,5,0,-1},{6977,5,1,-1},{6981,5,0,-1},{6982,5,1,-1}, +{6985,5,0,-1},{6986,5,1,-1},{6989,5,0,-1},{6990,5,1,-1},{6994,5,0,-1}, +{6995,5,1,-1},{6928,5,0,-1},{6998,5,0,-1},{6999,5,1,-1},{7002,5,0,-1}, +{7003,5,1,-1},{7007,5,0,-1},{7008,5,1,-1},{6941,5,1,-1},{7011,5,0,-1}, +{7012,5,1,-1},{7015,5,0,-1},{7016,5,1,-1},{7020,5,0,-1},{7021,5,1,-1}, +{7024,5,0,-1},{7025,5,1,-1},{7028,5,0,-1},{7029,5,1,-1},{7033,5,0,-1}, +{7034,5,1,-1},{7037,5,0,-1},{7038,5,1,-1},{7041,5,0,-1},{7042,5,1,-1}, +{7046,5,0,-1},{7047,5,1,-1},{7050,5,0,-1},{7051,5,1,-1},{7054,5,0,-1}, +{7055,5,1,-1},{7059,5,0,-1},{7060,5,1,-1},{7063,5,0,-1},{7064,5,1,-1}, +{7067,5,0,-1},{7068,5,1,-1},{7072,5,0,-1},{7073,5,1,-1},{7076,5,0,-1}, +{7077,5,1,-1},{7080,5,0,-1},{7081,5,1,-1},{7085,5,0,-1},{7086,5,1,-1}, +{7089,5,0,-1},{7090,5,1,-1},{7093,5,0,-1},{7094,5,1,-1},{7098,5,0,-1}, +{7099,5,1,-1},{7032,5,0,-1},{7102,5,0,-1},{7103,5,1,-1},{7106,5,0,-1}, +{7107,5,1,-1},{7111,5,0,-1},{7112,5,1,-1},{7045,5,1,-1},{7115,5,0,-1}, +{7116,5,1,-1},{7119,5,0,-1},{7120,5,1,-1},{7124,5,0,-1},{7125,5,1,-1}, +{7128,5,0,-1},{7129,5,1,-1},{7132,5,0,-1},{7133,5,1,-1},{7137,5,0,-1}, +{7138,5,1,-1},{7141,5,0,-1},{7142,5,1,-1},{7145,5,0,-1},{7146,5,1,-1}, +{7150,5,0,-1},{7151,5,1,-1},{7154,5,0,-1},{7155,5,1,-1},{7158,5,0,-1}, +{7159,5,1,-1},{7163,5,0,-1},{7164,5,1,-1},{7167,5,0,-1},{7168,5,1,-1}, +{7171,5,0,-1},{7172,5,1,-1},{7176,5,0,-1},{7177,5,1,-1},{7180,5,0,-1}, +{7181,5,1,-1},{7184,5,0,-1},{7185,5,1,-1},{7189,5,0,-1},{7190,5,1,-1}, +{7193,5,0,-1},{7194,5,1,-1},{7197,5,0,-1},{7198,5,1,-1},{7202,5,0,-1}, +{7203,5,1,-1},{7136,5,0,-1},{7206,5,0,-1},{7207,5,1,-1},{7210,5,0,-1}, +{7211,5,1,-1},{7215,5,0,-1},{7216,5,1,-1},{7149,5,1,-1},{7219,5,0,-1}, +{7220,5,1,-1},{7223,5,0,-1},{7224,5,1,-1},{7228,5,0,-1},{7229,5,1,-1}, +{7232,5,0,-1},{7233,5,1,-1},{7236,5,0,-1},{7237,5,1,-1},{7241,5,0,-1}, +{7242,5,1,-1},{7245,5,0,-1},{7246,5,1,-1},{7249,5,0,-1},{7250,5,1,-1}, +{7254,5,0,-1},{7255,5,1,-1},{7258,5,0,-1},{7259,5,1,-1},{7262,5,0,-1}, +{7263,5,1,-1},{7267,5,0,-1},{7268,5,1,-1},{7271,5,0,-1},{7272,5,1,-1}, +{7275,5,0,-1},{7276,5,1,-1},{7280,5,0,-1},{7281,5,1,-1},{7284,5,0,-1}, +{7285,5,1,-1},{7288,5,0,-1},{7289,5,1,-1},{7293,5,0,-1},{7294,5,1,-1}, +{7297,5,0,-1},{7298,5,1,-1},{7301,5,0,-1},{7302,5,1,-1},{7306,5,0,-1}, +{7307,5,1,-1},{7240,5,0,-1},{7310,5,0,-1},{7311,5,1,-1},{7314,5,0,-1}, +{7315,5,1,-1},{7319,5,0,-1},{7320,5,1,-1},{7253,5,1,-1},{7323,5,0,-1}, +{7324,5,1,-1},{7327,5,0,-1},{7328,5,1,-1},{7332,5,0,-1},{7333,5,1,-1}, +{7336,5,0,-1},{7337,5,1,-1},{7340,5,0,-1},{7341,5,1,-1},{7345,5,0,-1}, +{7346,5,1,-1},{7349,5,0,-1},{7350,5,1,-1},{7353,5,0,-1},{7354,5,1,-1}, +{7358,5,0,-1},{7359,5,1,-1},{7362,5,0,-1},{7363,5,1,-1},{7366,5,0,-1}, +{7367,5,1,-1},{7371,5,0,-1},{7372,5,1,-1},{7375,5,0,-1},{7376,5,1,-1}, +{7379,5,0,-1},{7380,5,1,-1},{7384,5,0,-1},{7385,5,1,-1},{7388,5,0,-1}, +{7389,5,1,-1},{7392,5,0,-1},{7393,5,1,-1},{7397,5,0,-1},{7398,5,1,-1}, +{7401,5,0,-1},{7402,5,1,-1},{7405,5,0,-1},{7406,5,1,-1},{7410,5,0,-1}, +{7411,5,1,-1},{7344,5,0,-1},{7414,5,0,-1},{7415,5,1,-1},{7418,5,0,-1}, +{7419,5,1,-1},{7423,5,0,-1},{7424,5,1,-1},{7357,5,1,-1},{7427,5,0,-1}, +{7428,5,1,-1},{7431,5,0,-1},{7432,5,1,-1},{7436,5,0,-1},{7437,5,1,-1}, +{7440,5,0,-1},{7441,5,1,-1},{7444,5,0,-1},{7445,5,1,-1},{7449,5,0,-1}, +{7450,5,1,-1},{7453,5,0,-1},{7454,5,1,-1},{7457,5,0,-1},{7458,5,1,-1}, +{7462,5,0,-1},{7463,5,1,-1},{7466,5,0,-1},{7467,5,1,-1},{7470,5,0,-1}, +{7471,5,1,-1},{7475,5,0,-1},{7476,5,1,-1},{7479,5,0,-1},{7480,5,1,-1}, +{7483,5,0,-1},{7484,5,1,-1},{7488,5,0,-1},{7489,5,1,-1},{7492,5,0,-1}, +{7493,5,1,-1},{7496,5,0,-1},{7497,5,1,-1},{7501,5,0,-1},{7502,5,1,-1}, +{7505,5,0,-1},{7506,5,1,-1},{7509,5,0,-1},{7510,5,1,-1},{7514,5,0,-1}, +{7515,5,1,-1},{7448,5,0,-1},{7518,5,0,-1},{7519,5,1,-1},{7522,5,0,-1}, +{7523,5,1,-1},{7527,5,0,-1},{7528,5,1,-1},{7461,5,1,-1},{7531,5,0,-1}, +{7532,5,1,-1},{7535,5,0,-1},{7536,5,1,-1},{7540,5,0,-1},{7541,5,1,-1}, +{7544,5,0,-1},{7545,5,1,-1},{7548,5,0,-1},{7549,5,1,-1},{7553,5,0,-1}, +{7554,5,1,-1},{7557,5,0,-1},{7558,5,1,-1},{7561,5,0,-1},{7562,5,1,-1}, +{7566,5,0,-1},{7567,5,1,-1},{7570,5,0,-1},{7571,5,1,-1},{7574,5,0,-1}, +{7575,5,1,-1},{7579,5,0,-1},{7580,5,1,-1},{7583,5,0,-1},{7584,5,1,-1}, +{7587,5,0,-1},{7588,5,1,-1},{7592,5,0,-1},{7593,5,1,-1},{7596,5,0,-1}, +{7597,5,1,-1},{7600,5,0,-1},{7601,5,1,-1},{7605,5,0,-1},{7606,5,1,-1}, +{7609,5,0,-1},{7610,5,1,-1},{7613,5,0,-1},{7614,5,1,-1},{7618,5,0,-1}, +{7619,5,1,-1},{7552,5,0,-1},{7622,5,0,-1},{7623,5,1,-1},{7626,5,0,-1}, +{7627,5,1,-1},{7631,5,0,-1},{7632,5,1,-1},{7565,5,1,-1},{7635,5,0,-1}, +{7636,5,1,-1},{7639,5,0,-1},{7640,5,1,-1},{7644,5,0,-1},{7645,5,1,-1}, +{7648,5,0,-1},{7649,5,1,-1},{7652,5,0,-1},{7653,5,1,-1},{7657,5,0,-1}, +{7658,5,1,-1},{7661,5,0,-1},{7662,5,1,-1},{7665,5,0,-1},{7666,5,1,-1}, +{7670,5,0,-1},{7671,5,1,-1},{7674,5,0,-1},{7675,5,1,-1},{7678,5,0,-1}, +{7679,5,1,-1},{7683,5,0,-1},{7684,5,1,-1},{7687,5,0,-1},{7688,5,1,-1}, +{7691,5,0,-1},{7692,5,1,-1},{7696,5,0,-1},{7697,5,1,-1},{7700,5,0,-1}, +{7701,5,1,-1},{7704,5,0,-1},{7705,5,1,-1},{7709,5,0,-1},{7710,5,1,-1}, +{7713,5,0,-1},{7714,5,1,-1},{7717,5,0,-1},{7718,5,1,-1},{7722,5,0,-1}, +{7723,5,1,-1},{7656,5,0,-1},{7726,5,0,-1},{7727,5,1,-1},{7730,5,0,-1}, +{7731,5,1,-1},{7735,5,0,-1},{7736,5,1,-1},{7669,5,1,-1},{7739,5,0,-1}, +{7740,5,1,-1},{7743,5,0,-1},{7744,5,1,-1},{7748,5,0,-1},{7749,5,1,-1}, +{7752,5,0,-1},{7753,5,1,-1},{7756,5,0,-1},{7757,5,1,-1},{7761,5,0,-1}, +{7762,5,1,-1},{7765,5,0,-1},{7766,5,1,-1},{7769,5,0,-1},{7770,5,1,-1}, +{7774,5,0,-1},{7775,5,1,-1},{7778,5,0,-1},{7779,5,1,-1},{7782,5,0,-1}, +{7783,5,1,-1},{7787,5,0,-1},{7788,5,1,-1},{7791,5,0,-1},{7792,5,1,-1}, +{7795,5,0,-1},{7796,5,1,-1},{7800,5,0,-1},{7801,5,1,-1},{7804,5,0,-1}, +{7805,5,1,-1},{7808,5,0,-1},{7809,5,1,-1},{7813,5,0,-1},{7814,5,1,-1}, +{7817,5,0,-1},{7818,5,1,-1},{7821,5,0,-1},{7822,5,1,-1},{7826,5,0,-1}, +{7827,5,1,-1},{7760,5,0,-1},{7830,5,0,-1},{7831,5,1,-1},{7834,5,0,-1}, +{7835,5,1,-1},{7839,5,0,-1},{7840,5,1,-1},{7773,5,1,-1},{7843,5,0,-1}, +{7844,5,1,-1},{7847,5,0,-1},{7848,5,1,-1},{7852,5,0,-1},{7853,5,1,-1}, +{7856,5,0,-1},{7857,5,1,-1},{7860,5,0,-1},{7861,5,1,-1},{7865,5,0,-1}, +{7866,5,1,-1},{7869,5,0,-1},{7870,5,1,-1},{7873,5,0,-1},{7874,5,1,-1}, +{7878,5,0,-1},{7879,5,1,-1},{7882,5,0,-1},{7883,5,1,-1},{7886,5,0,-1}, +{7887,5,1,-1},{7891,5,0,-1},{7892,5,1,-1},{7895,5,0,-1},{7896,5,1,-1}, +{7899,5,0,-1},{7900,5,1,-1},{7904,5,0,-1},{7905,5,1,-1},{7908,5,0,-1}, +{7909,5,1,-1},{7912,5,0,-1},{7913,5,1,-1},{7917,5,0,-1},{7918,5,1,-1}, +{7921,5,0,-1},{7922,5,1,-1},{7925,5,0,-1},{7926,5,1,-1},{7930,5,0,-1}, +{7931,5,1,-1},{7864,5,0,-1},{7934,5,0,-1},{7935,5,1,-1},{7938,5,0,-1}, +{7939,5,1,-1},{7943,5,0,-1},{7944,5,1,-1},{7877,5,1,-1},{7947,5,0,-1}, +{7948,5,1,-1},{7951,5,0,-1},{7952,5,1,-1},{7956,5,0,-1},{7957,5,1,-1}, +{7960,5,0,-1},{7961,5,1,-1},{7964,5,0,-1},{7965,5,1,-1},{7969,5,0,-1}, +{7970,5,1,-1},{7973,5,0,-1},{7974,5,1,-1},{7977,5,0,-1},{7978,5,1,-1}, +{7982,5,0,-1},{7983,5,1,-1},{7986,5,0,-1},{7987,5,1,-1},{7990,5,0,-1}, +{7991,5,1,-1},{7995,5,0,-1},{7996,5,1,-1},{7999,5,0,-1},{8000,5,1,-1}, +{8003,5,0,-1},{8004,5,1,-1},{8008,5,0,-1},{8009,5,1,-1},{8012,5,0,-1}, +{8013,5,1,-1},{8016,5,0,-1},{8017,5,1,-1},{8021,5,0,-1},{8022,5,1,-1}, +{8025,5,0,-1},{8026,5,1,-1},{8029,5,0,-1},{8030,5,1,-1},{8034,5,0,-1}, +{8035,5,1,-1},{7968,5,0,-1},{8038,5,0,-1},{8039,5,1,-1},{8042,5,0,-1}, +{8043,5,1,-1},{8047,5,0,-1},{8048,5,1,-1},{7981,5,1,-1},{8051,5,0,-1}, +{8052,5,1,-1},{8055,5,0,-1},{8056,5,1,-1},{8060,5,0,-1},{8061,5,1,-1}, +{8064,5,0,-1},{8065,5,1,-1},{8068,5,0,-1},{8069,5,1,-1},{8073,5,0,-1}, +{8074,5,1,-1},{8077,5,0,-1},{8078,5,1,-1},{8081,5,0,-1},{8082,5,1,-1}, +{8086,5,0,-1},{8087,5,1,-1},{8090,5,0,-1},{8091,5,1,-1},{8094,5,0,-1}, +{8095,5,1,-1},{8099,5,0,-1},{8100,5,1,-1},{8103,5,0,-1},{8104,5,1,-1}, +{8107,5,0,-1},{8108,5,1,-1},{8112,5,0,-1},{8113,5,1,-1},{8116,5,0,-1}, +{8117,5,1,-1},{8120,5,0,-1},{8121,5,1,-1},{8125,5,0,-1},{8126,5,1,-1}, +{8129,5,0,-1},{8130,5,1,-1},{8133,5,0,-1},{8134,5,1,-1},{8138,5,0,-1}, +{8139,5,1,-1},{8072,5,0,-1},{8142,5,0,-1},{8143,5,1,-1},{8146,5,0,-1}, +{8147,5,1,-1},{8151,5,0,-1},{8152,5,1,-1},{8085,5,1,-1},{8155,5,0,-1}, +{8156,5,1,-1},{8159,5,0,-1},{8160,5,1,-1},{8164,5,0,-1},{8165,5,1,-1}, +{8168,5,0,-1},{8169,5,1,-1},{8172,5,0,-1},{8173,5,1,-1},{8177,5,0,-1}, +{8178,5,1,-1},{8181,5,0,-1},{8182,5,1,-1},{8185,5,0,-1},{8186,5,1,-1}, +{8190,5,0,-1},{8191,5,1,-1},{8194,5,0,-1},{8195,5,1,-1},{8198,5,0,-1}, +{8199,5,1,-1},{8203,5,0,-1},{8204,5,1,-1},{8207,5,0,-1},{8208,5,1,-1}, +{8211,5,0,-1},{8212,5,1,-1},{8216,5,0,-1},{8217,5,1,-1},{8220,5,0,-1}, +{8221,5,1,-1},{8224,5,0,-1},{8225,5,1,-1},{8229,5,0,-1},{8230,5,1,-1}, +{8233,5,0,-1},{8234,5,1,-1},{8237,5,0,-1},{8238,5,1,-1},{8242,5,0,-1}, +{8243,5,1,-1},{8176,5,0,-1},{8246,5,0,-1},{8247,5,1,-1},{8250,5,0,-1}, +{8251,5,1,-1},{8255,5,0,-1},{8256,5,1,-1},{8189,5,1,-1},{8259,5,0,-1}, +{8260,5,1,-1},{8263,5,0,-1},{8264,5,1,-1},{8268,5,0,-1},{8269,5,1,-1}, +{8272,5,0,-1},{8273,5,1,-1},{8276,5,0,-1},{8277,5,1,-1},{8281,5,0,-1}, +{8282,5,1,-1},{8285,5,0,-1},{8286,5,1,-1},{8289,5,0,-1},{8290,5,1,-1}, +{8294,5,0,-1},{8295,5,1,-1},{8298,5,0,-1},{8299,5,1,-1},{8302,5,0,-1}, +{8303,5,1,-1},{8307,5,0,-1},{8308,5,1,-1},{8311,5,0,-1},{8312,5,1,-1}, +{8315,5,0,-1},{8316,5,1,-1},{8320,5,0,-1},{8321,5,1,-1},{8324,5,0,-1}, +{8325,5,1,-1},{8328,5,0,-1},{8329,5,1,-1},{8333,5,0,-1},{8334,5,1,-1}, +{8337,5,0,-1},{8338,5,1,-1},{8341,5,0,-1},{8342,5,1,-1},{8346,5,0,-1}, +{8347,5,1,-1},{8280,5,0,-1},{8350,5,0,-1},{8351,5,1,-1},{8354,5,0,-1}, +{8355,5,1,-1},{8359,5,0,-1},{8360,5,1,-1},{8293,5,1,-1},{8363,5,0,-1}, +{8364,5,1,-1},{8367,5,0,-1},{8368,5,1,-1},{8372,5,0,-1},{8373,5,1,-1}, +{8376,5,0,-1},{8377,5,1,-1},{8380,5,0,-1},{8381,5,1,-1},{8385,5,0,-1}, +{8386,5,1,-1},{8389,5,0,-1},{8390,5,1,-1},{8393,5,0,-1},{8394,5,1,-1}, +{8398,5,0,-1},{8399,5,1,-1},{8402,5,0,-1},{8403,5,1,-1},{8406,5,0,-1}, +{8407,5,1,-1},{8411,5,0,-1},{8412,5,1,-1},{8415,5,0,-1},{8416,5,1,-1}, +{8419,5,0,-1},{8420,5,1,-1},{8424,5,0,-1},{8425,5,1,-1},{8428,5,0,-1}, +{8429,5,1,-1},{8432,5,0,-1},{8433,5,1,-1},{8437,5,0,-1},{8438,5,1,-1}, +{8441,5,0,-1},{8442,5,1,-1},{8445,5,0,-1},{8446,5,1,-1},{8450,5,0,-1}, +{8451,5,1,-1},{8384,5,0,-1},{8454,5,0,-1},{8455,5,1,-1},{8458,5,0,-1}, +{8459,5,1,-1},{8463,5,0,-1},{8464,5,1,-1},{8397,5,1,-1},{8467,5,0,-1}, +{8468,5,1,-1},{8471,5,0,-1},{8472,5,1,-1},{8476,5,0,-1},{8477,5,1,-1}, +{8480,5,0,-1},{8481,5,1,-1},{8484,5,0,-1},{8485,5,1,-1},{8489,5,0,-1}, +{8490,5,1,-1},{8493,5,0,-1},{8494,5,1,-1},{8497,5,0,-1},{8498,5,1,-1}, +{8502,5,0,-1},{8503,5,1,-1},{8506,5,0,-1},{8507,5,1,-1},{8510,5,0,-1}, +{8511,5,1,-1},{8515,5,0,-1},{8516,5,1,-1},{8519,5,0,-1},{8520,5,1,-1}, +{8523,5,0,-1},{8524,5,1,-1},{8528,5,0,-1},{8529,5,1,-1},{8532,5,0,-1}, +{8533,5,1,-1},{8536,5,0,-1},{8537,5,1,-1},{8541,5,0,-1},{8545,5,0,-1}, +{8549,5,0,-1},{8554,5,0,-1},{8488,5,0,-1},{8558,5,0,-1},{8562,5,0,-1}, +{8567,5,0,-1},{8501,5,1,-1},{8571,5,0,-1},{8575,5,0,-1},{8580,5,0,-1}, +{8584,5,0,-1},{8585,5,1,-1},{8588,5,0,-1},{8589,5,1,-1},{8597,5,0,-1}, +{8601,5,0,-1},{8606,5,0,-1},{8610,5,0,-1},{8614,5,0,-1},{8619,5,0,-1}, +{8623,5,0,-1},{8627,5,0,-1},{8632,5,0,-1},{8636,5,0,-1},{8640,5,0,-1}, +{8641,5,1,-1},{8649,5,0,-1},{8653,5,0,-1},{8658,5,0,-1},{8662,5,0,-1}}; + +/* Frame number data sampled from measurement.c:lchan_meas_check_compute() + * Call was made between to phones on half rate channels TS6, SS0 and SS1 */ +struct fn_sample test_fn_tch_h_ts_6_ss0_ss1[] = { +{8112,6,0,-1},{8120,6,0,-1},{8129,6,0,-1},{8138,6,0,-1},{8146,6,0,-1}, +{8155,6,0,-1},{8164,6,0,-1},{8098,6,0,-1},{8172,6,0,-1},{8181,6,0,-1}, +{8190,6,0,-1},{8198,6,0,-1},{8207,6,0,-1},{8211,6,0,-1},{8216,6,0,-1}, +{8220,6,0,-1},{8224,6,0,-1},{8229,6,0,-1},{8233,6,0,-1},{8237,6,0,-1}, +{8242,6,0,-1},{8246,6,0,-1},{8250,6,0,-1},{8255,6,0,-1},{8259,6,0,-1}, +{8263,6,0,-1},{8268,6,0,-1},{8202,6,0,-1},{8272,6,0,-1},{8276,6,0,-1}, +{8281,6,0,-1},{8285,6,0,-1},{8289,6,0,-1},{8294,6,0,-1},{8298,6,0,-1}, +{8302,6,0,-1},{8307,6,0,-1},{8311,6,0,-1},{8315,6,0,-1},{8320,6,0,-1}, +{8324,6,0,-1},{8328,6,0,-1},{8333,6,0,-1},{8337,6,0,-1},{8341,6,0,-1}, +{8346,6,0,-1},{8350,6,0,-1},{8354,6,0,-1},{8355,6,1,-1},{8359,6,0,-1}, +{8363,6,0,-1},{8364,6,1,-1},{8367,6,0,-1},{8372,6,0,-1},{8373,6,1,-1}, +{8306,6,0,-1},{8376,6,0,-1},{8380,6,0,-1},{8381,6,1,-1},{8385,6,0,-1}, +{8389,6,0,-1},{8390,6,1,-1},{8393,6,0,-1},{8398,6,0,-1},{8399,6,1,-1}, +{8402,6,0,-1},{8406,6,0,-1},{8407,6,1,-1},{8411,6,0,-1},{8412,6,1,-1}, +{8415,6,0,-1},{8416,6,1,-1},{8419,6,0,-1},{8420,6,1,-1},{8424,6,0,-1}, +{8425,6,1,-1},{8428,6,0,-1},{8432,6,0,-1},{8433,6,1,-1},{8437,6,0,-1}, +{8441,6,0,-1},{8442,6,1,-1},{8445,6,0,-1},{8450,6,0,-1},{8451,6,1,-1}, +{8454,6,0,-1},{8458,6,0,-1},{8459,6,1,-1},{8463,6,0,-1},{8467,6,0,-1}, +{8468,6,1,-1},{8471,6,0,-1},{8476,6,0,-1},{8477,6,1,-1},{8410,6,0,-1}, +{8480,6,0,-1},{8484,6,0,-1},{8485,6,1,-1},{8489,6,0,-1},{8490,6,1,-1}, +{8423,6,1,-1},{8493,6,0,-1},{8494,6,1,-1},{8497,6,0,-1},{8498,6,1,-1}, +{8502,6,0,-1},{8503,6,1,-1},{8506,6,0,-1},{8507,6,1,-1},{8510,6,0,-1}, +{8511,6,1,-1},{8515,6,0,-1},{8519,6,0,-1},{8520,6,1,-1},{8523,6,0,-1}, +{8524,6,1,-1},{8528,6,0,-1},{8529,6,1,-1},{8532,6,0,-1},{8533,6,1,-1}, +{8536,6,0,-1},{8537,6,1,-1},{8541,6,0,-1},{8542,6,1,-1},{8545,6,0,-1}, +{8546,6,1,-1},{8549,6,0,-1},{8550,6,1,-1},{8554,6,0,-1},{8555,6,1,-1}, +{8558,6,0,-1},{8559,6,1,-1},{8562,6,0,-1},{8563,6,1,-1},{8567,6,0,-1}, +{8568,6,1,-1},{8571,6,0,-1},{8572,6,1,-1},{8575,6,0,-1},{8576,6,1,-1}, +{8580,6,0,-1},{8581,6,1,-1},{8514,6,0,-1},{8584,6,0,-1},{8585,6,1,-1}, +{8588,6,0,-1},{8589,6,1,-1},{8593,6,0,-1},{8594,6,1,-1},{8527,6,1,-1}, +{8597,6,0,-1},{8598,6,1,-1},{8601,6,0,-1},{8602,6,1,-1},{8606,6,0,-1}, +{8607,6,1,-1},{8610,6,0,-1},{8611,6,1,-1},{8614,6,0,-1},{8615,6,1,-1}, +{8619,6,0,-1},{8620,6,1,-1},{8623,6,0,-1},{8624,6,1,-1},{8627,6,0,-1}, +{8628,6,1,-1},{8632,6,0,-1},{8633,6,1,-1},{8636,6,0,-1},{8637,6,1,-1}, +{8640,6,0,-1},{8641,6,1,-1},{8645,6,0,-1},{8646,6,1,-1},{8649,6,0,-1}, +{8650,6,1,-1},{8653,6,0,-1},{8654,6,1,-1},{8658,6,0,-1},{8659,6,1,-1}, +{8662,6,0,-1},{8663,6,1,-1},{8666,6,0,-1},{8667,6,1,-1},{8671,6,0,-1}, +{8672,6,1,-1},{8675,6,0,-1},{8676,6,1,-1},{8679,6,0,-1},{8680,6,1,-1}, +{8684,6,0,-1},{8685,6,1,-1},{8618,6,0,-1},{8688,6,0,-1},{8689,6,1,-1}, +{8692,6,0,-1},{8693,6,1,-1},{8697,6,0,-1},{8698,6,1,-1},{8631,6,1,-1}, +{8701,6,0,-1},{8702,6,1,-1},{8705,6,0,-1},{8706,6,1,-1},{8710,6,0,-1}, +{8711,6,1,-1},{8714,6,0,-1},{8715,6,1,-1},{8718,6,0,-1},{8719,6,1,-1}, +{8723,6,0,-1},{8724,6,1,-1},{8727,6,0,-1},{8728,6,1,-1},{8731,6,0,-1}, +{8732,6,1,-1},{8736,6,0,-1},{8737,6,1,-1},{8740,6,0,-1},{8741,6,1,-1}, +{8744,6,0,-1},{8745,6,1,-1},{8749,6,0,-1},{8750,6,1,-1},{8753,6,0,-1}, +{8754,6,1,-1},{8757,6,0,-1},{8758,6,1,-1},{8762,6,0,-1},{8763,6,1,-1}, +{8766,6,0,-1},{8770,6,0,-1},{8771,6,1,-1},{8775,6,0,-1},{8776,6,1,-1}, +{8779,6,0,-1},{8780,6,1,-1},{8783,6,0,-1},{8784,6,1,-1},{8788,6,0,-1}, +{8789,6,1,-1},{8722,6,0,-1},{8792,6,0,-1},{8793,6,1,-1},{8796,6,0,-1}, +{8797,6,1,-1},{8801,6,0,-1},{8802,6,1,-1},{8735,6,1,-1},{8805,6,0,-1}, +{8806,6,1,-1},{8810,6,1,-1},{8814,6,0,-1},{8815,6,1,-1},{8818,6,0,-1}, +{8822,6,0,-1},{8823,6,1,-1},{8827,6,0,-1},{8828,6,1,-1},{8831,6,0,-1}, +{8832,6,1,-1},{8835,6,0,-1},{8836,6,1,-1},{8840,6,0,-1},{8841,6,1,-1}, +{8844,6,0,-1},{8845,6,1,-1},{8848,6,0,-1},{8849,6,1,-1},{8853,6,0,-1}, +{8854,6,1,-1},{8857,6,0,-1},{8858,6,1,-1},{8861,6,0,-1},{8862,6,1,-1}, +{8866,6,0,-1},{8867,6,1,-1},{8870,6,0,-1},{8871,6,1,-1},{8874,6,0,-1}, +{8875,6,1,-1},{8879,6,0,-1},{8880,6,1,-1},{8883,6,0,-1},{8884,6,1,-1}, +{8887,6,0,-1},{8888,6,1,-1},{8892,6,0,-1},{8893,6,1,-1},{8826,6,0,-1}, +{8896,6,0,-1},{8897,6,1,-1},{8900,6,0,-1},{8901,6,1,-1},{8905,6,0,-1}, +{8906,6,1,-1},{8839,6,1,-1},{8909,6,0,-1},{8910,6,1,-1},{8913,6,0,-1}, +{8914,6,1,-1},{8918,6,0,-1},{8919,6,1,-1},{8922,6,0,-1},{8923,6,1,-1}, +{8926,6,0,-1},{8927,6,1,-1},{8931,6,0,-1},{8932,6,1,-1},{8935,6,0,-1}, +{8936,6,1,-1},{8939,6,0,-1},{8940,6,1,-1},{8944,6,0,-1},{8945,6,1,-1}, +{8948,6,0,-1},{8949,6,1,-1},{8952,6,0,-1},{8953,6,1,-1},{8957,6,0,-1}, +{8958,6,1,-1},{8961,6,0,-1},{8962,6,1,-1},{8965,6,0,-1},{8966,6,1,-1}, +{8970,6,0,-1},{8971,6,1,-1},{8974,6,0,-1},{8975,6,1,-1},{8978,6,0,-1}, +{8979,6,1,-1},{8983,6,0,-1},{8984,6,1,-1},{8987,6,0,-1},{8988,6,1,-1}, +{8991,6,0,-1},{8992,6,1,-1},{8996,6,0,-1},{8997,6,1,-1},{8930,6,0,-1}, +{9000,6,0,-1},{9001,6,1,-1},{9004,6,0,-1},{9005,6,1,-1},{9009,6,0,-1}, +{9010,6,1,-1},{8943,6,1,-1},{9013,6,0,-1},{9014,6,1,-1},{9017,6,0,-1}, +{9018,6,1,-1},{9022,6,0,-1},{9023,6,1,-1},{9026,6,0,-1},{9027,6,1,-1}, +{9030,6,0,-1},{9031,6,1,-1},{9035,6,0,-1},{9036,6,1,-1},{9039,6,0,-1}, +{9040,6,1,-1},{9043,6,0,-1},{9044,6,1,-1},{9048,6,0,-1},{9049,6,1,-1}, +{9052,6,0,-1},{9053,6,1,-1},{9056,6,0,-1},{9057,6,1,-1},{9061,6,0,-1}, +{9062,6,1,-1},{9065,6,0,-1},{9066,6,1,-1},{9069,6,0,-1},{9070,6,1,-1}, +{9074,6,0,-1},{9075,6,1,-1},{9078,6,0,-1},{9079,6,1,-1},{9082,6,0,-1}, +{9083,6,1,-1},{9087,6,0,-1},{9088,6,1,-1},{9091,6,0,-1},{9092,6,1,-1}, +{9095,6,0,-1},{9096,6,1,-1},{9100,6,0,-1},{9101,6,1,-1},{9034,6,0,-1}, +{9104,6,0,-1},{9105,6,1,-1},{9108,6,0,-1},{9109,6,1,-1},{9113,6,0,-1}, +{9114,6,1,-1},{9047,6,1,-1},{9117,6,0,-1},{9118,6,1,-1},{9121,6,0,-1}, +{9122,6,1,-1},{9126,6,0,-1},{9127,6,1,-1},{9130,6,0,-1},{9131,6,1,-1}, +{9134,6,0,-1},{9135,6,1,-1},{9139,6,0,-1},{9140,6,1,-1},{9143,6,0,-1}, +{9144,6,1,-1},{9147,6,0,-1},{9148,6,1,-1},{9152,6,0,-1},{9153,6,1,-1}, +{9156,6,0,-1},{9157,6,1,-1},{9160,6,0,-1},{9161,6,1,-1},{9165,6,0,-1}, +{9166,6,1,-1},{9169,6,0,-1},{9170,6,1,-1},{9173,6,0,-1},{9174,6,1,-1}, +{9178,6,0,-1},{9179,6,1,-1},{9182,6,0,-1},{9183,6,1,-1},{9186,6,0,-1}, +{9187,6,1,-1},{9191,6,0,-1},{9192,6,1,-1},{9195,6,0,-1},{9196,6,1,-1}, +{9199,6,0,-1},{9200,6,1,-1},{9204,6,0,-1},{9205,6,1,-1},{9138,6,0,-1}, +{9208,6,0,-1},{9209,6,1,-1},{9212,6,0,-1},{9213,6,1,-1},{9217,6,0,-1}, +{9218,6,1,-1},{9151,6,1,-1},{9221,6,0,-1},{9222,6,1,-1},{9225,6,0,-1}, +{9226,6,1,-1},{9230,6,0,-1},{9231,6,1,-1},{9234,6,0,-1},{9235,6,1,-1}, +{9238,6,0,-1},{9239,6,1,-1},{9243,6,0,-1},{9244,6,1,-1},{9247,6,0,-1}, +{9248,6,1,-1},{9251,6,0,-1},{9252,6,1,-1},{9256,6,0,-1},{9257,6,1,-1}, +{9260,6,0,-1},{9261,6,1,-1},{9264,6,0,-1},{9265,6,1,-1},{9269,6,0,-1}, +{9270,6,1,-1},{9273,6,0,-1},{9274,6,1,-1},{9277,6,0,-1},{9278,6,1,-1}, +{9282,6,0,-1},{9283,6,1,-1},{9286,6,0,-1},{9287,6,1,-1},{9290,6,0,-1}, +{9291,6,1,-1},{9295,6,0,-1},{9296,6,1,-1},{9299,6,0,-1},{9300,6,1,-1}, +{9303,6,0,-1},{9304,6,1,-1},{9308,6,0,-1},{9309,6,1,-1},{9242,6,0,-1}, +{9312,6,0,-1},{9313,6,1,-1},{9316,6,0,-1},{9317,6,1,-1},{9321,6,0,-1}, +{9322,6,1,-1},{9255,6,1,-1},{9325,6,0,-1},{9326,6,1,-1},{9329,6,0,-1}, +{9330,6,1,-1},{9334,6,0,-1},{9335,6,1,-1},{9338,6,0,-1},{9339,6,1,-1}, +{9342,6,0,-1},{9343,6,1,-1},{9347,6,0,-1},{9348,6,1,-1},{9351,6,0,-1}, +{9352,6,1,-1},{9355,6,0,-1},{9356,6,1,-1},{9360,6,0,-1},{9361,6,1,-1}, +{9364,6,0,-1},{9365,6,1,-1},{9368,6,0,-1},{9369,6,1,-1},{9373,6,0,-1}, +{9374,6,1,-1},{9377,6,0,-1},{9378,6,1,-1},{9381,6,0,-1},{9382,6,1,-1}, +{9386,6,0,-1},{9387,6,1,-1},{9390,6,0,-1},{9391,6,1,-1},{9394,6,0,-1}, +{9395,6,1,-1},{9399,6,0,-1},{9400,6,1,-1},{9403,6,0,-1},{9404,6,1,-1}, +{9407,6,0,-1},{9408,6,1,-1},{9412,6,0,-1},{9413,6,1,-1},{9346,6,0,-1}, +{9416,6,0,-1},{9417,6,1,-1},{9420,6,0,-1},{9421,6,1,-1},{9425,6,0,-1}, +{9426,6,1,-1},{9359,6,1,-1},{9429,6,0,-1},{9430,6,1,-1},{9433,6,0,-1}, +{9434,6,1,-1},{9438,6,0,-1},{9439,6,1,-1},{9442,6,0,-1},{9443,6,1,-1}, +{9446,6,0,-1},{9447,6,1,-1},{9451,6,0,-1},{9452,6,1,-1},{9455,6,0,-1}, +{9456,6,1,-1},{9459,6,0,-1},{9460,6,1,-1},{9464,6,0,-1},{9465,6,1,-1}, +{9468,6,0,-1},{9469,6,1,-1},{9472,6,0,-1},{9473,6,1,-1},{9477,6,0,-1}, +{9478,6,1,-1},{9481,6,0,-1},{9482,6,1,-1},{9485,6,0,-1},{9486,6,1,-1}, +{9490,6,0,-1},{9491,6,1,-1},{9494,6,0,-1},{9495,6,1,-1},{9498,6,0,-1}, +{9499,6,1,-1},{9503,6,0,-1},{9504,6,1,-1},{9507,6,0,-1},{9508,6,1,-1}, +{9511,6,0,-1},{9512,6,1,-1},{9516,6,0,-1},{9517,6,1,-1},{9450,6,0,-1}, +{9520,6,0,-1},{9521,6,1,-1},{9524,6,0,-1},{9525,6,1,-1},{9529,6,0,-1}, +{9530,6,1,-1},{9463,6,1,-1},{9533,6,0,-1},{9534,6,1,-1},{9537,6,0,-1}, +{9538,6,1,-1},{9542,6,0,-1},{9543,6,1,-1},{9546,6,0,-1},{9547,6,1,-1}, +{9550,6,0,-1},{9551,6,1,-1},{9555,6,0,-1},{9556,6,1,-1},{9559,6,0,-1}, +{9560,6,1,-1},{9563,6,0,-1},{9564,6,1,-1},{9568,6,0,-1},{9569,6,1,-1}, +{9572,6,0,-1},{9573,6,1,-1},{9576,6,0,-1},{9577,6,1,-1},{9581,6,0,-1}, +{9582,6,1,-1},{9585,6,0,-1},{9586,6,1,-1},{9589,6,0,-1},{9590,6,1,-1}, +{9594,6,0,-1},{9595,6,1,-1},{9598,6,0,-1},{9599,6,1,-1},{9602,6,0,-1}, +{9603,6,1,-1},{9607,6,0,-1},{9608,6,1,-1},{9611,6,0,-1},{9612,6,1,-1}, +{9615,6,0,-1},{9616,6,1,-1},{9620,6,0,-1},{9621,6,1,-1},{9554,6,0,-1}, +{9624,6,0,-1},{9625,6,1,-1},{9628,6,0,-1},{9629,6,1,-1},{9633,6,0,-1}, +{9634,6,1,-1},{9567,6,1,-1},{9637,6,0,-1},{9638,6,1,-1},{9641,6,0,-1}, +{9642,6,1,-1},{9646,6,0,-1},{9647,6,1,-1},{9650,6,0,-1},{9651,6,1,-1}, +{9654,6,0,-1},{9655,6,1,-1},{9659,6,0,-1},{9660,6,1,-1},{9663,6,0,-1}, +{9664,6,1,-1},{9667,6,0,-1},{9668,6,1,-1},{9672,6,0,-1},{9673,6,1,-1}, +{9676,6,0,-1},{9677,6,1,-1},{9680,6,0,-1},{9681,6,1,-1},{9685,6,0,-1}, +{9686,6,1,-1},{9689,6,0,-1},{9690,6,1,-1},{9693,6,0,-1},{9694,6,1,-1}, +{9698,6,0,-1},{9699,6,1,-1},{9702,6,0,-1},{9703,6,1,-1},{9706,6,0,-1}, +{9707,6,1,-1},{9711,6,0,-1},{9712,6,1,-1},{9715,6,0,-1},{9716,6,1,-1}, +{9719,6,0,-1},{9720,6,1,-1},{9724,6,0,-1},{9725,6,1,-1},{9658,6,0,-1}, +{9728,6,0,-1},{9729,6,1,-1},{9732,6,0,-1},{9733,6,1,-1},{9737,6,0,-1}, +{9738,6,1,-1},{9671,6,1,-1},{9741,6,0,-1},{9742,6,1,-1},{9745,6,0,-1}, +{9746,6,1,-1},{9750,6,0,-1},{9751,6,1,-1},{9754,6,0,-1},{9755,6,1,-1}, +{9758,6,0,-1},{9759,6,1,-1},{9763,6,0,-1},{9764,6,1,-1},{9767,6,0,-1}, +{9768,6,1,-1},{9771,6,0,-1},{9772,6,1,-1},{9776,6,0,-1},{9777,6,1,-1}, +{9780,6,0,-1},{9781,6,1,-1},{9784,6,0,-1},{9785,6,1,-1},{9789,6,0,-1}, +{9790,6,1,-1},{9793,6,0,-1},{9794,6,1,-1},{9797,6,0,-1},{9798,6,1,-1}, +{9802,6,0,-1},{9803,6,1,-1},{9806,6,0,-1},{9807,6,1,-1},{9810,6,0,-1}, +{9811,6,1,-1},{9815,6,0,-1},{9816,6,1,-1},{9819,6,0,-1},{9820,6,1,-1}, +{9823,6,0,-1},{9824,6,1,-1},{9828,6,0,-1},{9829,6,1,-1},{9762,6,0,-1}, +{9832,6,0,-1},{9833,6,1,-1},{9836,6,0,-1},{9837,6,1,-1},{9841,6,0,-1}, +{9842,6,1,-1},{9775,6,1,-1},{9845,6,0,-1},{9846,6,1,-1},{9849,6,0,-1}, +{9850,6,1,-1},{9854,6,0,-1},{9855,6,1,-1},{9858,6,0,-1},{9859,6,1,-1}, +{9862,6,0,-1},{9863,6,1,-1},{9867,6,0,-1},{9868,6,1,-1},{9871,6,0,-1}, +{9872,6,1,-1},{9875,6,0,-1},{9876,6,1,-1},{9880,6,0,-1},{9881,6,1,-1}, +{9884,6,0,-1},{9885,6,1,-1},{9888,6,0,-1},{9889,6,1,-1},{9893,6,0,-1}, +{9894,6,1,-1},{9897,6,0,-1},{9898,6,1,-1},{9901,6,0,-1},{9902,6,1,-1}, +{9906,6,0,-1},{9907,6,1,-1},{9910,6,0,-1},{9911,6,1,-1},{9914,6,0,-1}, +{9915,6,1,-1},{9919,6,0,-1},{9920,6,1,-1},{9923,6,0,-1},{9924,6,1,-1}, +{9927,6,0,-1},{9928,6,1,-1},{9932,6,0,-1},{9933,6,1,-1},{9866,6,0,-1}, +{9936,6,0,-1},{9937,6,1,-1},{9940,6,0,-1},{9941,6,1,-1},{9945,6,0,-1}, +{9946,6,1,-1},{9879,6,1,-1},{9949,6,0,-1},{9950,6,1,-1},{9953,6,0,-1}, +{9954,6,1,-1},{9958,6,0,-1},{9959,6,1,-1},{9962,6,0,-1},{9963,6,1,-1}, +{9966,6,0,-1},{9967,6,1,-1},{9971,6,0,-1},{9972,6,1,-1},{9975,6,0,-1}, +{9976,6,1,-1},{9979,6,0,-1},{9980,6,1,-1},{9984,6,0,-1},{9985,6,1,-1}, +{9988,6,0,-1},{9989,6,1,-1},{9992,6,0,-1},{9993,6,1,-1},{9997,6,0,-1}, +{9998,6,1,-1},{10001,6,0,-1},{10002,6,1,-1},{10005,6,0,-1},{10006,6,1,-1}, +{10010,6,0,-1},{10011,6,1,-1},{10014,6,0,-1},{10015,6,1,-1},{10018,6,0,-1}, +{10019,6,1,-1},{10023,6,0,-1},{10024,6,1,-1},{10027,6,0,-1},{10028,6,1,-1}, +{10031,6,0,-1},{10032,6,1,-1},{10036,6,0,-1},{10037,6,1,-1},{9970,6,0,-1}, +{10040,6,0,-1},{10041,6,1,-1},{10044,6,0,-1},{10045,6,1,-1},{10049,6,0,-1}, +{10050,6,1,-1},{9983,6,1,-1},{10053,6,0,-1},{10054,6,1,-1},{10057,6,0,-1}, +{10058,6,1,-1},{10062,6,0,-1},{10063,6,1,-1},{10066,6,0,-1},{10067,6,1,-1}, +{10070,6,0,-1},{10071,6,1,-1},{10075,6,0,-1},{10076,6,1,-1},{10079,6,0,-1}, +{10080,6,1,-1},{10083,6,0,-1},{10084,6,1,-1},{10088,6,0,-1},{10089,6,1,-1}, +{10092,6,0,-1},{10093,6,1,-1},{10096,6,0,-1},{10097,6,1,-1},{10101,6,0,-1}, +{10102,6,1,-1},{10105,6,0,-1},{10106,6,1,-1},{10109,6,0,-1},{10110,6,1,-1}, +{10114,6,0,-1},{10115,6,1,-1},{10118,6,0,-1},{10119,6,1,-1},{10122,6,0,-1}, +{10123,6,1,-1},{10127,6,0,-1},{10128,6,1,-1},{10131,6,0,-1},{10132,6,1,-1}, +{10135,6,0,-1},{10136,6,1,-1},{10140,6,0,-1},{10141,6,1,-1},{10074,6,0,-1}, +{10144,6,0,-1},{10145,6,1,-1},{10148,6,0,-1},{10149,6,1,-1},{10153,6,0,-1}, +{10154,6,1,-1},{10087,6,1,-1},{10157,6,0,-1},{10158,6,1,-1},{10161,6,0,-1}, +{10162,6,1,-1},{10166,6,0,-1},{10167,6,1,-1},{10170,6,0,-1},{10171,6,1,-1}, +{10174,6,0,-1},{10175,6,1,-1},{10179,6,0,-1},{10180,6,1,-1},{10183,6,0,-1}, +{10184,6,1,-1},{10187,6,0,-1},{10188,6,1,-1},{10192,6,0,-1},{10193,6,1,-1}, +{10196,6,0,-1},{10197,6,1,-1},{10200,6,0,-1},{10201,6,1,-1},{10205,6,0,-1}, +{10206,6,1,-1},{10209,6,0,-1},{10210,6,1,-1},{10213,6,0,-1},{10214,6,1,-1}, +{10218,6,0,-1},{10219,6,1,-1},{10222,6,0,-1},{10223,6,1,-1},{10226,6,0,-1}, +{10227,6,1,-1},{10231,6,0,-1},{10232,6,1,-1},{10235,6,0,-1},{10236,6,1,-1}, +{10239,6,0,-1},{10240,6,1,-1},{10244,6,0,-1},{10245,6,1,-1},{10178,6,0,-1}, +{10248,6,0,-1},{10249,6,1,-1},{10252,6,0,-1},{10253,6,1,-1},{10257,6,0,-1}, +{10258,6,1,-1},{10191,6,1,-1},{10261,6,0,-1},{10262,6,1,-1},{10265,6,0,-1}, +{10266,6,1,-1},{10270,6,0,-1},{10271,6,1,-1},{10274,6,0,-1},{10275,6,1,-1}, +{10278,6,0,-1},{10279,6,1,-1},{10283,6,0,-1},{10284,6,1,-1},{10287,6,0,-1}, +{10288,6,1,-1},{10291,6,0,-1},{10292,6,1,-1},{10296,6,0,-1},{10297,6,1,-1}, +{10300,6,0,-1},{10301,6,1,-1},{10304,6,0,-1},{10305,6,1,-1},{10309,6,0,-1}, +{10310,6,1,-1},{10313,6,0,-1},{10314,6,1,-1},{10317,6,0,-1},{10318,6,1,-1}, +{10322,6,0,-1},{10323,6,1,-1},{10326,6,0,-1},{10327,6,1,-1},{10330,6,0,-1}, +{10331,6,1,-1},{10335,6,0,-1},{10336,6,1,-1},{10339,6,0,-1},{10340,6,1,-1}, +{10343,6,0,-1},{10344,6,1,-1},{10348,6,0,-1},{10349,6,1,-1},{10282,6,0,-1}, +{10352,6,0,-1},{10353,6,1,-1},{10356,6,0,-1},{10357,6,1,-1},{10361,6,0,-1}, +{10362,6,1,-1},{10295,6,1,-1},{10365,6,0,-1},{10366,6,1,-1},{10369,6,0,-1}, +{10370,6,1,-1},{10374,6,0,-1},{10375,6,1,-1},{10378,6,0,-1},{10379,6,1,-1}, +{10382,6,0,-1},{10383,6,1,-1},{10387,6,0,-1},{10388,6,1,-1},{10391,6,0,-1}, +{10392,6,1,-1},{10395,6,0,-1},{10396,6,1,-1},{10400,6,0,-1},{10401,6,1,-1}, +{10404,6,0,-1},{10405,6,1,-1},{10408,6,0,-1},{10409,6,1,-1},{10413,6,0,-1}, +{10414,6,1,-1},{10417,6,0,-1},{10418,6,1,-1},{10421,6,0,-1},{10422,6,1,-1}, +{10426,6,0,-1},{10427,6,1,-1},{10430,6,0,-1},{10431,6,1,-1},{10434,6,0,-1}, +{10435,6,1,-1},{10439,6,0,-1},{10440,6,1,-1},{10443,6,0,-1},{10444,6,1,-1}, +{10447,6,0,-1},{10448,6,1,-1},{10452,6,0,-1},{10453,6,1,-1},{10386,6,0,-1}, +{10456,6,0,-1},{10457,6,1,-1},{10460,6,0,-1},{10461,6,1,-1},{10465,6,0,-1}, +{10466,6,1,-1},{10399,6,1,-1},{10469,6,0,-1},{10470,6,1,-1},{10473,6,0,-1}, +{10474,6,1,-1},{10478,6,0,-1},{10479,6,1,-1},{10482,6,0,-1},{10483,6,1,-1}, +{10486,6,0,-1},{10487,6,1,-1},{10491,6,0,-1},{10492,6,1,-1},{10495,6,0,-1}, +{10496,6,1,-1},{10499,6,0,-1},{10500,6,1,-1},{10504,6,0,-1},{10505,6,1,-1}, +{10508,6,0,-1},{10509,6,1,-1},{10512,6,0,-1},{10513,6,1,-1},{10517,6,0,-1}, +{10518,6,1,-1},{10521,6,0,-1},{10522,6,1,-1},{10525,6,0,-1},{10526,6,1,-1}, +{10530,6,0,-1},{10531,6,1,-1},{10534,6,0,-1},{10535,6,1,-1},{10538,6,0,-1}, +{10539,6,1,-1},{10543,6,0,-1},{10544,6,1,-1},{10547,6,0,-1},{10548,6,1,-1}, +{10551,6,0,-1},{10552,6,1,-1},{10556,6,0,-1},{10557,6,1,-1},{10490,6,0,-1}, +{10560,6,0,-1},{10561,6,1,-1},{10564,6,0,-1},{10565,6,1,-1},{10569,6,0,-1}, +{10570,6,1,-1},{10503,6,1,-1},{10573,6,0,-1},{10574,6,1,-1},{10577,6,0,-1}, +{10578,6,1,-1},{10582,6,0,-1},{10583,6,1,-1},{10586,6,0,-1},{10587,6,1,-1}, +{10590,6,0,-1},{10591,6,1,-1},{10595,6,0,-1},{10596,6,1,-1},{10599,6,0,-1}, +{10600,6,1,-1},{10603,6,0,-1},{10604,6,1,-1},{10608,6,0,-1},{10609,6,1,-1}, +{10612,6,0,-1},{10613,6,1,-1},{10616,6,0,-1},{10617,6,1,-1},{10621,6,0,-1}, +{10622,6,1,-1},{10625,6,0,-1},{10626,6,1,-1},{10629,6,0,-1},{10630,6,1,-1}, +{10634,6,0,-1},{10635,6,1,-1},{10638,6,0,-1},{10639,6,1,-1},{10642,6,0,-1}, +{10643,6,1,-1},{10647,6,0,-1},{10648,6,1,-1},{10651,6,0,-1},{10652,6,1,-1}, +{10655,6,0,-1},{10656,6,1,-1},{10660,6,0,-1},{10661,6,1,-1},{10594,6,0,-1}, +{10664,6,0,-1},{10665,6,1,-1},{10668,6,0,-1},{10669,6,1,-1},{10673,6,0,-1}, +{10674,6,1,-1},{10607,6,1,-1},{10677,6,0,-1},{10678,6,1,-1},{10681,6,0,-1}, +{10682,6,1,-1},{10686,6,0,-1},{10687,6,1,-1},{10690,6,0,-1},{10691,6,1,-1}, +{10694,6,0,-1},{10695,6,1,-1},{10699,6,0,-1},{10700,6,1,-1},{10703,6,0,-1}, +{10704,6,1,-1},{10707,6,0,-1},{10708,6,1,-1},{10712,6,0,-1},{10713,6,1,-1}, +{10716,6,0,-1},{10717,6,1,-1},{10720,6,0,-1},{10721,6,1,-1},{10725,6,0,-1}, +{10726,6,1,-1},{10729,6,0,-1},{10730,6,1,-1},{10733,6,0,-1},{10734,6,1,-1}, +{10738,6,0,-1},{10739,6,1,-1},{10742,6,0,-1},{10743,6,1,-1},{10746,6,0,-1}, +{10747,6,1,-1},{10751,6,0,-1},{10752,6,1,-1},{10755,6,0,-1},{10756,6,1,-1}, +{10759,6,0,-1},{10760,6,1,-1},{10764,6,0,-1},{10765,6,1,-1},{10698,6,0,-1}, +{10768,6,0,-1},{10769,6,1,-1},{10772,6,0,-1},{10773,6,1,-1},{10777,6,0,-1}, +{10778,6,1,-1},{10711,6,1,-1},{10781,6,0,-1},{10782,6,1,-1},{10785,6,0,-1}, +{10786,6,1,-1},{10790,6,0,-1},{10791,6,1,-1},{10794,6,0,-1},{10795,6,1,-1}, +{10798,6,0,-1},{10799,6,1,-1},{10803,6,0,-1},{10804,6,1,-1},{10807,6,0,-1}, +{10808,6,1,-1},{10811,6,0,-1},{10812,6,1,-1},{10816,6,0,-1},{10817,6,1,-1}, +{10820,6,0,-1},{10821,6,1,-1},{10824,6,0,-1},{10825,6,1,-1},{10829,6,0,-1}, +{10830,6,1,-1},{10833,6,0,-1},{10834,6,1,-1},{10837,6,0,-1},{10838,6,1,-1}, +{10842,6,0,-1},{10843,6,1,-1},{10846,6,0,-1},{10847,6,1,-1},{10850,6,0,-1}, +{10851,6,1,-1},{10855,6,0,-1},{10856,6,1,-1},{10859,6,0,-1},{10860,6,1,-1}, +{10863,6,0,-1},{10864,6,1,-1},{10868,6,0,-1},{10869,6,1,-1},{10802,6,0,-1}, +{10872,6,0,-1},{10873,6,1,-1},{10876,6,0,-1},{10877,6,1,-1},{10881,6,0,-1}, +{10882,6,1,-1},{10815,6,1,-1},{10885,6,0,-1},{10886,6,1,-1},{10889,6,0,-1}, +{10890,6,1,-1},{10894,6,0,-1},{10895,6,1,-1},{10898,6,0,-1},{10899,6,1,-1}, +{10902,6,0,-1},{10903,6,1,-1},{10907,6,0,-1},{10908,6,1,-1},{10911,6,0,-1}, +{10912,6,1,-1},{10915,6,0,-1},{10916,6,1,-1},{10920,6,0,-1},{10921,6,1,-1}, +{10924,6,0,-1},{10925,6,1,-1},{10928,6,0,-1},{10929,6,1,-1},{10933,6,0,-1}, +{10934,6,1,-1},{10937,6,0,-1},{10938,6,1,-1},{10941,6,0,-1},{10942,6,1,-1}, +{10946,6,0,-1},{10947,6,1,-1},{10950,6,0,-1},{10951,6,1,-1},{10954,6,0,-1}, +{10955,6,1,-1},{10959,6,0,-1},{10960,6,1,-1},{10963,6,0,-1},{10964,6,1,-1}, +{10967,6,0,-1},{10968,6,1,-1},{10972,6,0,-1},{10973,6,1,-1},{10906,6,0,-1}, +{10976,6,0,-1},{10977,6,1,-1},{10980,6,0,-1},{10981,6,1,-1},{10985,6,0,-1}, +{10986,6,1,-1},{10919,6,1,-1},{10989,6,0,-1},{10990,6,1,-1},{10993,6,0,-1}, +{10994,6,1,-1},{10998,6,0,-1},{10999,6,1,-1},{11002,6,0,-1},{11003,6,1,-1}, +{11006,6,0,-1},{11007,6,1,-1},{11011,6,0,-1},{11012,6,1,-1},{11015,6,0,-1}, +{11016,6,1,-1},{11019,6,0,-1},{11020,6,1,-1},{11024,6,0,-1},{11025,6,1,-1}, +{11028,6,0,-1},{11029,6,1,-1},{11032,6,0,-1},{11033,6,1,-1},{11037,6,0,-1}, +{11038,6,1,-1},{11041,6,0,-1},{11042,6,1,-1},{11045,6,0,-1},{11046,6,1,-1}, +{11050,6,0,-1},{11051,6,1,-1},{11054,6,0,-1},{11055,6,1,-1},{11058,6,0,-1}, +{11059,6,1,-1},{11063,6,0,-1},{11064,6,1,-1},{11067,6,0,-1},{11068,6,1,-1}, +{11071,6,0,-1},{11072,6,1,-1},{11076,6,0,-1},{11077,6,1,-1},{11010,6,0,-1}, +{11080,6,0,-1},{11081,6,1,-1},{11084,6,0,-1},{11085,6,1,-1},{11089,6,0,-1}, +{11090,6,1,-1},{11023,6,1,-1},{11093,6,0,-1},{11094,6,1,-1},{11097,6,0,-1}, +{11098,6,1,-1},{11102,6,0,-1},{11103,6,1,-1},{11106,6,0,-1},{11107,6,1,-1}, +{11110,6,0,-1},{11111,6,1,-1},{11115,6,0,-1},{11116,6,1,-1},{11119,6,0,-1}, +{11120,6,1,-1},{11123,6,0,-1},{11124,6,1,-1},{11128,6,0,-1},{11129,6,1,-1}, +{11132,6,0,-1},{11133,6,1,-1},{11136,6,0,-1},{11137,6,1,-1},{11141,6,0,-1}, +{11142,6,1,-1},{11145,6,0,-1},{11146,6,1,-1},{11149,6,0,-1},{11150,6,1,-1}, +{11154,6,0,-1},{11155,6,1,-1},{11158,6,0,-1},{11159,6,1,-1},{11162,6,0,-1}, +{11163,6,1,-1},{11167,6,0,-1},{11168,6,1,-1},{11171,6,0,-1},{11172,6,1,-1}, +{11175,6,0,-1},{11176,6,1,-1},{11180,6,0,-1},{11181,6,1,-1},{11114,6,0,-1}, +{11184,6,0,-1},{11185,6,1,-1},{11188,6,0,-1},{11189,6,1,-1},{11193,6,0,-1}, +{11194,6,1,-1},{11127,6,1,-1},{11197,6,0,-1},{11198,6,1,-1},{11201,6,0,-1}, +{11202,6,1,-1},{11206,6,0,-1},{11207,6,1,-1},{11210,6,0,-1},{11211,6,1,-1}, +{11214,6,0,-1},{11215,6,1,-1},{11219,6,0,-1},{11220,6,1,-1},{11223,6,0,-1}, +{11224,6,1,-1},{11227,6,0,-1},{11228,6,1,-1},{11232,6,0,-1},{11233,6,1,-1}, +{11236,6,0,-1},{11237,6,1,-1},{11240,6,0,-1},{11241,6,1,-1},{11245,6,0,-1}, +{11246,6,1,-1},{11249,6,0,-1},{11250,6,1,-1},{11253,6,0,-1},{11254,6,1,-1}, +{11258,6,0,-1},{11259,6,1,-1},{11262,6,0,-1},{11263,6,1,-1},{11266,6,0,-1}, +{11267,6,1,-1},{11271,6,0,-1},{11272,6,1,-1},{11275,6,0,-1},{11276,6,1,-1}, +{11279,6,0,-1},{11280,6,1,-1},{11284,6,0,-1},{11285,6,1,-1},{11218,6,0,-1}, +{11288,6,0,-1},{11289,6,1,-1},{11292,6,0,-1},{11293,6,1,-1},{11297,6,0,-1}, +{11298,6,1,-1},{11231,6,1,-1},{11301,6,0,-1},{11302,6,1,-1},{11305,6,0,-1}, +{11306,6,1,-1},{11310,6,0,-1},{11311,6,1,-1},{11314,6,0,-1},{11315,6,1,-1}, +{11318,6,0,-1},{11319,6,1,-1},{11323,6,0,-1},{11324,6,1,-1},{11327,6,0,-1}, +{11328,6,1,-1},{11331,6,0,-1},{11332,6,1,-1},{11336,6,0,-1},{11337,6,1,-1}, +{11340,6,0,-1},{11341,6,1,-1},{11344,6,0,-1},{11345,6,1,-1},{11349,6,0,-1}, +{11350,6,1,-1},{11353,6,0,-1},{11354,6,1,-1},{11357,6,0,-1},{11358,6,1,-1}, +{11362,6,0,-1},{11363,6,1,-1},{11366,6,0,-1},{11367,6,1,-1},{11370,6,0,-1}, +{11371,6,1,-1},{11375,6,0,-1},{11376,6,1,-1},{11379,6,0,-1},{11380,6,1,-1}, +{11383,6,0,-1},{11384,6,1,-1},{11388,6,0,-1},{11389,6,1,-1},{11322,6,0,-1}, +{11392,6,0,-1},{11393,6,1,-1},{11396,6,0,-1},{11397,6,1,-1},{11401,6,0,-1}, +{11402,6,1,-1},{11335,6,1,-1},{11405,6,0,-1},{11406,6,1,-1},{11409,6,0,-1}, +{11410,6,1,-1},{11414,6,0,-1},{11415,6,1,-1},{11418,6,0,-1},{11419,6,1,-1}, +{11422,6,0,-1},{11423,6,1,-1},{11427,6,0,-1},{11428,6,1,-1},{11431,6,0,-1}, +{11432,6,1,-1},{11435,6,0,-1},{11436,6,1,-1},{11440,6,0,-1},{11441,6,1,-1}, +{11444,6,0,-1},{11445,6,1,-1},{11448,6,0,-1},{11449,6,1,-1},{11453,6,0,-1}, +{11454,6,1,-1},{11457,6,0,-1},{11458,6,1,-1},{11461,6,0,-1},{11462,6,1,-1}, +{11466,6,0,-1},{11467,6,1,-1},{11470,6,0,-1},{11471,6,1,-1},{11474,6,0,-1}, +{11475,6,1,-1},{11479,6,0,-1},{11480,6,1,-1},{11483,6,0,-1},{11484,6,1,-1}, +{11487,6,0,-1},{11488,6,1,-1},{11492,6,0,-1},{11493,6,1,-1},{11426,6,0,-1}, +{11496,6,0,-1},{11497,6,1,-1},{11500,6,0,-1},{11501,6,1,-1},{11505,6,0,-1}, +{11506,6,1,-1},{11439,6,1,-1},{11509,6,0,-1},{11510,6,1,-1},{11513,6,0,-1}, +{11514,6,1,-1},{11518,6,0,-1},{11519,6,1,-1},{11522,6,0,-1},{11523,6,1,-1}, +{11526,6,0,-1},{11527,6,1,-1},{11531,6,0,-1},{11532,6,1,-1},{11535,6,0,-1}, +{11536,6,1,-1},{11539,6,0,-1},{11540,6,1,-1},{11544,6,0,-1},{11545,6,1,-1}, +{11548,6,0,-1},{11549,6,1,-1},{11552,6,0,-1},{11553,6,1,-1},{11557,6,0,-1}, +{11558,6,1,-1},{11561,6,0,-1},{11562,6,1,-1},{11565,6,0,-1},{11566,6,1,-1}, +{11570,6,0,-1},{11571,6,1,-1},{11574,6,0,-1},{11575,6,1,-1},{11578,6,0,-1}, +{11579,6,1,-1},{11583,6,0,-1},{11584,6,1,-1},{11587,6,0,-1},{11588,6,1,-1}, +{11591,6,0,-1},{11592,6,1,-1},{11596,6,0,-1},{11597,6,1,-1},{11530,6,0,-1}, +{11600,6,0,-1},{11601,6,1,-1},{11604,6,0,-1},{11605,6,1,-1},{11609,6,0,-1}, +{11610,6,1,-1},{11543,6,1,-1},{11613,6,0,-1},{11614,6,1,-1},{11617,6,0,-1}, +{11622,6,0,-1},{11623,6,1,-1},{11626,6,0,-1},{11627,6,1,-1},{11630,6,0,-1}, +{11631,6,1,-1},{11635,6,0,-1},{11636,6,1,-1},{11639,6,0,-1},{11640,6,1,-1}, +{11643,6,0,-1},{11644,6,1,-1},{11648,6,0,-1},{11649,6,1,-1},{11652,6,0,-1}, +{11653,6,1,-1},{11656,6,0,-1},{11657,6,1,-1},{11661,6,0,-1},{11662,6,1,-1}, +{11665,6,0,-1},{11666,6,1,-1},{11674,6,0,-1},{11675,6,1,-1},{11678,6,0,-1}, +{11679,6,1,-1},{11682,6,0,-1},{11683,6,1,-1},{11687,6,0,-1},{11688,6,1,-1}, +{11691,6,0,-1},{11692,6,1,-1},{11695,6,0,-1},{11696,6,1,-1},{11700,6,0,-1}, +{11701,6,1,-1},{11704,6,0,-1},{11705,6,1,-1},{11708,6,0,-1},{11709,6,1,-1}, +{11713,6,0,-1},{11717,6,0,-1},{11726,6,0,-1},{11730,6,0,-1},{11734,6,0,-1}}; + +/* Frame number data sampled from measurement.c:lchan_meas_check_compute() + * Call was made between to phones on half rate channels TS7, SS0 and SS1 */ +struct fn_sample test_fn_tch_h_ts_7_ss0_ss1[] = { +{11752,7,0,-1},{11760,7,0,-1},{11769,7,0,-1},{11778,7,0,-1},{11786,7,0,-1}, +{11795,7,0,-1},{11799,7,0,-1},{11804,7,0,-1},{11738,7,0,-1},{11808,7,0,-1}, +{11812,7,0,-1},{11817,7,0,-1},{11821,7,0,-1},{11825,7,0,-1},{11830,7,0,-1}, +{11834,7,0,-1},{11838,7,0,-1},{11843,7,0,-1},{11847,7,0,-1},{11851,7,0,-1}, +{11856,7,0,-1},{11860,7,0,-1},{11864,7,0,-1},{11869,7,0,-1},{11873,7,0,-1}, +{11877,7,0,-1},{11882,7,0,-1},{11886,7,0,-1},{11890,7,0,-1},{11895,7,0,-1}, +{11899,7,0,-1},{11903,7,0,-1},{11908,7,0,-1},{11842,7,0,-1},{11912,7,0,-1}, +{11916,7,0,-1},{11921,7,0,-1},{11925,7,0,-1},{11929,7,0,-1},{11934,7,0,-1}, +{11938,7,0,-1},{11942,7,0,-1},{11947,7,0,-1},{11951,7,0,-1},{11955,7,0,-1}, +{11960,7,0,-1},{11964,7,0,-1},{11968,7,0,-1},{11973,7,0,-1},{11977,7,0,-1}, +{11981,7,0,-1},{11986,7,0,-1},{11990,7,0,-1},{11994,7,0,-1},{11999,7,0,-1}, +{12003,7,0,-1},{12007,7,0,-1},{12012,7,0,-1},{11946,7,0,-1},{12016,7,0,-1}, +{12020,7,0,-1},{12025,7,0,-1},{12026,7,1,-1},{12029,7,0,-1},{12033,7,0,-1}, +{12034,7,1,-1},{12038,7,0,-1},{12042,7,0,-1},{12046,7,0,-1},{12047,7,1,-1}, +{12051,7,0,-1},{12055,7,0,-1},{12056,7,1,-1},{12059,7,0,-1},{12064,7,0,-1}, +{12065,7,1,-1},{12068,7,0,-1},{12072,7,0,-1},{12073,7,1,-1},{12077,7,0,-1}, +{12081,7,0,-1},{12082,7,1,-1},{12085,7,0,-1},{12090,7,0,-1},{12091,7,1,-1}, +{12094,7,0,-1},{12098,7,0,-1},{12099,7,1,-1},{12103,7,0,-1},{12107,7,0,-1}, +{12108,7,1,-1},{12111,7,0,-1},{12116,7,0,-1},{12117,7,1,-1},{12050,7,0,-1}, +{12120,7,0,-1},{12124,7,0,-1},{12125,7,1,-1},{12129,7,0,-1},{12063,7,1,-1}, +{12133,7,0,-1},{12134,7,1,-1},{12137,7,0,-1},{12142,7,0,-1},{12143,7,1,-1}, +{12146,7,0,-1},{12150,7,0,-1},{12151,7,1,-1},{12155,7,0,-1},{12159,7,0,-1}, +{12160,7,1,-1},{12163,7,0,-1},{12168,7,0,-1},{12169,7,1,-1},{12172,7,0,-1}, +{12176,7,0,-1},{12177,7,1,-1},{12181,7,0,-1},{12185,7,0,-1},{12186,7,1,-1}, +{12189,7,0,-1},{12190,7,1,-1},{12194,7,0,-1},{12195,7,1,-1},{12198,7,0,-1}, +{12199,7,1,-1},{12202,7,0,-1},{12203,7,1,-1},{12207,7,0,-1},{12208,7,1,-1}, +{12211,7,0,-1},{12212,7,1,-1},{12215,7,0,-1},{12220,7,0,-1},{12221,7,1,-1}, +{12154,7,0,-1},{12224,7,0,-1},{12225,7,1,-1},{12228,7,0,-1},{12229,7,1,-1}, +{12233,7,0,-1},{12234,7,1,-1},{12167,7,1,-1},{12237,7,0,-1},{12238,7,1,-1}, +{12241,7,0,-1},{12242,7,1,-1},{12246,7,0,-1},{12247,7,1,-1},{12250,7,0,-1}, +{12251,7,1,-1},{12254,7,0,-1},{12255,7,1,-1},{12260,7,1,-1},{12263,7,0,-1}, +{12264,7,1,-1},{12267,7,0,-1},{12268,7,1,-1},{12272,7,0,-1},{12273,7,1,-1}, +{12276,7,0,-1},{12277,7,1,-1},{12280,7,0,-1},{12281,7,1,-1},{12285,7,0,-1}, +{12286,7,1,-1},{12289,7,0,-1},{12290,7,1,-1},{12293,7,0,-1},{12294,7,1,-1}, +{12298,7,0,-1},{12299,7,1,-1},{12302,7,0,-1},{12303,7,1,-1},{12306,7,0,-1}, +{12307,7,1,-1},{12311,7,0,-1},{12312,7,1,-1},{12315,7,0,-1},{12316,7,1,-1}, +{12319,7,0,-1},{12320,7,1,-1},{12324,7,0,-1},{12325,7,1,-1},{12258,7,0,-1}, +{12328,7,0,-1},{12329,7,1,-1},{12332,7,0,-1},{12333,7,1,-1},{12337,7,0,-1}, +{12338,7,1,-1},{12271,7,1,-1},{12341,7,0,-1},{12342,7,1,-1},{12345,7,0,-1}, +{12346,7,1,-1},{12350,7,0,-1},{12351,7,1,-1},{12354,7,0,-1},{12355,7,1,-1}, +{12358,7,0,-1},{12359,7,1,-1},{12363,7,0,-1},{12367,7,0,-1},{12368,7,1,-1}, +{12371,7,0,-1},{12372,7,1,-1},{12376,7,0,-1},{12377,7,1,-1},{12380,7,0,-1}, +{12381,7,1,-1},{12384,7,0,-1},{12385,7,1,-1},{12389,7,0,-1},{12390,7,1,-1}, +{12393,7,0,-1},{12394,7,1,-1},{12397,7,0,-1},{12398,7,1,-1},{12402,7,0,-1}, +{12403,7,1,-1},{12407,7,1,-1},{12410,7,0,-1},{12411,7,1,-1},{12415,7,0,-1}, +{12419,7,0,-1},{12420,7,1,-1},{12423,7,0,-1},{12424,7,1,-1},{12428,7,0,-1}, +{12429,7,1,-1},{12362,7,0,-1},{12432,7,0,-1},{12433,7,1,-1},{12436,7,0,-1}, +{12437,7,1,-1},{12441,7,0,-1},{12442,7,1,-1},{12375,7,1,-1},{12445,7,0,-1}, +{12446,7,1,-1},{12449,7,0,-1},{12450,7,1,-1},{12454,7,0,-1},{12455,7,1,-1}, +{12458,7,0,-1},{12459,7,1,-1},{12462,7,0,-1},{12463,7,1,-1},{12467,7,0,-1}, +{12468,7,1,-1},{12471,7,0,-1},{12472,7,1,-1},{12475,7,0,-1},{12476,7,1,-1}, +{12480,7,0,-1},{12481,7,1,-1},{12484,7,0,-1},{12485,7,1,-1},{12488,7,0,-1}, +{12489,7,1,-1},{12493,7,0,-1},{12494,7,1,-1},{12497,7,0,-1},{12498,7,1,-1}, +{12501,7,0,-1},{12502,7,1,-1},{12506,7,0,-1},{12507,7,1,-1},{12510,7,0,-1}, +{12511,7,1,-1},{12514,7,0,-1},{12515,7,1,-1},{12519,7,0,-1},{12520,7,1,-1}, +{12523,7,0,-1},{12524,7,1,-1},{12527,7,0,-1},{12528,7,1,-1},{12532,7,0,-1}, +{12533,7,1,-1},{12466,7,0,-1},{12536,7,0,-1},{12537,7,1,-1},{12540,7,0,-1}, +{12541,7,1,-1},{12545,7,0,-1},{12546,7,1,-1},{12479,7,1,-1},{12549,7,0,-1}, +{12550,7,1,-1},{12553,7,0,-1},{12554,7,1,-1},{12558,7,0,-1},{12559,7,1,-1}, +{12562,7,0,-1},{12563,7,1,-1},{12566,7,0,-1},{12567,7,1,-1},{12571,7,0,-1}, +{12572,7,1,-1},{12575,7,0,-1},{12576,7,1,-1},{12579,7,0,-1},{12580,7,1,-1}, +{12584,7,0,-1},{12585,7,1,-1},{12588,7,0,-1},{12589,7,1,-1},{12592,7,0,-1}, +{12593,7,1,-1},{12597,7,0,-1},{12598,7,1,-1},{12601,7,0,-1},{12602,7,1,-1}, +{12605,7,0,-1},{12606,7,1,-1},{12610,7,0,-1},{12611,7,1,-1},{12614,7,0,-1}, +{12615,7,1,-1},{12618,7,0,-1},{12619,7,1,-1},{12623,7,0,-1},{12624,7,1,-1}, +{12627,7,0,-1},{12628,7,1,-1},{12631,7,0,-1},{12632,7,1,-1},{12636,7,0,-1}, +{12637,7,1,-1},{12570,7,0,-1},{12640,7,0,-1},{12641,7,1,-1},{12644,7,0,-1}, +{12645,7,1,-1},{12649,7,0,-1},{12650,7,1,-1},{12583,7,1,-1},{12653,7,0,-1}, +{12654,7,1,-1},{12657,7,0,-1},{12658,7,1,-1},{12662,7,0,-1},{12663,7,1,-1}, +{12666,7,0,-1},{12667,7,1,-1},{12670,7,0,-1},{12671,7,1,-1},{12675,7,0,-1}, +{12676,7,1,-1},{12679,7,0,-1},{12680,7,1,-1},{12683,7,0,-1},{12684,7,1,-1}, +{12688,7,0,-1},{12689,7,1,-1},{12692,7,0,-1},{12693,7,1,-1},{12696,7,0,-1}, +{12697,7,1,-1},{12701,7,0,-1},{12702,7,1,-1},{12705,7,0,-1},{12706,7,1,-1}, +{12709,7,0,-1},{12710,7,1,-1},{12714,7,0,-1},{12715,7,1,-1},{12718,7,0,-1}, +{12719,7,1,-1},{12722,7,0,-1},{12723,7,1,-1},{12727,7,0,-1},{12728,7,1,-1}, +{12731,7,0,-1},{12732,7,1,-1},{12735,7,0,-1},{12736,7,1,-1},{12740,7,0,-1}, +{12741,7,1,-1},{12674,7,0,-1},{12744,7,0,-1},{12745,7,1,-1},{12748,7,0,-1}, +{12749,7,1,-1},{12753,7,0,-1},{12754,7,1,-1},{12687,7,1,-1},{12757,7,0,-1}, +{12758,7,1,-1},{12761,7,0,-1},{12762,7,1,-1},{12766,7,0,-1},{12767,7,1,-1}, +{12770,7,0,-1},{12771,7,1,-1},{12774,7,0,-1},{12775,7,1,-1},{12779,7,0,-1}, +{12780,7,1,-1},{12783,7,0,-1},{12784,7,1,-1},{12787,7,0,-1},{12788,7,1,-1}, +{12792,7,0,-1},{12793,7,1,-1},{12796,7,0,-1},{12797,7,1,-1},{12800,7,0,-1}, +{12801,7,1,-1},{12805,7,0,-1},{12806,7,1,-1},{12809,7,0,-1},{12810,7,1,-1}, +{12813,7,0,-1},{12814,7,1,-1},{12818,7,0,-1},{12819,7,1,-1},{12822,7,0,-1}, +{12823,7,1,-1},{12826,7,0,-1},{12827,7,1,-1},{12831,7,0,-1},{12832,7,1,-1}, +{12835,7,0,-1},{12836,7,1,-1},{12839,7,0,-1},{12840,7,1,-1},{12844,7,0,-1}, +{12845,7,1,-1},{12778,7,0,-1},{12848,7,0,-1},{12849,7,1,-1},{12852,7,0,-1}, +{12853,7,1,-1},{12857,7,0,-1},{12858,7,1,-1},{12791,7,1,-1},{12861,7,0,-1}, +{12862,7,1,-1},{12865,7,0,-1},{12866,7,1,-1},{12870,7,0,-1},{12871,7,1,-1}, +{12874,7,0,-1},{12875,7,1,-1},{12878,7,0,-1},{12879,7,1,-1},{12883,7,0,-1}, +{12884,7,1,-1},{12887,7,0,-1},{12888,7,1,-1},{12891,7,0,-1},{12892,7,1,-1}, +{12896,7,0,-1},{12897,7,1,-1},{12900,7,0,-1},{12901,7,1,-1},{12904,7,0,-1}, +{12905,7,1,-1},{12909,7,0,-1},{12910,7,1,-1},{12913,7,0,-1},{12914,7,1,-1}, +{12917,7,0,-1},{12918,7,1,-1},{12922,7,0,-1},{12923,7,1,-1},{12926,7,0,-1}, +{12927,7,1,-1},{12930,7,0,-1},{12931,7,1,-1},{12935,7,0,-1},{12936,7,1,-1}, +{12939,7,0,-1},{12940,7,1,-1},{12943,7,0,-1},{12944,7,1,-1},{12948,7,0,-1}, +{12949,7,1,-1},{12882,7,0,-1},{12952,7,0,-1},{12953,7,1,-1},{12956,7,0,-1}, +{12957,7,1,-1},{12961,7,0,-1},{12962,7,1,-1},{12895,7,1,-1},{12965,7,0,-1}, +{12966,7,1,-1},{12969,7,0,-1},{12970,7,1,-1},{12974,7,0,-1},{12975,7,1,-1}, +{12978,7,0,-1},{12979,7,1,-1},{12982,7,0,-1},{12983,7,1,-1},{12987,7,0,-1}, +{12988,7,1,-1},{12991,7,0,-1},{12992,7,1,-1},{12995,7,0,-1},{12996,7,1,-1}, +{13000,7,0,-1},{13001,7,1,-1},{13004,7,0,-1},{13005,7,1,-1},{13008,7,0,-1}, +{13009,7,1,-1},{13013,7,0,-1},{13014,7,1,-1},{13017,7,0,-1},{13018,7,1,-1}, +{13021,7,0,-1},{13022,7,1,-1},{13026,7,0,-1},{13027,7,1,-1},{13030,7,0,-1}, +{13031,7,1,-1},{13034,7,0,-1},{13035,7,1,-1},{13039,7,0,-1},{13040,7,1,-1}, +{13043,7,0,-1},{13044,7,1,-1},{13047,7,0,-1},{13048,7,1,-1},{13052,7,0,-1}, +{13053,7,1,-1},{12986,7,0,-1},{13056,7,0,-1},{13057,7,1,-1},{13060,7,0,-1}, +{13061,7,1,-1},{13065,7,0,-1},{13066,7,1,-1},{12999,7,1,-1},{13069,7,0,-1}, +{13070,7,1,-1},{13073,7,0,-1},{13074,7,1,-1},{13078,7,0,-1},{13079,7,1,-1}, +{13082,7,0,-1},{13083,7,1,-1},{13086,7,0,-1},{13087,7,1,-1},{13091,7,0,-1}, +{13092,7,1,-1},{13095,7,0,-1},{13096,7,1,-1},{13099,7,0,-1},{13100,7,1,-1}, +{13104,7,0,-1},{13105,7,1,-1},{13108,7,0,-1},{13109,7,1,-1},{13112,7,0,-1}, +{13113,7,1,-1},{13117,7,0,-1},{13118,7,1,-1},{13121,7,0,-1},{13122,7,1,-1}, +{13125,7,0,-1},{13126,7,1,-1},{13130,7,0,-1},{13131,7,1,-1},{13134,7,0,-1}, +{13135,7,1,-1},{13138,7,0,-1},{13139,7,1,-1},{13143,7,0,-1},{13144,7,1,-1}, +{13147,7,0,-1},{13148,7,1,-1},{13151,7,0,-1},{13152,7,1,-1},{13156,7,0,-1}, +{13157,7,1,-1},{13090,7,0,-1},{13160,7,0,-1},{13161,7,1,-1},{13164,7,0,-1}, +{13165,7,1,-1},{13169,7,0,-1},{13170,7,1,-1},{13103,7,1,-1},{13173,7,0,-1}, +{13174,7,1,-1},{13177,7,0,-1},{13178,7,1,-1},{13182,7,0,-1},{13183,7,1,-1}, +{13186,7,0,-1},{13187,7,1,-1},{13190,7,0,-1},{13191,7,1,-1},{13195,7,0,-1}, +{13196,7,1,-1},{13199,7,0,-1},{13200,7,1,-1},{13203,7,0,-1},{13204,7,1,-1}, +{13208,7,0,-1},{13209,7,1,-1},{13212,7,0,-1},{13213,7,1,-1},{13216,7,0,-1}, +{13217,7,1,-1},{13221,7,0,-1},{13222,7,1,-1},{13225,7,0,-1},{13226,7,1,-1}, +{13229,7,0,-1},{13230,7,1,-1},{13234,7,0,-1},{13235,7,1,-1},{13238,7,0,-1}, +{13239,7,1,-1},{13242,7,0,-1},{13243,7,1,-1},{13247,7,0,-1},{13248,7,1,-1}, +{13251,7,0,-1},{13252,7,1,-1},{13255,7,0,-1},{13256,7,1,-1},{13260,7,0,-1}, +{13261,7,1,-1},{13194,7,0,-1},{13264,7,0,-1},{13265,7,1,-1},{13268,7,0,-1}, +{13269,7,1,-1},{13273,7,0,-1},{13274,7,1,-1},{13207,7,1,-1},{13277,7,0,-1}, +{13278,7,1,-1},{13281,7,0,-1},{13282,7,1,-1},{13286,7,0,-1},{13287,7,1,-1}, +{13290,7,0,-1},{13291,7,1,-1},{13294,7,0,-1},{13295,7,1,-1},{13299,7,0,-1}, +{13300,7,1,-1},{13303,7,0,-1},{13304,7,1,-1},{13307,7,0,-1},{13308,7,1,-1}, +{13312,7,0,-1},{13313,7,1,-1},{13316,7,0,-1},{13317,7,1,-1},{13320,7,0,-1}, +{13321,7,1,-1},{13325,7,0,-1},{13326,7,1,-1},{13329,7,0,-1},{13330,7,1,-1}, +{13333,7,0,-1},{13334,7,1,-1},{13338,7,0,-1},{13339,7,1,-1},{13342,7,0,-1}, +{13343,7,1,-1},{13346,7,0,-1},{13347,7,1,-1},{13351,7,0,-1},{13352,7,1,-1}, +{13355,7,0,-1},{13356,7,1,-1},{13359,7,0,-1},{13360,7,1,-1},{13364,7,0,-1}, +{13365,7,1,-1},{13298,7,0,-1},{13368,7,0,-1},{13369,7,1,-1},{13372,7,0,-1}, +{13373,7,1,-1},{13377,7,0,-1},{13378,7,1,-1},{13311,7,1,-1},{13381,7,0,-1}, +{13382,7,1,-1},{13385,7,0,-1},{13386,7,1,-1},{13390,7,0,-1},{13391,7,1,-1}, +{13394,7,0,-1},{13395,7,1,-1},{13398,7,0,-1},{13399,7,1,-1},{13403,7,0,-1}, +{13404,7,1,-1},{13407,7,0,-1},{13408,7,1,-1},{13411,7,0,-1},{13412,7,1,-1}, +{13416,7,0,-1},{13417,7,1,-1},{13420,7,0,-1},{13421,7,1,-1},{13424,7,0,-1}, +{13425,7,1,-1},{13429,7,0,-1},{13430,7,1,-1},{13433,7,0,-1},{13434,7,1,-1}, +{13437,7,0,-1},{13438,7,1,-1},{13442,7,0,-1},{13443,7,1,-1},{13446,7,0,-1}, +{13447,7,1,-1},{13450,7,0,-1},{13451,7,1,-1},{13455,7,0,-1},{13456,7,1,-1}, +{13459,7,0,-1},{13460,7,1,-1},{13463,7,0,-1},{13464,7,1,-1},{13468,7,0,-1}, +{13469,7,1,-1},{13402,7,0,-1},{13472,7,0,-1},{13473,7,1,-1},{13476,7,0,-1}, +{13477,7,1,-1},{13481,7,0,-1},{13482,7,1,-1},{13415,7,1,-1},{13485,7,0,-1}, +{13486,7,1,-1},{13489,7,0,-1},{13490,7,1,-1},{13494,7,0,-1},{13495,7,1,-1}, +{13498,7,0,-1},{13499,7,1,-1},{13502,7,0,-1},{13503,7,1,-1},{13507,7,0,-1}, +{13508,7,1,-1},{13511,7,0,-1},{13512,7,1,-1},{13515,7,0,-1},{13516,7,1,-1}, +{13520,7,0,-1},{13521,7,1,-1},{13524,7,0,-1},{13525,7,1,-1},{13528,7,0,-1}, +{13529,7,1,-1},{13533,7,0,-1},{13534,7,1,-1},{13537,7,0,-1},{13538,7,1,-1}, +{13541,7,0,-1},{13542,7,1,-1},{13546,7,0,-1},{13547,7,1,-1},{13550,7,0,-1}, +{13551,7,1,-1},{13554,7,0,-1},{13555,7,1,-1},{13559,7,0,-1},{13560,7,1,-1}, +{13563,7,0,-1},{13564,7,1,-1},{13567,7,0,-1},{13568,7,1,-1},{13572,7,0,-1}, +{13573,7,1,-1},{13506,7,0,-1},{13576,7,0,-1},{13577,7,1,-1},{13580,7,0,-1}, +{13581,7,1,-1},{13585,7,0,-1},{13586,7,1,-1},{13519,7,1,-1},{13589,7,0,-1}, +{13590,7,1,-1},{13593,7,0,-1},{13594,7,1,-1},{13598,7,0,-1},{13599,7,1,-1}, +{13602,7,0,-1},{13603,7,1,-1},{13606,7,0,-1},{13607,7,1,-1},{13611,7,0,-1}, +{13612,7,1,-1},{13615,7,0,-1},{13616,7,1,-1},{13619,7,0,-1},{13620,7,1,-1}, +{13624,7,0,-1},{13625,7,1,-1},{13628,7,0,-1},{13629,7,1,-1},{13632,7,0,-1}, +{13633,7,1,-1},{13637,7,0,-1},{13638,7,1,-1},{13641,7,0,-1},{13642,7,1,-1}, +{13645,7,0,-1},{13646,7,1,-1},{13650,7,0,-1},{13651,7,1,-1},{13654,7,0,-1}, +{13655,7,1,-1},{13658,7,0,-1},{13659,7,1,-1},{13663,7,0,-1},{13664,7,1,-1}, +{13667,7,0,-1},{13668,7,1,-1},{13671,7,0,-1},{13672,7,1,-1},{13676,7,0,-1}, +{13677,7,1,-1},{13610,7,0,-1},{13680,7,0,-1},{13681,7,1,-1},{13684,7,0,-1}, +{13685,7,1,-1},{13689,7,0,-1},{13690,7,1,-1},{13623,7,1,-1},{13693,7,0,-1}, +{13694,7,1,-1},{13697,7,0,-1},{13698,7,1,-1},{13702,7,0,-1},{13703,7,1,-1}, +{13706,7,0,-1},{13707,7,1,-1},{13710,7,0,-1},{13711,7,1,-1},{13715,7,0,-1}, +{13716,7,1,-1},{13719,7,0,-1},{13720,7,1,-1},{13723,7,0,-1},{13724,7,1,-1}, +{13728,7,0,-1},{13729,7,1,-1},{13732,7,0,-1},{13733,7,1,-1},{13736,7,0,-1}, +{13737,7,1,-1},{13741,7,0,-1},{13742,7,1,-1},{13745,7,0,-1},{13746,7,1,-1}, +{13749,7,0,-1},{13750,7,1,-1},{13754,7,0,-1},{13755,7,1,-1},{13758,7,0,-1}, +{13759,7,1,-1},{13762,7,0,-1},{13763,7,1,-1},{13767,7,0,-1},{13768,7,1,-1}, +{13771,7,0,-1},{13772,7,1,-1},{13775,7,0,-1},{13776,7,1,-1},{13780,7,0,-1}, +{13781,7,1,-1},{13714,7,0,-1},{13784,7,0,-1},{13785,7,1,-1},{13788,7,0,-1}, +{13789,7,1,-1},{13793,7,0,-1},{13794,7,1,-1},{13727,7,1,-1},{13797,7,0,-1}, +{13798,7,1,-1},{13801,7,0,-1},{13802,7,1,-1},{13806,7,0,-1},{13807,7,1,-1}, +{13810,7,0,-1},{13811,7,1,-1},{13814,7,0,-1},{13815,7,1,-1},{13819,7,0,-1}, +{13820,7,1,-1},{13823,7,0,-1},{13824,7,1,-1},{13827,7,0,-1},{13828,7,1,-1}, +{13832,7,0,-1},{13833,7,1,-1},{13836,7,0,-1},{13837,7,1,-1},{13840,7,0,-1}, +{13841,7,1,-1},{13845,7,0,-1},{13846,7,1,-1},{13849,7,0,-1},{13850,7,1,-1}, +{13853,7,0,-1},{13854,7,1,-1},{13858,7,0,-1},{13859,7,1,-1},{13862,7,0,-1}, +{13863,7,1,-1},{13866,7,0,-1},{13867,7,1,-1},{13871,7,0,-1},{13872,7,1,-1}, +{13875,7,0,-1},{13876,7,1,-1},{13879,7,0,-1},{13880,7,1,-1},{13884,7,0,-1}, +{13885,7,1,-1},{13818,7,0,-1},{13888,7,0,-1},{13889,7,1,-1},{13892,7,0,-1}, +{13893,7,1,-1},{13897,7,0,-1},{13898,7,1,-1},{13831,7,1,-1},{13901,7,0,-1}, +{13902,7,1,-1},{13905,7,0,-1},{13906,7,1,-1},{13910,7,0,-1},{13911,7,1,-1}, +{13914,7,0,-1},{13915,7,1,-1},{13918,7,0,-1},{13919,7,1,-1},{13923,7,0,-1}, +{13924,7,1,-1},{13927,7,0,-1},{13928,7,1,-1},{13931,7,0,-1},{13932,7,1,-1}, +{13936,7,0,-1},{13937,7,1,-1},{13940,7,0,-1},{13941,7,1,-1},{13944,7,0,-1}, +{13945,7,1,-1},{13949,7,0,-1},{13950,7,1,-1},{13953,7,0,-1},{13954,7,1,-1}, +{13957,7,0,-1},{13958,7,1,-1},{13962,7,0,-1},{13963,7,1,-1},{13966,7,0,-1}, +{13967,7,1,-1},{13970,7,0,-1},{13971,7,1,-1},{13975,7,0,-1},{13976,7,1,-1}, +{13979,7,0,-1},{13980,7,1,-1},{13983,7,0,-1},{13984,7,1,-1},{13988,7,0,-1}, +{13989,7,1,-1},{13922,7,0,-1},{13992,7,0,-1},{13993,7,1,-1},{13996,7,0,-1}, +{13997,7,1,-1},{14001,7,0,-1},{14002,7,1,-1},{13935,7,1,-1},{14005,7,0,-1}, +{14006,7,1,-1},{14009,7,0,-1},{14010,7,1,-1},{14014,7,0,-1},{14015,7,1,-1}, +{14018,7,0,-1},{14019,7,1,-1},{14022,7,0,-1},{14023,7,1,-1},{14027,7,0,-1}, +{14028,7,1,-1},{14031,7,0,-1},{14032,7,1,-1},{14035,7,0,-1},{14036,7,1,-1}, +{14040,7,0,-1},{14041,7,1,-1},{14044,7,0,-1},{14045,7,1,-1},{14048,7,0,-1}, +{14049,7,1,-1},{14053,7,0,-1},{14054,7,1,-1},{14057,7,0,-1},{14058,7,1,-1}, +{14061,7,0,-1},{14062,7,1,-1},{14066,7,0,-1},{14067,7,1,-1},{14070,7,0,-1}, +{14071,7,1,-1},{14074,7,0,-1},{14075,7,1,-1},{14079,7,0,-1},{14080,7,1,-1}, +{14083,7,0,-1},{14084,7,1,-1},{14087,7,0,-1},{14088,7,1,-1},{14092,7,0,-1}, +{14093,7,1,-1},{14026,7,0,-1},{14096,7,0,-1},{14097,7,1,-1},{14100,7,0,-1}, +{14101,7,1,-1},{14105,7,0,-1},{14106,7,1,-1},{14039,7,1,-1},{14109,7,0,-1}, +{14110,7,1,-1},{14113,7,0,-1},{14114,7,1,-1},{14118,7,0,-1},{14119,7,1,-1}, +{14122,7,0,-1},{14123,7,1,-1},{14126,7,0,-1},{14127,7,1,-1},{14131,7,0,-1}, +{14132,7,1,-1},{14135,7,0,-1},{14136,7,1,-1},{14139,7,0,-1},{14140,7,1,-1}, +{14144,7,0,-1},{14145,7,1,-1},{14148,7,0,-1},{14149,7,1,-1},{14152,7,0,-1}, +{14153,7,1,-1},{14157,7,0,-1},{14158,7,1,-1},{14161,7,0,-1},{14162,7,1,-1}, +{14165,7,0,-1},{14166,7,1,-1},{14170,7,0,-1},{14171,7,1,-1},{14174,7,0,-1}, +{14175,7,1,-1},{14178,7,0,-1},{14179,7,1,-1},{14183,7,0,-1},{14184,7,1,-1}, +{14187,7,0,-1},{14188,7,1,-1},{14191,7,0,-1},{14192,7,1,-1},{14196,7,0,-1}, +{14197,7,1,-1},{14130,7,0,-1},{14200,7,0,-1},{14201,7,1,-1},{14204,7,0,-1}, +{14205,7,1,-1},{14209,7,0,-1},{14210,7,1,-1},{14143,7,1,-1},{14213,7,0,-1}, +{14214,7,1,-1},{14217,7,0,-1},{14218,7,1,-1},{14222,7,0,-1},{14223,7,1,-1}, +{14226,7,0,-1},{14227,7,1,-1},{14230,7,0,-1},{14231,7,1,-1},{14235,7,0,-1}, +{14236,7,1,-1},{14239,7,0,-1},{14240,7,1,-1},{14243,7,0,-1},{14244,7,1,-1}, +{14248,7,0,-1},{14249,7,1,-1},{14252,7,0,-1},{14253,7,1,-1},{14256,7,0,-1}, +{14257,7,1,-1},{14261,7,0,-1},{14262,7,1,-1},{14265,7,0,-1},{14266,7,1,-1}, +{14269,7,0,-1},{14270,7,1,-1},{14274,7,0,-1},{14275,7,1,-1},{14278,7,0,-1}, +{14279,7,1,-1},{14282,7,0,-1},{14283,7,1,-1},{14287,7,0,-1},{14288,7,1,-1}, +{14291,7,0,-1},{14292,7,1,-1},{14295,7,0,-1},{14296,7,1,-1},{14300,7,0,-1}, +{14301,7,1,-1},{14234,7,0,-1},{14304,7,0,-1},{14305,7,1,-1},{14308,7,0,-1}, +{14309,7,1,-1},{14313,7,0,-1},{14314,7,1,-1},{14247,7,1,-1},{14317,7,0,-1}, +{14318,7,1,-1},{14321,7,0,-1},{14322,7,1,-1},{14326,7,0,-1},{14327,7,1,-1}, +{14330,7,0,-1},{14331,7,1,-1},{14334,7,0,-1},{14335,7,1,-1},{14339,7,0,-1}, +{14340,7,1,-1},{14343,7,0,-1},{14344,7,1,-1},{14347,7,0,-1},{14348,7,1,-1}, +{14352,7,0,-1},{14353,7,1,-1},{14356,7,0,-1},{14357,7,1,-1},{14360,7,0,-1}, +{14361,7,1,-1},{14365,7,0,-1},{14366,7,1,-1},{14369,7,0,-1},{14370,7,1,-1}, +{14373,7,0,-1},{14374,7,1,-1},{14378,7,0,-1},{14379,7,1,-1},{14382,7,0,-1}, +{14383,7,1,-1},{14386,7,0,-1},{14387,7,1,-1},{14391,7,0,-1},{14392,7,1,-1}, +{14395,7,0,-1},{14396,7,1,-1},{14399,7,0,-1},{14400,7,1,-1},{14404,7,0,-1}, +{14405,7,1,-1},{14338,7,0,-1},{14408,7,0,-1},{14409,7,1,-1},{14412,7,0,-1}, +{14413,7,1,-1},{14417,7,0,-1},{14418,7,1,-1},{14351,7,1,-1},{14421,7,0,-1}, +{14422,7,1,-1},{14425,7,0,-1},{14426,7,1,-1},{14430,7,0,-1},{14431,7,1,-1}, +{14434,7,0,-1},{14435,7,1,-1},{14438,7,0,-1},{14439,7,1,-1},{14443,7,0,-1}, +{14444,7,1,-1},{14447,7,0,-1},{14448,7,1,-1},{14451,7,0,-1},{14452,7,1,-1}, +{14456,7,0,-1},{14457,7,1,-1},{14460,7,0,-1},{14461,7,1,-1},{14464,7,0,-1}, +{14465,7,1,-1},{14469,7,0,-1},{14470,7,1,-1},{14473,7,0,-1},{14474,7,1,-1}, +{14477,7,0,-1},{14478,7,1,-1},{14482,7,0,-1},{14483,7,1,-1},{14486,7,0,-1}, +{14487,7,1,-1},{14490,7,0,-1},{14491,7,1,-1},{14495,7,0,-1},{14496,7,1,-1}, +{14499,7,0,-1},{14500,7,1,-1},{14503,7,0,-1},{14504,7,1,-1},{14508,7,0,-1}, +{14509,7,1,-1},{14442,7,0,-1},{14512,7,0,-1},{14513,7,1,-1},{14516,7,0,-1}, +{14517,7,1,-1},{14521,7,0,-1},{14522,7,1,-1},{14455,7,1,-1},{14525,7,0,-1}, +{14526,7,1,-1},{14529,7,0,-1},{14530,7,1,-1},{14534,7,0,-1},{14535,7,1,-1}, +{14538,7,0,-1},{14539,7,1,-1},{14542,7,0,-1},{14543,7,1,-1},{14547,7,0,-1}, +{14548,7,1,-1},{14551,7,0,-1},{14552,7,1,-1},{14555,7,0,-1},{14556,7,1,-1}, +{14560,7,0,-1},{14561,7,1,-1},{14564,7,0,-1},{14565,7,1,-1},{14568,7,0,-1}, +{14569,7,1,-1},{14573,7,0,-1},{14574,7,1,-1},{14577,7,0,-1},{14578,7,1,-1}, +{14581,7,0,-1},{14582,7,1,-1},{14586,7,0,-1},{14587,7,1,-1},{14590,7,0,-1}, +{14591,7,1,-1},{14594,7,0,-1},{14595,7,1,-1},{14599,7,0,-1},{14600,7,1,-1}, +{14603,7,0,-1},{14604,7,1,-1},{14607,7,0,-1},{14608,7,1,-1},{14612,7,0,-1}, +{14613,7,1,-1},{14546,7,0,-1},{14616,7,0,-1},{14617,7,1,-1},{14620,7,0,-1}, +{14621,7,1,-1},{14625,7,0,-1},{14626,7,1,-1},{14559,7,1,-1},{14629,7,0,-1}, +{14630,7,1,-1},{14633,7,0,-1},{14634,7,1,-1},{14638,7,0,-1},{14639,7,1,-1}, +{14642,7,0,-1},{14643,7,1,-1},{14646,7,0,-1},{14647,7,1,-1},{14651,7,0,-1}, +{14652,7,1,-1},{14655,7,0,-1},{14656,7,1,-1},{14659,7,0,-1},{14660,7,1,-1}, +{14664,7,0,-1},{14665,7,1,-1},{14668,7,0,-1},{14669,7,1,-1},{14672,7,0,-1}, +{14673,7,1,-1},{14677,7,0,-1},{14678,7,1,-1},{14681,7,0,-1},{14682,7,1,-1}, +{14685,7,0,-1},{14686,7,1,-1},{14690,7,0,-1},{14691,7,1,-1},{14694,7,0,-1}, +{14695,7,1,-1},{14698,7,0,-1},{14699,7,1,-1},{14703,7,0,-1},{14704,7,1,-1}, +{14707,7,0,-1},{14708,7,1,-1},{14711,7,0,-1},{14712,7,1,-1},{14716,7,0,-1}, +{14717,7,1,-1},{14650,7,0,-1},{14720,7,0,-1},{14721,7,1,-1},{14724,7,0,-1}, +{14725,7,1,-1},{14729,7,0,-1},{14730,7,1,-1},{14663,7,1,-1},{14733,7,0,-1}, +{14734,7,1,-1},{14737,7,0,-1},{14738,7,1,-1},{14742,7,0,-1},{14743,7,1,-1}, +{14746,7,0,-1},{14747,7,1,-1},{14750,7,0,-1},{14751,7,1,-1},{14755,7,0,-1}, +{14756,7,1,-1},{14759,7,0,-1},{14760,7,1,-1},{14763,7,0,-1},{14764,7,1,-1}, +{14768,7,0,-1},{14769,7,1,-1},{14772,7,0,-1},{14773,7,1,-1},{14776,7,0,-1}, +{14777,7,1,-1},{14781,7,0,-1},{14782,7,1,-1},{14785,7,0,-1},{14786,7,1,-1}, +{14789,7,0,-1},{14790,7,1,-1},{14794,7,0,-1},{14795,7,1,-1},{14798,7,0,-1}, +{14799,7,1,-1},{14802,7,0,-1},{14803,7,1,-1},{14807,7,0,-1},{14808,7,1,-1}, +{14811,7,0,-1},{14812,7,1,-1},{14815,7,0,-1},{14816,7,1,-1},{14820,7,0,-1}, +{14821,7,1,-1},{14754,7,0,-1},{14824,7,0,-1},{14825,7,1,-1},{14828,7,0,-1}, +{14829,7,1,-1},{14833,7,0,-1},{14834,7,1,-1},{14767,7,1,-1},{14837,7,0,-1}, +{14838,7,1,-1},{14841,7,0,-1},{14842,7,1,-1},{14846,7,0,-1},{14847,7,1,-1}, +{14850,7,0,-1},{14851,7,1,-1},{14854,7,0,-1},{14855,7,1,-1},{14859,7,0,-1}, +{14860,7,1,-1},{14863,7,0,-1},{14864,7,1,-1},{14867,7,0,-1},{14868,7,1,-1}, +{14872,7,0,-1},{14873,7,1,-1},{14876,7,0,-1},{14877,7,1,-1},{14880,7,0,-1}, +{14881,7,1,-1},{14885,7,0,-1},{14886,7,1,-1},{14889,7,0,-1},{14890,7,1,-1}, +{14893,7,0,-1},{14894,7,1,-1},{14898,7,0,-1},{14899,7,1,-1},{14902,7,0,-1}, +{14903,7,1,-1},{14906,7,0,-1},{14907,7,1,-1},{14911,7,0,-1},{14912,7,1,-1}, +{14915,7,0,-1},{14916,7,1,-1},{14919,7,0,-1},{14920,7,1,-1},{14924,7,0,-1}, +{14925,7,1,-1},{14858,7,0,-1},{14928,7,0,-1},{14929,7,1,-1},{14932,7,0,-1}, +{14933,7,1,-1},{14937,7,0,-1},{14938,7,1,-1},{14871,7,1,-1},{14941,7,0,-1}, +{14942,7,1,-1},{14945,7,0,-1},{14946,7,1,-1},{14950,7,0,-1},{14951,7,1,-1}, +{14954,7,0,-1},{14955,7,1,-1},{14958,7,0,-1},{14959,7,1,-1},{14963,7,0,-1}, +{14964,7,1,-1},{14967,7,0,-1},{14968,7,1,-1},{14971,7,0,-1},{14972,7,1,-1}, +{14976,7,0,-1},{14977,7,1,-1},{14980,7,0,-1},{14981,7,1,-1},{14984,7,0,-1}, +{14985,7,1,-1},{14989,7,0,-1},{14990,7,1,-1},{14993,7,0,-1},{14994,7,1,-1}, +{14997,7,0,-1},{14998,7,1,-1},{15002,7,0,-1},{15003,7,1,-1},{15006,7,0,-1}, +{15007,7,1,-1},{15010,7,0,-1},{15011,7,1,-1},{15015,7,0,-1},{15016,7,1,-1}, +{15019,7,0,-1},{15020,7,1,-1},{15023,7,0,-1},{15024,7,1,-1},{15028,7,0,-1}, +{15029,7,1,-1},{14962,7,0,-1},{15032,7,0,-1},{15033,7,1,-1},{15036,7,0,-1}, +{15037,7,1,-1},{15041,7,0,-1},{15042,7,1,-1},{14975,7,1,-1},{15045,7,0,-1}, +{15046,7,1,-1},{15049,7,0,-1},{15050,7,1,-1},{15054,7,0,-1},{15055,7,1,-1}, +{15058,7,0,-1},{15059,7,1,-1},{15062,7,0,-1},{15063,7,1,-1},{15067,7,0,-1}, +{15068,7,1,-1},{15071,7,0,-1},{15072,7,1,-1},{15075,7,0,-1},{15076,7,1,-1}, +{15080,7,0,-1},{15081,7,1,-1},{15084,7,0,-1},{15085,7,1,-1},{15088,7,0,-1}, +{15089,7,1,-1},{15093,7,0,-1},{15094,7,1,-1},{15097,7,0,-1},{15098,7,1,-1}, +{15101,7,0,-1},{15102,7,1,-1},{15106,7,0,-1},{15107,7,1,-1},{15110,7,0,-1}, +{15111,7,1,-1},{15114,7,0,-1},{15115,7,1,-1},{15119,7,0,-1},{15120,7,1,-1}, +{15123,7,0,-1},{15124,7,1,-1},{15127,7,0,-1},{15128,7,1,-1},{15132,7,0,-1}, +{15133,7,1,-1},{15066,7,0,-1},{15136,7,0,-1},{15137,7,1,-1},{15140,7,0,-1}, +{15141,7,1,-1},{15145,7,0,-1},{15146,7,1,-1},{15079,7,1,-1},{15149,7,0,-1}, +{15150,7,1,-1},{15153,7,0,-1},{15154,7,1,-1},{15158,7,0,-1},{15159,7,1,-1}, +{15162,7,0,-1},{15163,7,1,-1},{15166,7,0,-1},{15167,7,1,-1},{15171,7,0,-1}, +{15172,7,1,-1},{15175,7,0,-1},{15176,7,1,-1},{15179,7,0,-1},{15180,7,1,-1}, +{15184,7,0,-1},{15185,7,1,-1},{15188,7,0,-1},{15189,7,1,-1},{15192,7,0,-1}, +{15193,7,1,-1},{15197,7,0,-1},{15198,7,1,-1},{15201,7,0,-1},{15202,7,1,-1}, +{15205,7,0,-1},{15206,7,1,-1},{15210,7,0,-1},{15211,7,1,-1},{15214,7,0,-1}, +{15215,7,1,-1},{15218,7,0,-1},{15219,7,1,-1},{15223,7,0,-1},{15224,7,1,-1}, +{15227,7,0,-1},{15228,7,1,-1},{15231,7,0,-1},{15232,7,1,-1},{15236,7,0,-1}, +{15237,7,1,-1},{15170,7,0,-1},{15240,7,0,-1},{15241,7,1,-1},{15244,7,0,-1}, +{15245,7,1,-1},{15249,7,0,-1},{15250,7,1,-1},{15183,7,1,-1},{15253,7,0,-1}, +{15254,7,1,-1},{15257,7,0,-1},{15258,7,1,-1},{15262,7,0,-1},{15263,7,1,-1}, +{15266,7,0,-1},{15267,7,1,-1},{15270,7,0,-1},{15271,7,1,-1},{15275,7,0,-1}, +{15276,7,1,-1},{15279,7,0,-1},{15280,7,1,-1},{15284,7,1,-1},{15288,7,0,-1}, +{15289,7,1,-1},{15292,7,0,-1},{15293,7,1,-1},{15296,7,0,-1},{15297,7,1,-1}, +{15301,7,0,-1},{15302,7,1,-1},{15305,7,0,-1},{15306,7,1,-1},{15309,7,0,-1}, +{15310,7,1,-1},{15314,7,0,-1},{15315,7,1,-1},{15318,7,0,-1},{15319,7,1,-1}, +{15322,7,0,-1},{15323,7,1,-1},{15327,7,0,-1},{15328,7,1,-1},{15331,7,0,-1}, +{15332,7,1,-1},{15336,7,1,-1},{15340,7,0,-1},{15341,7,1,-1},{15344,7,0,-1}, +{15345,7,1,-1},{15348,7,0,-1},{15349,7,1,-1},{15353,7,0,-1},{15354,7,1,-1}, +{15357,7,0,-1},{15361,7,0,-1},{15366,7,0,-1},{15370,7,0,-1},{15374,7,0,-1}}; diff --git a/tests/misc/Makefile.am b/tests/misc/Makefile.am new file mode 100644 index 0000000..f232c49 --- /dev/null +++ b/tests/misc/Makefile.am @@ -0,0 +1,11 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOCODEC_CFLAGS) \ + $(LIBOSMOABIS_CFLAGS) $(LIBOSMOTRAU_CFLAGS) +LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) \ + $(LIBOSMOABIS_LIBS) $(LIBOSMOTRAU_LIBS) +noinst_PROGRAMS = misc_test +EXTRA_DIST = misc_test.ok + +misc_test_SOURCES = misc_test.c $(srcdir)/../stubs.c +misc_test_LDADD = $(top_builddir)/src/common/libbts.a \ + $(LDADD) diff --git a/tests/misc/misc_test.c b/tests/misc/misc_test.c new file mode 100644 index 0000000..c4d3a59 --- /dev/null +++ b/tests/misc/misc_test.c @@ -0,0 +1,199 @@ +/* testing misc code */ + +/* (C) 2011 by Holger Hans Peter Freyther + * (C) 2014 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include +#include + +#include +#include + +void *ctx = NULL; + +static const uint8_t ipa_rsl_connect[] = { + 0x00, 0x1c, 0xff, 0x10, 0x80, 0x00, 0x0a, 0x0d, + 0x63, 0x6f, 0x6d, 0x2e, 0x69, 0x70, 0x61, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x00, 0xe0, 0x04, 0x00, + 0x00, 0xff, 0x85, 0x00, 0x81, 0x0b, 0xbb +}; + +static const uint8_t osmo_rsl_power[] = { + 0x00, 0x18, 0xff, 0x10, 0x80, 0x00, 0x07, 0x0c, + 0x6f, 0x72, 0x67, 0x2e, 0x6f, 0x73, 0x6d, 0x6f, + 0x63, 0x6f, 0x6d, 0x00, 0x44, 0x02, 0x00, 0x00, + 0xff, 0xfe, 0x04 +}; + +static const uint8_t etsi_oml_opstart[] = { + 0x00, 0x09, 0xff, 0x80, 0x80, 0x00, 0x05, 0x74, + 0x00, 0xff, 0xff, 0xff +}; + +static void test_msg_utils_ipa(void) +{ + struct msgb *msg; + int rc, size; + + printf("Testing IPA structure\n"); + + msg = msgb_alloc(sizeof(ipa_rsl_connect), "IPA test"); + msg->l1h = msgb_put(msg, sizeof(ipa_rsl_connect)); + memcpy(msg->l1h, ipa_rsl_connect, sizeof(ipa_rsl_connect)); + rc = msg_verify_ipa_structure(msg); + OSMO_ASSERT(rc == 0); + msgb_free(msg); + + /* test truncated messages and they should fail */ + for (size = sizeof(ipa_rsl_connect) - 1; size >= 0; --size) { + msg = msgb_alloc(sizeof(ipa_rsl_connect) - 1, "IPA test"); + msg->l1h = msgb_put(msg, size); + memcpy(msg->l1h, ipa_rsl_connect, size); + rc = msg_verify_ipa_structure(msg); + OSMO_ASSERT(rc == -1); + msgb_free(msg); + } + + /* change the type of the message */ + msg = msgb_alloc(sizeof(ipa_rsl_connect), "IPA test"); + msg->l1h = msgb_put(msg, sizeof(ipa_rsl_connect)); + memcpy(msg->l1h, ipa_rsl_connect, sizeof(ipa_rsl_connect)); + msg->l1h[2] = 0x23; + rc = msg_verify_ipa_structure(msg); + OSMO_ASSERT(rc == 0); + msgb_free(msg); +} + +static void test_oml_data(const uint8_t *data, const size_t len, const int exp) +{ + int rc; + struct msgb *msg; + + msg = msgb_alloc(len, "IPA test"); + msg->l2h = msgb_put(msg, len); + memcpy(msg->l2h, data, len); + rc = msg_verify_oml_structure(msg); + if (rc >= 0) + OSMO_ASSERT(msg->l3h > msg->l2h); + OSMO_ASSERT(rc == exp); + msgb_free(msg); +} + +static void test_msg_utils_oml(void) +{ + static const size_t hh_size = sizeof(struct ipaccess_head); + int size; + + printf("Testing OML structure\n"); + + /* test with IPA message */ + printf(" Testing IPA messages.\n"); + test_oml_data(ipa_rsl_connect + hh_size, + sizeof(ipa_rsl_connect) - hh_size, + OML_MSG_TYPE_IPA); + + /* test truncated messages and they should fail */ + for (size = sizeof(ipa_rsl_connect) - hh_size - 1; size >=0; --size) + test_oml_data(ipa_rsl_connect + hh_size, size, -1); + + /* test with Osmo message */ + printf(" Testing Osmo messages.\n"); + test_oml_data(osmo_rsl_power + hh_size, + sizeof(osmo_rsl_power) - hh_size, + OML_MSG_TYPE_OSMO); + for (size = sizeof(osmo_rsl_power) - hh_size - 1; size >=0; --size) + test_oml_data(osmo_rsl_power + hh_size, size, -1); + + /* test with plain ETSI message */ + printf(" Testing ETSI messages.\n"); + test_oml_data(etsi_oml_opstart + hh_size, + sizeof(etsi_oml_opstart) - hh_size, + OML_MSG_TYPE_ETSI); + for (size = sizeof(etsi_oml_opstart) - hh_size - 1; size >=0; --size) + test_oml_data(etsi_oml_opstart + hh_size, size, -1); +} + +static void test_sacch_get(void) +{ + struct gsm_lchan lchan; + int i, off; + + printf("Testing lchan_sacch_get\n"); + memset(&lchan, 0, sizeof(lchan)); + + /* initialize the input. */ + for (i = 1; i < _MAX_SYSINFO_TYPE; ++i) { + lchan.si.valid |= (1 << i); + memset(GSM_LCHAN_SI(&lchan, i), i, GSM_MACBLOCK_LEN); + } + + /* It will start with '1' */ + for (i = 1, off = 0; i <= 32; ++i) { + uint8_t *data = lchan_sacch_get(&lchan); + off = (off + 1) % _MAX_SYSINFO_TYPE; + if (off == 0) + off += 1; + + //printf("i=%d (%%=%d) -> data[0]=%d\n", i, off, data[0]); + OSMO_ASSERT(data[0] == off); + } +} + +static void test_bts_supports_cm(void) +{ + struct gsm_bts *bts; + + bts = gsm_bts_alloc(ctx, 0); + + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_AMR); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_AMR); + + OSMO_ASSERT(bts_supports_cm + (bts, GSM_PCHAN_TCH_F, GSM48_CMODE_SPEECH_V1) == 1); + OSMO_ASSERT(bts_supports_cm + (bts, GSM_PCHAN_TCH_H, GSM48_CMODE_SPEECH_V1) == 1); + OSMO_ASSERT(bts_supports_cm + (bts, GSM_PCHAN_TCH_F, GSM48_CMODE_SPEECH_EFR) == 0); + OSMO_ASSERT(bts_supports_cm + (bts, GSM_PCHAN_TCH_F, GSM48_CMODE_SPEECH_AMR) == 1); + OSMO_ASSERT(bts_supports_cm + (bts, GSM_PCHAN_TCH_H, GSM48_CMODE_SPEECH_AMR) == 1); + + talloc_free(bts); +} + +int main(int argc, char **argv) +{ + ctx = talloc_named_const(NULL, 0, "misc_test"); + + osmo_init_logging2(ctx, &bts_log_info); + + test_sacch_get(); + test_msg_utils_ipa(); + test_msg_utils_oml(); + test_bts_supports_cm(); + return EXIT_SUCCESS; +} diff --git a/tests/misc/misc_test.ok b/tests/misc/misc_test.ok new file mode 100644 index 0000000..a52ce5d --- /dev/null +++ b/tests/misc/misc_test.ok @@ -0,0 +1,6 @@ +Testing lchan_sacch_get +Testing IPA structure +Testing OML structure + Testing IPA messages. + Testing Osmo messages. + Testing ETSI messages. diff --git a/tests/paging/Makefile.am b/tests/paging/Makefile.am new file mode 100644 index 0000000..98c6673 --- /dev/null +++ b/tests/paging/Makefile.am @@ -0,0 +1,8 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(ORTP_CFLAGS) +LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCODEC_LIBS) $(ORTP_LIBS) +noinst_PROGRAMS = paging_test +EXTRA_DIST = paging_test.ok + +paging_test_SOURCES = paging_test.c $(srcdir)/../stubs.c +paging_test_LDADD = $(top_builddir)/src/common/libbts.a $(LDADD) diff --git a/tests/paging/paging_test.c b/tests/paging/paging_test.c new file mode 100644 index 0000000..0accd0f --- /dev/null +++ b/tests/paging/paging_test.c @@ -0,0 +1,132 @@ +/* testing the paging code */ + +/* (C) 2011 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include + +#include +#include +#include +#include + +#include + +static struct gsm_bts *bts; + +static const uint8_t static_ilv[] = { + 0x08, 0x59, 0x51, 0x30, 0x99, 0x00, 0x00, 0x00, 0x19 +}; + +#define ASSERT_TRUE(rc) \ + if (!(rc)) { \ + printf("Assert failed in %s:%d.\n", \ + __FILE__, __LINE__); \ + abort(); \ + } + +static void test_paging_smoke(void) +{ + int rc; + uint8_t out_buf[GSM_MACBLOCK_LEN]; + struct gsm_time g_time; + int is_empty = -1; + printf("Testing that paging messages expire.\n"); + + /* add paging entry */ + rc = paging_add_identity(bts->paging_state, 0, static_ilv, 0); + ASSERT_TRUE(rc == 0); + ASSERT_TRUE(paging_queue_length(bts->paging_state) == 1); + + /* generate messages */ + g_time.fn = 0; + g_time.t1 = 0; + g_time.t2 = 0; + g_time.t3 = 6; + rc = paging_gen_msg(bts->paging_state, out_buf, &g_time, &is_empty); + ASSERT_TRUE(rc == 13); + ASSERT_TRUE(is_empty == 0); + + ASSERT_TRUE(paging_group_queue_empty(bts->paging_state, 0)); + ASSERT_TRUE(paging_queue_length(bts->paging_state) == 0); + + /* now test the empty queue */ + g_time.fn = 0; + g_time.t1 = 0; + g_time.t2 = 0; + g_time.t3 = 6; + rc = paging_gen_msg(bts->paging_state, out_buf, &g_time, &is_empty); + ASSERT_TRUE(rc == 6); + ASSERT_TRUE(is_empty == 1); + + /* + * TODO: test all the cases of different amount tmsi/imsi and check + * if we fill the slots in a optimal way. + */ +} + +static void test_paging_sleep(void) +{ + int rc; + uint8_t out_buf[GSM_MACBLOCK_LEN]; + struct gsm_time g_time; + int is_empty = -1; + printf("Testing that paging messages expire with sleep.\n"); + + /* add paging entry */ + rc = paging_add_identity(bts->paging_state, 0, static_ilv, 0); + ASSERT_TRUE(rc == 0); + ASSERT_TRUE(paging_queue_length(bts->paging_state) == 1); + + /* sleep */ + sleep(1); + + /* generate messages */ + g_time.fn = 0; + g_time.t1 = 0; + g_time.t2 = 0; + g_time.t3 = 6; + rc = paging_gen_msg(bts->paging_state, out_buf, &g_time, &is_empty); + ASSERT_TRUE(rc == 13); + ASSERT_TRUE(is_empty == 0); + + ASSERT_TRUE(paging_group_queue_empty(bts->paging_state, 0)); + ASSERT_TRUE(paging_queue_length(bts->paging_state) == 0); +} + +int main(int argc, char **argv) +{ + tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context"); + msgb_talloc_ctx_init(tall_bts_ctx, 0); + + osmo_init_logging2(tall_bts_ctx, &bts_log_info); + + bts = gsm_bts_alloc(tall_bts_ctx, 0); + if (bts_init(bts) < 0) { + fprintf(stderr, "unable to open bts\n"); + exit(1); + } + + test_paging_smoke(); + test_paging_sleep(); + printf("Success\n"); + + return 0; +} + diff --git a/tests/paging/paging_test.ok b/tests/paging/paging_test.ok new file mode 100644 index 0000000..57565e2 --- /dev/null +++ b/tests/paging/paging_test.ok @@ -0,0 +1,3 @@ +Testing that paging messages expire. +Testing that paging messages expire with sleep. +Success diff --git a/tests/power/Makefile.am b/tests/power/Makefile.am new file mode 100644 index 0000000..3cb8d15 --- /dev/null +++ b/tests/power/Makefile.am @@ -0,0 +1,9 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(ORTP_CFLAGS) +LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(ORTP_LIBS) + +noinst_PROGRAMS = power_test +EXTRA_DIST = power_test.ok + +power_test_SOURCES = power_test.c $(srcdir)/../stubs.c +power_test_LDADD = $(top_builddir)/src/common/libbts.a $(LIBOSMOABIS_LIBS) $(LDADD) diff --git a/tests/power/power_test.c b/tests/power/power_test.c new file mode 100644 index 0000000..a46a430 --- /dev/null +++ b/tests/power/power_test.c @@ -0,0 +1,88 @@ +/* + * (C) 2013,2014 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include + +static inline void apply_power_test(struct gsm_lchan *lchan, int rxlev, int exp_ret, uint8_t exp_current) +{ + int ret = lchan_ms_pwr_ctrl(lchan, lchan->ms_power_ctrl.current, rxlev); + + printf("power control [%d]: MS current power %u\n", ret, lchan->ms_power_ctrl.current); + OSMO_ASSERT(ret == exp_ret); + OSMO_ASSERT(lchan->ms_power_ctrl.current == exp_current); +} + +static void test_power_loop(void) +{ + struct gsm_bts bts; + struct gsm_bts_trx trx; + struct gsm_bts_trx_ts ts; + struct gsm_lchan *lchan; + + memset(&bts, 0, sizeof(bts)); + memset(&trx, 0, sizeof(trx)); + memset(&ts, 0, sizeof(ts)); + + lchan = &ts.lchan[0]; + lchan->ts = &ts; + ts.trx = &trx; + trx.bts = &bts; + bts.band = GSM_BAND_1800; + trx.ms_power_control = 1; + bts.ul_power_target = -75; + + lchan->state = LCHAN_S_NONE; + lchan->ms_power_ctrl.current = ms_pwr_ctl_lvl(GSM_BAND_1800, 0); + OSMO_ASSERT(lchan->ms_power_ctrl.current == 15); + + /* Simply clamping */ + apply_power_test(lchan, -60, 0, 15); + + /* + * Now 15 dB too little and we should power it up. Could be a + * power level of 7 or 8 for 15 dBm + */ + apply_power_test(lchan, -90, 1, 7); + + /* It should be clamped to level 0 and 30 dBm */ + apply_power_test(lchan, -100, 1, 0); + + /* Fix it and jump down */ + lchan->ms_power_ctrl.fixed = 1; + apply_power_test(lchan, -60, 0, 0); + + /* And leave it again */ + lchan->ms_power_ctrl.fixed = 0; + apply_power_test(lchan, -40, 1, 15); +} + +int main(int argc, char **argv) +{ + printf("Testing power loop...\n"); + + test_power_loop(); + + printf("Power loop test OK\n"); + + return 0; +} diff --git a/tests/power/power_test.ok b/tests/power/power_test.ok new file mode 100644 index 0000000..cf0a38b --- /dev/null +++ b/tests/power/power_test.ok @@ -0,0 +1,7 @@ +Testing power loop... +power control [0]: MS current power 15 +power control [1]: MS current power 7 +power control [1]: MS current power 0 +power control [0]: MS current power 0 +power control [1]: MS current power 15 +Power loop test OK diff --git a/tests/stubs.c b/tests/stubs.c new file mode 100644 index 0000000..f969cb3 --- /dev/null +++ b/tests/stubs.c @@ -0,0 +1,57 @@ +#include + +struct femtol1_hdl; +struct bts_model_set_dyn_pdch_data; + +/* + * Stubs to provide an empty bts model implementation for testing. + * If we ever want to re-define such a symbol we can make them weak + * here. + */ +int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo, + void *obj, uint8_t adm_state) +{ return 0; } +int bts_model_init(struct gsm_bts *bts) +{ return 0; } +int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, + struct tlv_parsed *new_attr, int kind, void *obj) +{ return 0; } + +int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) +{ return 0; } +int bts_model_trx_close(struct gsm_bts_trx *trx) +{ return 0; } +int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, + struct tlv_parsed *old_attr, struct tlv_parsed *new_attr, + void *obj) +{ return 0; } +int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, + void *obj) +{ return 0; } +int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) +{ return 0; } + +uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx) +{ return 0; } + +int bts_model_oml_estab(struct gsm_bts *bts) +{ return 0; } + +int l1if_set_txpower(struct femtol1_hdl *fl1h, float tx_power) +{ return 0; } + +int bts_model_lchan_deactivate(struct gsm_lchan *lchan) { return 0; } +int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan) { return 0; } + +int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) +{ return 0; } + +void bts_model_abis_close(struct gsm_bts *bts) +{ } + +int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) +{ return 0; } + +int bts_model_ts_connect(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config as_pchan) +{ return 0; } diff --git a/tests/sysmobts/Makefile.am b/tests/sysmobts/Makefile.am new file mode 100644 index 0000000..5f27116 --- /dev/null +++ b/tests/sysmobts/Makefile.am @@ -0,0 +1,17 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_srcdir)/src/osmo-bts-sysmo $(SYSMOBTS_INCDIR) +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(ORTP_CFLAGS) +LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(ORTP_LIBS) + +noinst_PROGRAMS = sysmobts_test +EXTRA_DIST = sysmobts_test.ok + +sysmobts_test_SOURCES = sysmobts_test.c $(top_srcdir)/src/osmo-bts-sysmo/utils.c \ + $(top_srcdir)/src/osmo-bts-sysmo/l1_if.c \ + $(top_srcdir)/src/osmo-bts-sysmo/oml.c \ + $(top_srcdir)/src/osmo-bts-sysmo/l1_transp_hw.c \ + $(top_srcdir)/src/osmo-bts-sysmo/tch.c \ + $(top_srcdir)/src/osmo-bts-sysmo/calib_file.c \ + $(top_srcdir)/src/osmo-bts-sysmo/calib_fixup.c \ + $(top_srcdir)/src/osmo-bts-sysmo/misc/sysmobts_par.c \ + $(top_srcdir)/src/osmo-bts-sysmo/eeprom.c +sysmobts_test_LDADD = $(top_builddir)/src/common/libbts.a $(LIBOSMOABIS_LIBS) $(LDADD) diff --git a/tests/sysmobts/sysmobts_test.c b/tests/sysmobts/sysmobts_test.c new file mode 100644 index 0000000..02490be --- /dev/null +++ b/tests/sysmobts/sysmobts_test.c @@ -0,0 +1,208 @@ +/* + * (C) 2013,2014 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 General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include "femtobts.h" +#include "l1_if.h" +#include "utils.h" + +#include + +#include + +static int direct_map[][3] = { + { GSM_BAND_850, GsmL1_FreqBand_850, 128 }, + { GSM_BAND_900, GsmL1_FreqBand_900, 1 }, + { GSM_BAND_1800, GsmL1_FreqBand_1800, 600 }, + { GSM_BAND_1900, GsmL1_FreqBand_1900, 600 }, +}; + +static int dcs_to_dcs[][3] = { + { GSM_BAND_900, GsmL1_FreqBand_1800, 600 }, + { GSM_BAND_1800, GsmL1_FreqBand_900, 1 }, + { GSM_BAND_900, -1, 438 }, +}; + +static int pcs_to_pcs[][3] = { + { GSM_BAND_850, GsmL1_FreqBand_1900, 512 }, + { GSM_BAND_1900, GsmL1_FreqBand_850, 128 }, + { GSM_BAND_900, -1, 438 }, +}; + +static void test_sysmobts_auto_band(void) +{ + struct gsm_bts bts; + struct gsm_bts_trx trx; + struct femtol1_hdl hdl; + int i; + + memset(&bts, 0, sizeof(bts)); + memset(&trx, 0, sizeof(trx)); + memset(&hdl, 0, sizeof(hdl)); + trx.bts = &bts; + trx.role_bts.l1h = &hdl; + + /* claim to support all hw_info's */ + hdl.hw_info.band_support = GSM_BAND_850 | GSM_BAND_900 | + GSM_BAND_1800 | GSM_BAND_1900; + + /* start with the current option */ + printf("Testing the no auto-band mapping.\n"); + for (i = 0; i < ARRAY_SIZE(direct_map); ++i) { + uint16_t arfcn; + int res; + + bts.auto_band = 0; + bts.band = direct_map[i][0]; + arfcn = direct_map[i][2]; + res = sysmobts_select_femto_band(&trx, arfcn); + printf("No auto-band band(%d) arfcn(%u) want(%d) got(%d)\n", + bts.band, arfcn, direct_map[i][1], res); + OSMO_ASSERT(res == direct_map[i][1]); + } + + /* Check if auto-band does not break things */ + printf("Checking the mapping with auto-band.\n"); + for (i = 0; i < ARRAY_SIZE(direct_map); ++i) { + uint16_t arfcn; + int res; + + bts.auto_band = 1; + bts.band = direct_map[i][0]; + arfcn = direct_map[i][2]; + res = sysmobts_select_femto_band(&trx, arfcn); + printf("Auto-band band(%d) arfcn(%u) want(%d) got(%d)\n", + bts.band, arfcn, direct_map[i][1], res); + OSMO_ASSERT(res == direct_map[i][1]); + } + + /* Check DCS to DCS change */ + printf("Checking DCS to DCS\n"); + for (i = 0; i < ARRAY_SIZE(dcs_to_dcs); ++i) { + uint16_t arfcn; + int res; + + bts.auto_band = 1; + bts.band = dcs_to_dcs[i][0]; + arfcn = dcs_to_dcs[i][2]; + res = sysmobts_select_femto_band(&trx, arfcn); + printf("DCS to DCS band(%d) arfcn(%u) want(%d) got(%d)\n", + bts.band, arfcn, dcs_to_dcs[i][1], res); + OSMO_ASSERT(res == dcs_to_dcs[i][1]); + } + + /* Check for a PCS to PCS change */ + printf("Checking PCS to PCS\n"); + for (i = 0; i < ARRAY_SIZE(pcs_to_pcs); ++i) { + uint16_t arfcn; + int res; + + bts.auto_band = 1; + bts.band = pcs_to_pcs[i][0]; + arfcn = pcs_to_pcs[i][2]; + res = sysmobts_select_femto_band(&trx, arfcn); + printf("PCS to PCS band(%d) arfcn(%u) want(%d) got(%d)\n", + bts.band, arfcn, pcs_to_pcs[i][1], res); + OSMO_ASSERT(res == pcs_to_pcs[i][1]); + } +} + +static void test_sysmobts_cipher(void) +{ + static const uint8_t cipher_cmd[] = { + 0x03, 0x00, 0x0d, 0x06, 0x35, 0x11, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b }; + static const uint8_t too_early_classmark[] = { + 0x01, 0x00, 0x4d, 0x06, 0x16, 0x03, 0x30, 0x18, + 0xa2, 0x20, 0x0b, 0x60, 0x14, 0x4c, 0xa7, 0x7b, + 0x29, 0x11, 0xdc, 0x40, 0x04, 0x00, 0x2b }; + static const uint8_t first_ciphered_cipher_cmpl[] = { + 0x01, 0x30, 0x4d, 0x06, 0x16, 0x03, 0x30, 0x18, + 0xa2, 0x20, 0x0b, 0x60, 0x14, 0x4c, 0xa7, 0x7b, + 0x29, 0x11, 0xdc, 0x40, 0x04, 0x00, 0x2b }; + + struct gsm_lchan lchan; + struct femtol1_hdl fl1h; + struct msgb *msg; + GsmL1_MsgUnitParam_t unit; + int rc; + + memset(&lchan, 0, sizeof(lchan)); + memset(&fl1h, 0, sizeof(fl1h)); + + /* Inject the cipher mode command */ + msg = msgb_alloc_headroom(128, 64, "ciphering mode command"); + lchan.ciph_state = LCHAN_CIPH_NONE; + memcpy(msgb_put(msg, ARRAY_SIZE(cipher_cmd)), cipher_cmd, ARRAY_SIZE(cipher_cmd)); + rc = bts_check_for_ciph_cmd(&fl1h, msg, &lchan); + OSMO_ASSERT(rc == 1); + OSMO_ASSERT(lchan.ciph_state == LCHAN_CIPH_RX_REQ); + OSMO_ASSERT(lchan.ciph_ns == 1); + msgb_free(msg); + + /* Move to the confirmed state */ + lchan.ciph_state = LCHAN_CIPH_RX_CONF; + + /* Handle message sent before ciphering was received */ + memcpy(&unit.u8Buffer[0], too_early_classmark, ARRAY_SIZE(too_early_classmark)); + unit.u8Size = ARRAY_SIZE(too_early_classmark); + rc = bts_check_for_first_ciphrd(&lchan, unit.u8Buffer, unit.u8Size); + OSMO_ASSERT(rc == 0); + OSMO_ASSERT(lchan.ciph_state == LCHAN_CIPH_RX_CONF); + + /* Now send the first ciphered message */ + memcpy(&unit.u8Buffer[0], first_ciphered_cipher_cmpl, ARRAY_SIZE(first_ciphered_cipher_cmpl)); + unit.u8Size = ARRAY_SIZE(first_ciphered_cipher_cmpl); + rc = bts_check_for_first_ciphrd(&lchan, unit.u8Buffer, unit.u8Size); + OSMO_ASSERT(rc == 1); + /* we cannot test for lchan.ciph_state == * LCHAN_CIPH_RX_CONF_TX_REQ, as + * this happens asynchronously on the other side of the l1sap queue */ +} + +int main(int argc, char **argv) +{ + printf("Testing sysmobts routines\n"); + test_sysmobts_auto_band(); + test_sysmobts_cipher(); + + return 0; +} + + +/* + * some local stubs. We need to pull in a lot more code and can't + * use the generic stubs unless we make all of them weak + */ +void bts_update_status(enum bts_global_status which, int on) +{} + +int bts_model_init(struct gsm_bts *bts) +{ return 0; } +int bts_model_oml_estab(struct gsm_bts *bts) +{ return 0; } +void bts_model_abis_close(struct gsm_bts *bts) +{ } +void bts_model_phy_link_set_defaults(struct phy_link *plink) +{ } +void bts_model_phy_instance_set_defaults(struct phy_instance *pinst) +{ } diff --git a/tests/sysmobts/sysmobts_test.ok b/tests/sysmobts/sysmobts_test.ok new file mode 100644 index 0000000..1f53417 --- /dev/null +++ b/tests/sysmobts/sysmobts_test.ok @@ -0,0 +1,19 @@ +Testing sysmobts routines +Testing the no auto-band mapping. +No auto-band band(1) arfcn(128) want(0) got(0) +No auto-band band(2) arfcn(1) want(1) got(1) +No auto-band band(4) arfcn(600) want(2) got(2) +No auto-band band(8) arfcn(600) want(3) got(3) +Checking the mapping with auto-band. +Auto-band band(1) arfcn(128) want(0) got(0) +Auto-band band(2) arfcn(1) want(1) got(1) +Auto-band band(4) arfcn(600) want(2) got(2) +Auto-band band(8) arfcn(600) want(3) got(3) +Checking DCS to DCS +DCS to DCS band(2) arfcn(600) want(2) got(2) +DCS to DCS band(4) arfcn(1) want(1) got(1) +DCS to DCS band(2) arfcn(438) want(-1) got(-1) +Checking PCS to PCS +PCS to PCS band(1) arfcn(512) want(3) got(3) +PCS to PCS band(8) arfcn(128) want(0) got(0) +PCS to PCS band(2) arfcn(438) want(-1) got(-1) diff --git a/tests/testsuite.at b/tests/testsuite.at new file mode 100644 index 0000000..2d1cefd --- /dev/null +++ b/tests/testsuite.at @@ -0,0 +1,51 @@ +AT_INIT +AT_BANNER([Regression tests.]) + +AT_SETUP([paging]) +AT_KEYWORDS([paging]) +cat $abs_srcdir/paging/paging_test.ok > expout +AT_CHECK([$OSMO_QEMU $abs_top_builddir/tests/paging/paging_test], [], [expout], [ignore]) +AT_CLEANUP + +AT_SETUP([agch]) +AT_KEYWORDS([agch]) +cat $abs_srcdir/agch/agch_test.ok > expout +AT_CHECK([$OSMO_QEMU $abs_top_builddir/tests/agch/agch_test], [], [expout], [ignore]) +AT_CLEANUP + +AT_SETUP([cipher]) +AT_KEYWORDS([cipher]) +cat $abs_srcdir/cipher/cipher_test.ok > expout +AT_CHECK([$OSMO_QEMU $abs_top_builddir/tests/cipher/cipher_test], [], [expout], [ignore]) +AT_CLEANUP + +AT_SETUP([misc]) +AT_KEYWORDS([misc]) +cat $abs_srcdir/misc/misc_test.ok > expout +AT_CHECK([$OSMO_QEMU $abs_top_builddir/tests/misc/misc_test], [], [expout], [ignore]) +AT_CLEANUP + +AT_SETUP([handover]) +AT_KEYWORDS([handover]) +cat $abs_srcdir/handover/handover_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/handover/handover_test], [], [expout], [ignore]) +AT_CLEANUP + +AT_SETUP([power]) +AT_KEYWORDS([power]) +cat $abs_srcdir/power/power_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/power/power_test], [], [expout], [ignore]) +AT_CLEANUP + +AT_SETUP([tx_power]) +AT_KEYWORDS([tx_power]) +cat $abs_srcdir/tx_power/tx_power_test.ok > expout +cat $abs_srcdir/tx_power/tx_power_test.err > experr +AT_CHECK([$abs_top_builddir/tests/tx_power/tx_power_test], [], [expout], [experr]) +AT_CLEANUP + +AT_SETUP([meas]) +AT_KEYWORDS([meas]) +cat $abs_srcdir/meas/meas_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/meas/meas_test], [], [expout], [ignore]) +AT_CLEANUP diff --git a/tests/tx_power/Makefile.am b/tests/tx_power/Makefile.am new file mode 100644 index 0000000..cd7ccc2 --- /dev/null +++ b/tests/tx_power/Makefile.am @@ -0,0 +1,8 @@ +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) +LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) +noinst_PROGRAMS = tx_power_test +EXTRA_DIST = tx_power_test.ok tx_power_test.err + +tx_power_test_SOURCES = tx_power_test.c $(srcdir)/../stubs.c +tx_power_test_LDADD = $(top_builddir)/src/common/libbts.a $(LDADD) diff --git a/tests/tx_power/tx_power_test.c b/tests/tx_power/tx_power_test.c new file mode 100644 index 0000000..ad3f68c --- /dev/null +++ b/tests/tx_power/tx_power_test.c @@ -0,0 +1,247 @@ +/* Test cases for tx_power.c Transmit Power Computation */ + +/* (C) 2017 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include + + +static const struct trx_power_params tpp_1002 = { + .trx_p_max_out_mdBm = to_mdB(23), + .p_total_tgt_mdBm = to_mdB(23), + .p_total_cur_mdBm = 0, + .thermal_attenuation_mdB = 0, + .user_gain_mdB = 0, + .pa = { + .nominal_gain_mdB = 0, + }, + .user_pa = { + .nominal_gain_mdB = 0, + }, + .ramp = { + .max_initial_pout_mdBm = to_mdB(23), + .step_size_mdB = to_mdB(2), + .step_interval_sec = 1, + }, +}; + +static const struct trx_power_params tpp_1020 = { + .trx_p_max_out_mdBm = to_mdB(23), + .p_total_tgt_mdBm = to_mdB(33), + .p_total_cur_mdBm = 0, + .thermal_attenuation_mdB = 0, + .user_gain_mdB = 0, + .pa = { + .nominal_gain_mdB = to_mdB(10), + }, + .user_pa = { + .nominal_gain_mdB = 0, + }, + .ramp = { + .max_initial_pout_mdBm = to_mdB(0), + .step_size_mdB = to_mdB(2), + .step_interval_sec = 1, + }, +}; + +static const struct trx_power_params tpp_1100 = { + .trx_p_max_out_mdBm = to_mdB(23), + .p_total_tgt_mdBm = to_mdB(40), + .p_total_cur_mdBm = 0, + .thermal_attenuation_mdB = 0, + .user_gain_mdB = 0, + .pa = { + .nominal_gain_mdB = to_mdB(17), + }, + .user_pa = { + .nominal_gain_mdB = 0, + }, + .ramp = { + .max_initial_pout_mdBm = to_mdB(0), + .step_size_mdB = to_mdB(2), + .step_interval_sec = 1, + }, +}; + +static const struct trx_power_params tpp_2050 = { + .trx_p_max_out_mdBm = to_mdB(37), + .p_total_tgt_mdBm = to_mdB(37), + .p_total_cur_mdBm = 0, + .thermal_attenuation_mdB = 0, + .user_gain_mdB = 0, + .pa = { + .nominal_gain_mdB = 0, + }, + .user_pa = { + .nominal_gain_mdB = 0, + }, + .ramp = { + .max_initial_pout_mdBm = to_mdB(0), + .step_size_mdB = to_mdB(2), + .step_interval_sec = 1, + }, +}; + +static void test_sbts1002(struct gsm_bts_trx *trx) +{ + printf("Testing tx_power calculation for sysmoBTS 1002\n"); + trx->power_params = tpp_1002; + trx->max_power_red = 0; + OSMO_ASSERT(power_ramp_initial_power_mdBm(trx) == to_mdB(23)); + OSMO_ASSERT(get_p_max_out_mdBm(trx) == to_mdB(23)); + /* at max_power_red = 0, we expect full 23dBm */ + OSMO_ASSERT(get_p_nominal_mdBm(trx) == to_mdB(23)); + trx->max_power_red = 2; + /* at max_power_red = 2, we expect 21dBm */ + OSMO_ASSERT(get_p_nominal_mdBm(trx) == to_mdB(21)); + /* at 1 step (of 2dB), we expect full 23-2-2=19 dBm */ + OSMO_ASSERT(get_p_target_mdBm(trx, 1) == to_mdB(19)); + /* at 2 steps (= 4dB), we expect 23-2-4=17*/ + OSMO_ASSERT(get_p_trxout_target_mdBm(trx, 2) == to_mdB(17)); +} + +static void test_sbts1020(struct gsm_bts_trx *trx) +{ + printf("Testing tx_power calculation for sysmoBTS 1020\n"); + trx->power_params = tpp_1020; + trx->max_power_red = 0; + OSMO_ASSERT(power_ramp_initial_power_mdBm(trx) == to_mdB(-10)); + OSMO_ASSERT(get_p_max_out_mdBm(trx) == to_mdB(33)); + /* at max_power_red = 0, we expect full 33dBm */ + OSMO_ASSERT(get_p_nominal_mdBm(trx) == to_mdB(33)); + trx->max_power_red = 2; + /* at max_power_red = 2, we expect 31dBm */ + OSMO_ASSERT(get_p_nominal_mdBm(trx) == to_mdB(31)); + /* at 1 step (of 2dB), we expect full 33-2-2=29 dBm */ + OSMO_ASSERT(get_p_target_mdBm(trx, 1) == to_mdB(29)); + /* at 2 steps (= 4dB), we expect 33-2-4-10=17*/ + OSMO_ASSERT(get_p_trxout_target_mdBm(trx, 2) == to_mdB(17)); +} + + +static void test_sbts1100(struct gsm_bts_trx *trx) +{ + printf("Testing tx_power calculation for sysmoBTS 1100\n"); + trx->power_params = tpp_1100; + trx->max_power_red = 0; + OSMO_ASSERT(power_ramp_initial_power_mdBm(trx) == to_mdB(-17)); + OSMO_ASSERT(get_p_max_out_mdBm(trx) == to_mdB(40)); + /* at max_power_red = 0, we expect full 33dBm */ + OSMO_ASSERT(get_p_nominal_mdBm(trx) == to_mdB(40)); + trx->max_power_red = 2; + /* at max_power_red = 2, we expect 38dBm */ + OSMO_ASSERT(get_p_nominal_mdBm(trx) == to_mdB(38)); + /* at 1 step (of 2dB), we expect full 40-2-2=36 dBm */ + OSMO_ASSERT(get_p_target_mdBm(trx, 1) == to_mdB(36)); + /* at 2 steps (= 4dB), we expect 40-2-4-17=17*/ + OSMO_ASSERT(get_p_trxout_target_mdBm(trx, 2) == to_mdB(17)); +} + +static void test_sbts2050(struct gsm_bts_trx *trx) +{ + printf("Testing tx_power calculation for sysmoBTS 2050\n"); + trx->power_params = tpp_2050; + trx->max_power_red = 0; + OSMO_ASSERT(power_ramp_initial_power_mdBm(trx) == to_mdB(0)); + OSMO_ASSERT(get_p_max_out_mdBm(trx) == to_mdB(37)); + /* at max_power_red = 0, we expect full 37dBm */ + OSMO_ASSERT(get_p_nominal_mdBm(trx) == to_mdB(37)); + trx->max_power_red = 2; + /* at max_power_red = 2, we expect 35dBm */ + OSMO_ASSERT(get_p_nominal_mdBm(trx) == to_mdB(35)); + /* at 1 step (of 2dB), we expect full 37-2-2=33 dBm */ + OSMO_ASSERT(get_p_target_mdBm(trx, 1) == to_mdB(33)); + /* at 2 steps (= 4dB), we expect 37-2-4=31dBm */ + OSMO_ASSERT(get_p_trxout_target_mdBm(trx, 2) == to_mdB(31)); +} + +int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm) +{ + struct trx_power_params *tpp = &trx->power_params; + + printf("CHANGE_POWER(%d)\n", p_trxout_mdBm); + + if (tpp->ramp.attenuation_mdB == 0) + exit(0); + + power_trx_change_compl(trx, p_trxout_mdBm); + return 0; +} + +static void test_power_ramp(struct gsm_bts_trx *trx, int dBm) +{ + printf("Testing tx_power ramping for sysmoBTS 1020\n"); + trx->power_params = tpp_1020; + trx->max_power_red = 0; + + power_ramp_start(trx, to_mdB(dBm), 0); +} + +int main(int argc, char **argv) +{ + static struct gsm_bts *bts; + struct gsm_bts_trx *trx; + void *tall_bts_ctx; + + tall_bts_ctx = talloc_named_const(NULL, 1, "OsmoBTS context"); + msgb_talloc_ctx_init(tall_bts_ctx, 0); + + osmo_init_logging2(tall_bts_ctx, &bts_log_info); + osmo_stderr_target->categories[DL1C].loglevel = LOGL_DEBUG; + log_set_print_filename(osmo_stderr_target, 0); + + bts = gsm_bts_alloc(tall_bts_ctx, 0); + if (!bts) { + fprintf(stderr, "Failed to create BTS structure\n"); + exit(1); + } + trx = gsm_bts_trx_alloc(bts); + if (!trx) { + fprintf(stderr, "Failed to TRX structure\n"); + exit(1); + } + + if (bts_init(bts) < 0) { + fprintf(stderr, "unable to to open bts\n"); + exit(1); + } + + test_sbts1002(trx); + test_sbts1020(trx); + test_sbts1100(trx); + test_sbts2050(trx); + + /* test error case / excess power (40 dBm is too much) */ + test_power_ramp(trx, 40); + /* test actaul ramping to full 33 dBm */ + test_power_ramp(trx, 33); + + while (1) { + osmo_select_main(0); + } +} diff --git a/tests/tx_power/tx_power_test.err b/tests/tx_power/tx_power_test.err new file mode 100644 index 0000000..bf33b42 --- /dev/null +++ b/tests/tx_power/tx_power_test.err @@ -0,0 +1,38 @@ +power_ramp_start(cur=0, tgt=40000) +Asked to ramp power up to 40000 mdBm, which exceeds P_max_out (33000) +power_ramp_start(cur=0, tgt=33000) +ramp_timer_cb(cur_pout=2000, tgt_pout=33000, ramp_att=31000, therm_att=0, user_gain=0) +ramping TRX board output power to -8000 mdBm. +ramp_timer_cb(cur_pout=4000, tgt_pout=33000, ramp_att=29000, therm_att=0, user_gain=0) +ramping TRX board output power to -6000 mdBm. +ramp_timer_cb(cur_pout=6000, tgt_pout=33000, ramp_att=27000, therm_att=0, user_gain=0) +ramping TRX board output power to -4000 mdBm. +ramp_timer_cb(cur_pout=8000, tgt_pout=33000, ramp_att=25000, therm_att=0, user_gain=0) +ramping TRX board output power to -2000 mdBm. +ramp_timer_cb(cur_pout=10000, tgt_pout=33000, ramp_att=23000, therm_att=0, user_gain=0) +ramping TRX board output power to 0 mdBm. +ramp_timer_cb(cur_pout=12000, tgt_pout=33000, ramp_att=21000, therm_att=0, user_gain=0) +ramping TRX board output power to 2000 mdBm. +ramp_timer_cb(cur_pout=14000, tgt_pout=33000, ramp_att=19000, therm_att=0, user_gain=0) +ramping TRX board output power to 4000 mdBm. +ramp_timer_cb(cur_pout=16000, tgt_pout=33000, ramp_att=17000, therm_att=0, user_gain=0) +ramping TRX board output power to 6000 mdBm. +ramp_timer_cb(cur_pout=18000, tgt_pout=33000, ramp_att=15000, therm_att=0, user_gain=0) +ramping TRX board output power to 8000 mdBm. +ramp_timer_cb(cur_pout=20000, tgt_pout=33000, ramp_att=13000, therm_att=0, user_gain=0) +ramping TRX board output power to 10000 mdBm. +ramp_timer_cb(cur_pout=22000, tgt_pout=33000, ramp_att=11000, therm_att=0, user_gain=0) +ramping TRX board output power to 12000 mdBm. +ramp_timer_cb(cur_pout=24000, tgt_pout=33000, ramp_att=9000, therm_att=0, user_gain=0) +ramping TRX board output power to 14000 mdBm. +ramp_timer_cb(cur_pout=26000, tgt_pout=33000, ramp_att=7000, therm_att=0, user_gain=0) +ramping TRX board output power to 16000 mdBm. +ramp_timer_cb(cur_pout=28000, tgt_pout=33000, ramp_att=5000, therm_att=0, user_gain=0) +ramping TRX board output power to 18000 mdBm. +ramp_timer_cb(cur_pout=30000, tgt_pout=33000, ramp_att=3000, therm_att=0, user_gain=0) +ramping TRX board output power to 20000 mdBm. +ramp_timer_cb(cur_pout=32000, tgt_pout=33000, ramp_att=1000, therm_att=0, user_gain=0) +ramping TRX board output power to 22000 mdBm. +ramp_timer_cb(cur_pout=33000, tgt_pout=33000, ramp_att=0, therm_att=0, user_gain=0) +ramping TRX board output power to 23000 mdBm. + \ No newline at end of file diff --git a/tests/tx_power/tx_power_test.ok b/tests/tx_power/tx_power_test.ok new file mode 100644 index 0000000..ceb88ab --- /dev/null +++ b/tests/tx_power/tx_power_test.ok @@ -0,0 +1,23 @@ +Testing tx_power calculation for sysmoBTS 1002 +Testing tx_power calculation for sysmoBTS 1020 +Testing tx_power calculation for sysmoBTS 1100 +Testing tx_power calculation for sysmoBTS 2050 +Testing tx_power ramping for sysmoBTS 1020 +Testing tx_power ramping for sysmoBTS 1020 +CHANGE_POWER(-8000) +CHANGE_POWER(-6000) +CHANGE_POWER(-4000) +CHANGE_POWER(-2000) +CHANGE_POWER(0) +CHANGE_POWER(2000) +CHANGE_POWER(4000) +CHANGE_POWER(6000) +CHANGE_POWER(8000) +CHANGE_POWER(10000) +CHANGE_POWER(12000) +CHANGE_POWER(14000) +CHANGE_POWER(16000) +CHANGE_POWER(18000) +CHANGE_POWER(20000) +CHANGE_POWER(22000) +CHANGE_POWER(23000) -- cgit v1.2.3 From 91754f0f59145e5c552a2ce401d3816455dd566c Mon Sep 17 00:00:00 2001 From: Debian Mobcom Maintainers Date: Mon, 16 Jul 2018 20:51:19 +0200 Subject: spelling =================================================================== Gbp-Pq: Name spelling.patch --- src/common/pcu_sock.c | 2 +- src/common/rsl.c | 2 +- src/osmo-bts-trx/trx_if.c | 2 +- src/osmo-bts-trx/trx_vty.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/pcu_sock.c b/src/common/pcu_sock.c index 5f94050..7b3993c 100644 --- a/src/common/pcu_sock.c +++ b/src/common/pcu_sock.c @@ -641,7 +641,7 @@ static int pcu_rx(struct gsm_network *net, uint8_t msg_type, rc = pcu_rx_txt_ind(bts, &pcu_prim->u.txt_ind); break; default: - LOGP(DPCU, LOGL_ERROR, "Received unknwon PCU msg type %d\n", + LOGP(DPCU, LOGL_ERROR, "Received unknown PCU msg type %d\n", msg_type); rc = -EINVAL; } diff --git a/src/common/rsl.c b/src/common/rsl.c index 5dd2c59..ac29885 100644 --- a/src/common/rsl.c +++ b/src/common/rsl.c @@ -2815,7 +2815,7 @@ static int rsl_rx_ipaccess(struct gsm_bts_trx *trx, struct msgb *msg) msg->lchan = lchan_lookup(trx, dch->chan_nr, "RSL rx IPACC: "); if (!msg->lchan) { - LOGP(DRSL, LOGL_ERROR, "Rx RSL %s for unknow lchan\n", + LOGP(DRSL, LOGL_ERROR, "Rx RSL %s for unknown lchan\n", rsl_msg_name(dch->c.msg_type)); return rsl_reject_unknown_lchan(msg); } diff --git a/src/osmo-bts-trx/trx_if.c b/src/osmo-bts-trx/trx_if.c index f3de245..156cf9d 100644 --- a/src/osmo-bts-trx/trx_if.c +++ b/src/osmo-bts-trx/trx_if.c @@ -532,7 +532,7 @@ static int trx_data_read_cb(struct osmo_fd *ofd, unsigned int what) burst_len = EGPRS_BURST_LEN; /* Accept bursts ending with 2 bytes of padding (OpenBTS compatible trx) or without them: */ } else if (len != GSM_BURST_LEN + 10 && len != GSM_BURST_LEN + 8) { - LOGP(DTRX, LOGL_NOTICE, "Got data message with invalid lenght " + LOGP(DTRX, LOGL_NOTICE, "Got data message with invalid length " "'%d'\n", len); return -EINVAL; } diff --git a/src/osmo-bts-trx/trx_vty.c b/src/osmo-bts-trx/trx_vty.c index 1dfc617..4e86b77 100644 --- a/src/osmo-bts-trx/trx_vty.c +++ b/src/osmo-bts-trx/trx_vty.c @@ -234,7 +234,7 @@ DEFUN(cfg_phyinst_maxdlynb, cfg_phyinst_maxdlynb_cmd, OSMOTRX_STR "Set the maximum acceptable delay of a Normal Burst (in GSM symbols)." " USE FOR TESTING ONLY, DON'T CHANGE IN PRODUCTION USE!" - " During normal operation, Normal Bursts delay are controled by a Timing" + " During normal operation, Normal Bursts delay are controlled by a Timing" " Advance control loop and thus Normal Bursts arrive to a BTS with no more" " than a couple GSM symbols, which is already taken into account in osmo-trx." " So changing this setting will have no effect in production installations" -- cgit v1.2.3 From 4bd583e29b8eddc0b93a89776907e93d43c700f5 Mon Sep 17 00:00:00 2001 From: Debian Mobcom Maintainers Date: Sat, 22 Sep 2018 14:11:29 +0200 Subject: spelling =================================================================== Gbp-Pq: Name spelling.patch --- src/common/pcu_sock.c | 2 +- src/common/rsl.c | 2 +- src/osmo-bts-trx/trx_if.c | 2 +- src/osmo-bts-trx/trx_vty.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/pcu_sock.c b/src/common/pcu_sock.c index 5f94050..7b3993c 100644 --- a/src/common/pcu_sock.c +++ b/src/common/pcu_sock.c @@ -641,7 +641,7 @@ static int pcu_rx(struct gsm_network *net, uint8_t msg_type, rc = pcu_rx_txt_ind(bts, &pcu_prim->u.txt_ind); break; default: - LOGP(DPCU, LOGL_ERROR, "Received unknwon PCU msg type %d\n", + LOGP(DPCU, LOGL_ERROR, "Received unknown PCU msg type %d\n", msg_type); rc = -EINVAL; } diff --git a/src/common/rsl.c b/src/common/rsl.c index 5dd2c59..ac29885 100644 --- a/src/common/rsl.c +++ b/src/common/rsl.c @@ -2815,7 +2815,7 @@ static int rsl_rx_ipaccess(struct gsm_bts_trx *trx, struct msgb *msg) msg->lchan = lchan_lookup(trx, dch->chan_nr, "RSL rx IPACC: "); if (!msg->lchan) { - LOGP(DRSL, LOGL_ERROR, "Rx RSL %s for unknow lchan\n", + LOGP(DRSL, LOGL_ERROR, "Rx RSL %s for unknown lchan\n", rsl_msg_name(dch->c.msg_type)); return rsl_reject_unknown_lchan(msg); } diff --git a/src/osmo-bts-trx/trx_if.c b/src/osmo-bts-trx/trx_if.c index f3de245..156cf9d 100644 --- a/src/osmo-bts-trx/trx_if.c +++ b/src/osmo-bts-trx/trx_if.c @@ -532,7 +532,7 @@ static int trx_data_read_cb(struct osmo_fd *ofd, unsigned int what) burst_len = EGPRS_BURST_LEN; /* Accept bursts ending with 2 bytes of padding (OpenBTS compatible trx) or without them: */ } else if (len != GSM_BURST_LEN + 10 && len != GSM_BURST_LEN + 8) { - LOGP(DTRX, LOGL_NOTICE, "Got data message with invalid lenght " + LOGP(DTRX, LOGL_NOTICE, "Got data message with invalid length " "'%d'\n", len); return -EINVAL; } diff --git a/src/osmo-bts-trx/trx_vty.c b/src/osmo-bts-trx/trx_vty.c index 1dfc617..4e86b77 100644 --- a/src/osmo-bts-trx/trx_vty.c +++ b/src/osmo-bts-trx/trx_vty.c @@ -234,7 +234,7 @@ DEFUN(cfg_phyinst_maxdlynb, cfg_phyinst_maxdlynb_cmd, OSMOTRX_STR "Set the maximum acceptable delay of a Normal Burst (in GSM symbols)." " USE FOR TESTING ONLY, DON'T CHANGE IN PRODUCTION USE!" - " During normal operation, Normal Bursts delay are controled by a Timing" + " During normal operation, Normal Bursts delay are controlled by a Timing" " Advance control loop and thus Normal Bursts arrive to a BTS with no more" " than a couple GSM symbols, which is already taken into account in osmo-trx." " So changing this setting will have no effect in production installations" -- cgit v1.2.3