aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--configure.ac22
-rwxr-xr-xcontrib/jenkins_bts_model.sh4
-rwxr-xr-xcontrib/jenkins_oc2g.sh25
-rw-r--r--contrib/systemd/oc2gbts-mgr.service29
-rw-r--r--contrib/systemd/osmo-bts-oc2g.service21
-rw-r--r--doc/examples/oc2g/oc2gbts-mgr.cfg33
-rw-r--r--doc/examples/oc2g/osmo-bts.cfg38
-rw-r--r--include/osmo-bts/gsm_data_shared.h15
-rw-r--r--include/osmo-bts/phy_link.h14
-rw-r--r--src/Makefile.am5
-rw-r--r--src/common/gsm_data_shared.c1
-rw-r--r--src/osmo-bts-oc2g/Makefile.am38
-rw-r--r--src/osmo-bts-oc2g/calib_file.c469
-rw-r--r--src/osmo-bts-oc2g/hw_info.ver_major0
-rw-r--r--src/osmo-bts-oc2g/hw_misc.c88
-rw-r--r--src/osmo-bts-oc2g/hw_misc.h13
-rw-r--r--src/osmo-bts-oc2g/l1_if.c1755
-rw-r--r--src/osmo-bts-oc2g/l1_if.h145
-rw-r--r--src/osmo-bts-oc2g/l1_transp.h14
-rw-r--r--src/osmo-bts-oc2g/l1_transp_hw.c326
-rw-r--r--src/osmo-bts-oc2g/main.c240
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_bid.c175
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_bid.h47
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_bts.c129
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_bts.h21
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_clock.c263
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_clock.h16
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_led.c332
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_led.h22
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_mgr.c353
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_mgr.h398
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c750
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c208
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_mgr_temp.c980
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_mgr_vty.c984
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_misc.c381
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_misc.h17
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_nl.c123
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_nl.h27
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_par.c249
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_par.h43
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_power.c177
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_power.h36
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_swd.c178
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_swd.h7
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_temp.c71
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_temp.h26
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_util.c158
-rw-r--r--src/osmo-bts-oc2g/oc2gbts.c361
-rw-r--r--src/osmo-bts-oc2g/oc2gbts.h92
-rw-r--r--src/osmo-bts-oc2g/oc2gbts_vty.c733
-rw-r--r--src/osmo-bts-oc2g/oml.c2078
-rw-r--r--src/osmo-bts-oc2g/oml_router.c132
-rw-r--r--src/osmo-bts-oc2g/oml_router.h13
-rw-r--r--src/osmo-bts-oc2g/tch.c545
-rw-r--r--src/osmo-bts-oc2g/utils.c114
-rw-r--r--src/osmo-bts-oc2g/utils.h13
57 files changed, 13546 insertions, 1 deletions
diff --git a/configure.ac b/configure.ac
index f2d4f846..9a8d58f1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -279,6 +279,27 @@ if test "$enable_litecell15" = "yes"; then
CPPFLAGS=$oldCPPFLAGS
fi
+AC_MSG_CHECKING([whether to enable NuRAN Wireless OC-2G hardware support])
+AC_ARG_ENABLE(oc2g,
+ AC_HELP_STRING([--enable-oc2g],
+ [enable code for NuRAN Wireless OC-2G bts [default=no]]),
+ [enable_oc2g="yes"],[enable_oc2g="no"])
+AC_ARG_WITH([oc2g], [AS_HELP_STRING([--with-oc2g=INCLUDE_DIR], [Location of the OC-2G API header files])],
+ [oc2g_incdir="$withval"],[oc2g_incdir="$incdir"])
+AC_SUBST([OC2G_INCDIR], -I$oc2g_incdir)
+AC_MSG_RESULT([$enable_oc2g])
+AM_CONDITIONAL(ENABLE_OC2GBTS, test "x$enable_oc2g" = "xyes")
+if test "$enable_oc2g" = "yes"; then
+ oldCPPFLAGS=$CPPFLAGS
+ CPPFLAGS="$CPPFLAGS $OC2G_INCDIR -I$srcdir/include"
+ AC_CHECK_HEADER([nrw/oc2g/oc2g.h],[],
+ [AC_MSG_ERROR([nrw/oc2g/oc2g.h can not be found in $oc2g_incdir])],
+ [#include <nrw/oc2g/oc2g.h>])
+ PKG_CHECK_MODULES(LIBSYSTEMD, libsystemd)
+ PKG_CHECK_MODULES(LIBGPS, libgps)
+ CPPFLAGS=$oldCPPFLAGS
+fi
+
# https://www.freedesktop.org/software/systemd/man/daemon.html
AC_ARG_WITH([systemdsystemunitdir],
[AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files])],,
@@ -307,6 +328,7 @@ AC_OUTPUT(
src/osmo-bts-omldummy/Makefile
src/osmo-bts-sysmo/Makefile
src/osmo-bts-litecell15/Makefile
+ src/osmo-bts-oc2g/Makefile
src/osmo-bts-trx/Makefile
src/osmo-bts-octphy/Makefile
include/Makefile
diff --git a/contrib/jenkins_bts_model.sh b/contrib/jenkins_bts_model.sh
index 2488f715..9aa943fa 100755
--- a/contrib/jenkins_bts_model.sh
+++ b/contrib/jenkins_bts_model.sh
@@ -30,6 +30,10 @@ case "$bts_model" in
./contrib/jenkins_lc15.sh
;;
+ oc2g)
+ ./contrib/jenkins_oc2g.sh
+ ;;
+
trx)
./contrib/jenkins_bts_trx.sh
;;
diff --git a/contrib/jenkins_oc2g.sh b/contrib/jenkins_oc2g.sh
new file mode 100755
index 00000000..b8badce6
--- /dev/null
+++ b/contrib/jenkins_oc2g.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+# jenkins build helper script for osmo-bts-oc2g
+
+# 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 oc2g "$FIRMWARE_VERSION"
+
+configure_flags="\
+ --enable-sanitize \
+ --with-oc2g=$deps/layer1-headers/inc/ \
+ --enable-oc2g \
+ "
+
+build_bts "osmo-bts-oc2g" "$configure_flags"
+
+osmo-clean-workspace.sh
diff --git a/contrib/systemd/oc2gbts-mgr.service b/contrib/systemd/oc2gbts-mgr.service
new file mode 100644
index 00000000..ed915b33
--- /dev/null
+++ b/contrib/systemd/oc2gbts-mgr.service
@@ -0,0 +1,29 @@
+[Unit]
+Description=osmo-bts manager for OC-2G
+After=oc2g-sysdev-remap.service
+Wants=oc2g-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/oc2gbts-mgr || mkdir -p /mnt/storage/var/run/oc2gbts-mgr ; test -d /var/run/oc2gbts-mgr || ln -sf /mnt/storage/var/run/oc2gbts-mgr/ /var/run'
+# Make sure BTS operation hour exist
+ExecStartPre=/bin/sh -c 'test -f /mnt/storage/var/run/oc2gbts-mgr/hours-running || echo 0 > /mnt/storage/var/run/oc2gbts-mgr/hours-running'
+# Shutdown all PA correctly
+ExecStartPre=/bin/sh -c 'echo disabled > /var/oc2g/pa-state/pa0/state;'
+#ExecStartPre=/bin/sh -c 'echo 0 > /var/oc2g/pa-supply/max_microvolts; echo 0 > /var/oc2g/pa-supply/min_microvolts'
+
+ExecStart=/usr/bin/oc2gbts-mgr -s -c /etc/osmocom/oc2gbts-mgr.cfg
+
+# Shutdown all PA correctly
+ExecStopPost=/bin/sh -c 'echo disabled > /var/oc2g/pa-state/pa0/state;'
+#ExecStopPost=/bin/sh -c 'echo 0 > /var/oc2g/pa-supply/max_microvolts; echo 0 > /var/oc2g/pa-supply/min_microvolts'
+
+[Install]
+WantedBy=multi-user.target
+Alias=osmo-bts-mgr.service
diff --git a/contrib/systemd/osmo-bts-oc2g.service b/contrib/systemd/osmo-bts-oc2g.service
new file mode 100644
index 00000000..2f2d8378
--- /dev/null
+++ b/contrib/systemd/osmo-bts-oc2g.service
@@ -0,0 +1,21 @@
+[Unit]
+Description=osmo-bts for OC-2G
+
+[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-oc2g -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/doc/examples/oc2g/oc2gbts-mgr.cfg b/doc/examples/oc2g/oc2gbts-mgr.cfg
new file mode 100644
index 00000000..8248f60d
--- /dev/null
+++ b/doc/examples/oc2g/oc2gbts-mgr.cfg
@@ -0,0 +1,33 @@
+!
+! oc2gbts-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
+!
+oc2gbts-mgr
+ limits supply_volt
+ threshold warning min 17500
+ threshold critical min 19000
+ limits supply_pwr
+ threshold warning max 110
+ threshold critical max 120
diff --git a/doc/examples/oc2g/osmo-bts.cfg b/doc/examples/oc2g/osmo-bts.cfg
new file mode 100644
index 00000000..f985f3bc
--- /dev/null
+++ b/doc/examples/oc2g/osmo-bts.cfg
@@ -0,0 +1,38 @@
+!
+! 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
+bts 0
+ band 900
+ ipa unit-id 1500 0
+ oml remote-ip 10.42.0.1
+ trx 0
+ phy 0 instance 0
diff --git a/include/osmo-bts/gsm_data_shared.h b/include/osmo-bts/gsm_data_shared.h
index 794eaeae..61cf81f6 100644
--- a/include/osmo-bts/gsm_data_shared.h
+++ b/include/osmo-bts/gsm_data_shared.h
@@ -289,6 +289,8 @@ struct gsm_lchan {
/* indicates if DTXd was active during DL measurement
period */
bool dl_active;
+ /* last UL SPEECH resume flag */
+ bool is_speech_resume;
} dtx;
uint8_t last_cmr;
uint32_t last_fn;
@@ -411,6 +413,9 @@ struct gsm_bts_trx {
uint16_t arfcn;
int nominal_power; /* in dBm */
unsigned int max_power_red; /* in actual dB */
+ uint8_t max_power_backoff_8psk; /* in actual dB OC-2G only */
+ uint8_t c0_idle_power_red; /* in actual dB OC-2G only */
+
struct trx_power_params power_params;
int ms_power_control;
@@ -437,6 +442,7 @@ struct gsm_bts_trx {
enum gsm_bts_type_variant {
BTS_UNKNOWN,
BTS_OSMO_LITECELL15,
+ BTS_OSMO_OC2G,
BTS_OSMO_OCTPHY,
BTS_OSMO_SYSMO,
BTS_OSMO_TRX,
@@ -746,7 +752,14 @@ struct gsm_bts {
struct timeval tv_clock;
struct osmo_timer_list fn_timer;
} vbts;
-
+#ifdef ENABLE_OC2GBTS
+ /* specific to Open Cellular 2G BTS */
+ struct {
+ uint8_t led_ctrl_mode; /* 0: control by BTS, 1: not control by BTS */
+ struct llist_head ceased_alarm_list; /* ceased alarm list*/
+ unsigned int rtp_drift_thres_ms; /* RTP timestamp drift detection threshold */
+ } oc2g;
+#endif
};
diff --git a/include/osmo-bts/phy_link.h b/include/osmo-bts/phy_link.h
index 36e34e1d..2472c051 100644
--- a/include/osmo-bts/phy_link.h
+++ b/include/osmo-bts/phy_link.h
@@ -135,6 +135,20 @@ struct phy_instance {
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;
+ struct {
+ /* configuration */
+ uint32_t dsp_trace_f;
+ char *calib_path;
+ int minTxPower;
+ int maxTxPower;
+ struct oc2gl1_hdl *hdl;
+ uint8_t max_cell_size; /* 0:166 qbits*/
+ 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 */
+ uint8_t tx_c0_idle_pwr_red; /* C0 idle slot Tx power reduction level in dB */
+ } oc2g;
} u;
};
diff --git a/src/Makefile.am b/src/Makefile.am
index 501591d6..70e4d968 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -15,3 +15,8 @@ endif
if ENABLE_LC15BTS
SUBDIRS += osmo-bts-litecell15
endif
+
+if ENABLE_OC2GBTS
+SUBDIRS += osmo-bts-oc2g
+endif
+
diff --git a/src/common/gsm_data_shared.c b/src/common/gsm_data_shared.c
index 588d0fdd..21e57814 100644
--- a/src/common/gsm_data_shared.c
+++ b/src/common/gsm_data_shared.c
@@ -71,6 +71,7 @@ const char *btsatttr2str(enum bts_attribute v)
const struct value_string osmo_bts_variant_names[_NUM_BTS_VARIANT + 1] = {
{ BTS_UNKNOWN, "unknown" },
{ BTS_OSMO_LITECELL15, "osmo-bts-lc15" },
+ { BTS_OSMO_OC2G, "osmo-bts-oc2g" },
{ BTS_OSMO_OCTPHY, "osmo-bts-octphy" },
{ BTS_OSMO_SYSMO, "osmo-bts-sysmo" },
{ BTS_OSMO_TRX, "omso-bts-trx" },
diff --git a/src/osmo-bts-oc2g/Makefile.am b/src/osmo-bts-oc2g/Makefile.am
new file mode 100644
index 00000000..7188626f
--- /dev/null
+++ b/src/osmo-bts-oc2g/Makefile.am
@@ -0,0 +1,38 @@
+AUTOMAKE_OPTIONS = subdir-objects
+
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include $(OC2G_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_OC2GBTS
+
+EXTRA_DIST = misc/oc2gbts_mgr.h misc/oc2gbts_misc.h misc/oc2gbts_par.h misc/oc2gbts_led.h \
+ misc/oc2gbts_temp.h misc/oc2gbts_power.h misc/oc2gbts_clock.h \
+ misc/oc2gbts_bid.h misc/oc2gbts_nl.h \
+ hw_misc.h l1_if.h l1_transp.h oc2gbts.h oml_router.h utils.h
+
+bin_PROGRAMS = osmo-bts-oc2g oc2gbts-mgr oc2gbts-util
+
+COMMON_SOURCES = main.c oc2gbts.c l1_if.c oml.c oc2gbts_vty.c tch.c hw_misc.c calib_file.c \
+ utils.c misc/oc2gbts_par.c misc/oc2gbts_bid.c oml_router.c
+
+osmo_bts_oc2g_SOURCES = $(COMMON_SOURCES) l1_transp_hw.c
+osmo_bts_oc2g_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD)
+
+oc2gbts_mgr_SOURCES = \
+ misc/oc2gbts_mgr.c misc/oc2gbts_misc.c \
+ misc/oc2gbts_par.c misc/oc2gbts_nl.c \
+ misc/oc2gbts_temp.c misc/oc2gbts_power.c \
+ misc/oc2gbts_clock.c misc/oc2gbts_bid.c \
+ misc/oc2gbts_mgr_vty.c \
+ misc/oc2gbts_mgr_nl.c \
+ misc/oc2gbts_mgr_temp.c \
+ misc/oc2gbts_mgr_calib.c \
+ misc/oc2gbts_led.c \
+ misc/oc2gbts_bts.c \
+ misc/oc2gbts_swd.c
+
+oc2gbts_mgr_LDADD = $(top_builddir)/src/common/libbts.a $(LIBGPS_LIBS) $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCTRL_LIBS) $(LIBSYSTEMD_LIBS) $(COMMON_LDADD)
+
+oc2gbts_util_SOURCES = misc/oc2gbts_util.c misc/oc2gbts_par.c
+oc2gbts_util_LDADD = $(LIBOSMOCORE_LIBS)
diff --git a/src/osmo-bts-oc2g/calib_file.c b/src/osmo-bts-oc2g/calib_file.c
new file mode 100644
index 00000000..49768480
--- /dev/null
+++ b/src/osmo-bts-oc2g/calib_file.c
@@ -0,0 +1,469 @@
+/* NuRAN Wireless OC-2G BTS L1 calibration file routines*/
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org>
+ *
+ * Based on sysmoBTS:
+ * (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <errno.h>
+
+#include <osmocom/core/utils.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+
+#include <nrw/oc2g/oc2g.h>
+#include <nrw/oc2g/gsml1const.h>
+
+#include "l1_if.h"
+#include "oc2gbts.h"
+#include "utils.h"
+#include "osmo-bts/oml.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_rx0.conf",
+ .rx = 1,
+ .trx = 0,
+ .rxpath = 0,
+ }, {
+ .fname = "calib_tx0.conf",
+ .rx = 0,
+ .trx = 0,
+ },
+};
+
+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 oc2gl1_hdl *fl1h,
+ const struct calib_file_desc *desc);
+static int calib_verify(struct oc2gl1_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 oc2gl1_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 oc2gl1_hdl *fl1h,
+ const struct calib_file_desc *desc)
+{
+ struct calib_send_state *st = &fl1h->st;
+ char *calib_path = fl1h->phy_inst->u.oc2g.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_NOTICE, "Failed to open '%s' for calibration data.\n", fname);
+
+ /* TODO (oramadan): Fix OML alarms
+ if( fl1h->phy_inst->trx ){
+ fl1h->phy_inst->trx->mo.obj_inst.trx_nr = fl1h->phy_inst->trx->nr;
+
+ alarm_sig_data.mo = &fl1h->phy_inst->trx->mo;
+ alarm_sig_data.add_text = (char*)&fname[0];
+ osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_FAIL_OPEN_CALIB_ALARM, &alarm_sig_data);
+ }
+ */
+ return -1;
+ }
+ return 0;
+}
+
+static int calib_file_close(struct oc2gl1_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 oc2gl1_hdl *fl1h)
+{
+ struct calib_send_state *st = &fl1h->st;
+ Oc2g_Prim_t *prim;
+ struct msgb *msg;
+ size_t n;
+
+ msg = sysp_msgb_alloc();
+ prim = msgb_sysprim(msg);
+
+ prim->id = Oc2g_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 oc2gl1_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_NOTICE,"Verify L1 calibration table %s -> failed (%d)\n", desc->fname, rc);
+
+ /* TODO (oramadan): Fix OML alarms
+ if (fl1h->phy_inst->trx) {
+ fl1h->phy_inst->trx->mo.obj_inst.trx_nr = fl1h->phy_inst->trx->nr;
+
+ alarm_sig_data.mo = &fl1h->phy_inst->trx->mo;
+ alarm_sig_data.add_text = (char*)&desc->fname[0];
+ memcpy(alarm_sig_data.spare, &rc, sizeof(int));
+ osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_FAIL_VERIFY_CALIB_ALARM, &alarm_sig_data);
+ }
+ */
+
+ 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 oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
+ struct calib_send_state *st = &fl1h->st;
+ Oc2g_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 oc2gl1_hdl *fl1h)
+{
+ int rc;
+ struct calib_send_state *st = &fl1h->st;
+ char *calib_path = fl1h->phy_inst->u.oc2g.calib_path;
+
+ if (!calib_path) {
+ LOGP(DL1C, LOGL_NOTICE, "Calibration file path not specified\n");
+
+ /* TODO (oramadan): Fix OML alarms
+ if( fl1h->phy_inst->trx ){
+ fl1h->phy_inst->trx->mo.obj_inst.trx_nr = fl1h->phy_inst->trx->nr;
+
+ alarm_sig_data.mo = &fl1h->phy_inst->trx->mo;
+ osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_NO_CALIB_PATH_ALARM, &alarm_sig_data);
+ }
+ */
+ 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 oc2gl1_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 == 0xFFF) {
+ 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.oc2g.minTxPower = calTbl->u8RawData[calTbl->hdr.v1.toc.u32DataOfst + (5 << 2)];
+ fl1h->phy_inst->u.oc2g.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.oc2g.maxTxPower) {
+ LOGP(DL1C, LOGL_INFO, "Set TRX %u nominal Tx power to %d dBm (%d)\n",
+ plink->num,
+ fl1h->phy_inst->u.oc2g.maxTxPower,
+ fl1h->phy_inst->trx->nominal_power);
+
+ fl1h->phy_inst->trx->nominal_power = fl1h->phy_inst->u.oc2g.maxTxPower;
+ }
+
+ if (fl1h->phy_inst->trx->nominal_power < fl1h->phy_inst->u.oc2g.minTxPower) {
+ LOGP(DL1C, LOGL_INFO, "Set TRX %u nominal Tx power to %d dBm (%d)\n",
+ plink->num,
+ fl1h->phy_inst->u.oc2g.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.oc2g.maxTxPower) ) {
+ LOGP(DL1C, LOGL_INFO, "Set TRX %u Tx power parameter to %d dBm (%d)\n",
+ plink->num,
+ to_mdB(fl1h->phy_inst->u.oc2g.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.oc2g.maxTxPower);
+ }
+
+ if (fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm < to_mdB(fl1h->phy_inst->u.oc2g.minTxPower) ) {
+ LOGP(DL1C, LOGL_INFO, "Set TRX %u Tx power parameter to %d dBm (%d)\n",
+ plink->num,
+ to_mdB(fl1h->phy_inst->u.oc2g.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.oc2g.minTxPower);
+ }
+
+ LOGP(DL1C, LOGL_DEBUG, "%s: minTxPower=%d, maxTxPower=%d\n",
+ desc->fname,
+ fl1h->phy_inst->u.oc2g.minTxPower,
+ fl1h->phy_inst->u.oc2g.maxTxPower );
+ }
+
+ /* rewind read pointer for subsequence tasks */
+ fseek(st->fp, 0L, SEEK_SET);
+ free(rbuf);
+
+ return 0;
+}
+
diff --git a/src/osmo-bts-oc2g/hw_info.ver_major b/src/osmo-bts-oc2g/hw_info.ver_major
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/osmo-bts-oc2g/hw_info.ver_major
diff --git a/src/osmo-bts-oc2g/hw_misc.c b/src/osmo-bts-oc2g/hw_misc.c
new file mode 100644
index 00000000..31daf078
--- /dev/null
+++ b/src/osmo-bts-oc2g/hw_misc.c
@@ -0,0 +1,88 @@
+/* Misc HW routines for NuRAN Wireless OC-2G BTS */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/utils.h>
+
+#include "hw_misc.h"
+
+int oc2gbts_led_set(enum oc2gbts_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/oc2g/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/oc2g/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-oc2g/hw_misc.h b/src/osmo-bts-oc2g/hw_misc.h
new file mode 100644
index 00000000..b8f3332a
--- /dev/null
+++ b/src/osmo-bts-oc2g/hw_misc.h
@@ -0,0 +1,13 @@
+#ifndef _HW_MISC_H
+#define _HW_MISC_H
+
+enum oc2gbts_led_color {
+ LED_OFF,
+ LED_RED,
+ LED_GREEN,
+ LED_ORANGE,
+};
+
+int oc2gbts_led_set(enum oc2gbts_led_color c);
+
+#endif
diff --git a/src/osmo-bts-oc2g/l1_if.c b/src/osmo-bts-oc2g/l1_if.c
new file mode 100644
index 00000000..d8fdd968
--- /dev/null
+++ b/src/osmo-bts-oc2g/l1_if.c
@@ -0,0 +1,1755 @@
+/* Interface handler for NuRAN Wireless OC-2G L1 */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org>
+ *
+ * Based on sysmoBTS:
+ * (C) 2011-2014 by Harald Welte <laforge@gnumonks.org>
+ * (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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/lapdm.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/paging.h>
+#include <osmo-bts/measurement.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/handover.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/msg_utils.h>
+#include <osmo-bts/dtx_dl_amr_fsm.h>
+
+#include <nrw/oc2g/oc2g.h>
+#include <nrw/oc2g/gsml1prim.h>
+#include <nrw/oc2g/gsml1const.h>
+#include <nrw/oc2g/gsml1types.h>
+
+#include "oc2gbts.h"
+#include "l1_if.h"
+#include "l1_transp.h"
+#include "hw_misc.h"
+#include "misc/oc2gbts_par.h"
+#include "misc/oc2gbts_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(oc2gbts_sysprim_names, wlc->conf_prim_id));
+ else
+ LOGP(DL1C, LOGL_FATAL, "Timeout waiting for L1 primitive %s\n",
+ get_value_string(oc2gbts_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 oc2gl1_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_DEBUG, "Tx L1 prim %s\n",
+ get_value_string(oc2gbts_l1prim_names, l1p->id));
+
+ if (oc2gbts_get_l1prim_type(l1p->id) != L1P_T_REQ) {
+ LOGP(DL1C, LOGL_ERROR, "L1 Prim %s is not a Request!\n",
+ get_value_string(oc2gbts_l1prim_names, l1p->id));
+ talloc_free(wlc);
+ return -EINVAL;
+ }
+ wlc->is_sys_prim = 0;
+ wlc->conf_prim_id = oc2gbts_get_l1prim_conf(l1p->id);
+ wlc->conf_hLayer3 = l1p_get_hLayer3(l1p);
+ wqueue = &fl1h->write_q[MQ_L1_WRITE];
+ timeout_secs = 30;
+ } else {
+ Oc2g_Prim_t *sysp = msgb_sysprim(msg);
+
+ LOGP(DL1C, LOGL_DEBUG, "Tx SYS prim %s\n",
+ get_value_string(oc2gbts_sysprim_names, sysp->id));
+
+ if (oc2gbts_get_sysprim_type(sysp->id) != L1P_T_REQ) {
+ LOGP(DL1C, LOGL_ERROR, "SYS Prim %s is not a Request!\n",
+ get_value_string(oc2gbts_sysprim_names, sysp->id));
+ talloc_free(wlc);
+ return -EINVAL;
+ }
+ wlc->is_sys_prim = 1;
+ wlc->conf_prim_id = oc2gbts_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 oc2gl1_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 oc2gl1_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 Oc2g_Prim_t */
+struct msgb *sysp_msgb_alloc(void)
+{
+ struct msgb *msg = msgb_alloc(sizeof(Oc2g_Prim_t), "sys_prim");
+
+ if (msg)
+ msg->l1h = msgb_put(msg, sizeof(Oc2g_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 oc2gl1_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 oc2gl1_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;
+}
+
+/* fill frame PH-DATA.req from l1sap primitive */
+static GsmL1_PhDataReq_t *
+fill_req_from_l1sap(GsmL1_Prim_t *l1p, struct oc2gl1_hdl *fl1,
+ uint8_t tn, uint32_t fn, uint8_t sapi, uint8_t sub_ch,
+ uint8_t block_nr)
+{
+ GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq;
+ GsmL1_MsgUnitParam_t *msu_param;
+ uint8_t *l1_payload;
+
+ msu_param = &data_req->msgUnitParam;
+ l1_payload = &msu_param->u8Buffer[0];
+ l1p->id = GsmL1_PrimId_PhDataReq;
+
+ memset(l1_payload, 0x2B, GSM_MACBLOCK_LEN);
+ /* address field */
+ l1_payload[0] = 0x03;
+ /* control field */
+ l1_payload[1] = 0x03;
+ /* length field */
+ l1_payload[2] = 0x01;
+
+ /* copy fields from PH-RTS.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 = GSM_MACBLOCK_LEN;
+
+
+ LOGP(DL1C, LOGL_DEBUG, "Send fill frame on in none DTX mode Tn=%d, Fn=%d, SAPI=%d, SubCh=%d, BlockNr=%d dump=%s\n",
+ tn,
+ fn,
+ sapi,
+ sub_ch,
+ block_nr,
+ osmo_hexdump(data_req->msgUnitParam.u8Buffer, data_req->msgUnitParam.u8Size));
+
+ return data_req;
+}
+
+static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg,
+ struct osmo_phsap_prim *l1sap, bool use_cache)
+{
+ struct oc2gl1_hdl *fl1 = trx_oc2gl1_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_CBCH(chan_nr)) {
+ sapi = GsmL1_Sapi_Cbch;
+ } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) {
+ /* The sapi depends on DSP configuration, not
+ * on the actual SYSTEM INFORMATION 3. */
+ u8BlockNbr = l1sap_fn2ccch_block(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 {
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1msg);
+ if (lchan->rsl_cmode == RSL_CMOD_SPD_SIGN)
+ /* fill frame */
+ fill_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr);
+ else {
+ if (lchan->ts->trx->bts->dtxd)
+ /* empty frame */
+ empty_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr);
+ else
+ /* fill frame */
+ fill_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 oc2gl1_hdl *fl1 = trx_oc2gl1_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 oc2gl1_hdl *fl1 = trx_oc2gl1_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 oc2gl1_hdl *fl1,
+ GsmL1_MphTimeInd_t *time_ind,
+ struct msgb *msg)
+{
+ struct gsm_bts_trx *trx = oc2gl1_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_Cbch:
+ cbits = 0xc8 >> 3; /* Osmocom extension for CBCH via L1SAP */
+ 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:
+ 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 GsmL1_Sapi_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 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 oc2gl1_hdl *fl1,
+ GsmL1_PhReadyToSendInd_t *rts_ind,
+ struct msgb *l1p_msg)
+{
+ struct gsm_bts_trx *trx = oc2gl1_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(oc2gbts_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 oc2gl1_hdl *fl1, GsmL1_PhDataInd_t *data_ind,
+ struct msgb *l1p_msg)
+{
+ struct gsm_bts_trx *trx = oc2gl1_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(oc2gbts_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(oc2gbts_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 oc2gl1_hdl *fl1, GsmL1_PhRaInd_t *ra_ind,
+ struct msgb *l1p_msg)
+{
+ struct gsm_bts_trx *trx = oc2gl1_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-oc2g */
+ 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 oc2gl1_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 oc2gl1_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(oc2gbts_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(oc2gl1_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 oc2gl1_hdl *fl1h, struct msgb *msg)
+{
+ Oc2g_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(oc2gbts_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(oc2gl1_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)
+{
+ Oc2g_Prim_t *sysp = msgb_sysprim(resp);
+ GsmL1_Status_t status;
+ int on = 0;
+ unsigned int i;
+ struct gsm_bts *bts = trx->bts;
+
+ if (sysp->id == Oc2g_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(oc2gbts_l1status_names, status));
+
+
+ if (on) {
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_FATAL, "RF-ACT.conf with status %s\n",
+ get_value_string(oc2gbts_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 oc2gl1_hdl *hdl, int on)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ Oc2g_Prim_t *sysp = msgb_sysprim(msg);
+ struct phy_instance *pinst = hdl->phy_inst;
+
+ if (on) {
+ sysp->id = Oc2g_PrimId_ActivateRfReq;
+ sysp->u.activateRfReq.msgq.u8UseTchMsgq = 0;
+ sysp->u.activateRfReq.msgq.u8UsePdtchMsgq = pcu_direct;
+
+ sysp->u.activateRfReq.u8UnusedTsMode = pinst->u.oc2g.pedestal_mode;
+
+ /* maximum cell size in quarter-bits, 90 == 12.456 km */
+ sysp->u.activateRfReq.u8MaxCellSize = pinst->u.oc2g.max_cell_size;
+
+ /* auto tx power adjustment mode 0:none, 1: automatic*/
+ sysp->u.activateRfReq.u8EnAutoPowerAdjust = pinst->u.oc2g.tx_pwr_adj_mode;
+
+ } else {
+ sysp->id = Oc2g_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 oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
+ Oc2g_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(oc2gbts_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(oc2gbts_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 oc2gl1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ Oc2g_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 = Oc2g_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)
+{
+ Oc2g_Prim_t *sysp = msgb_sysprim(resp);
+ Oc2g_SystemInfoCnf_t *sic = &sysp->u.systemInfoCnf;
+ struct oc2gl1_hdl *fl1h = trx_oc2gl1_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);
+
+ LOGP(DL1C, LOGL_INFO, "Band support %s", gsm_band_name(fl1h->hw_info.band_support));
+
+ 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 oc2gl1_hdl *hdl)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ Oc2g_Prim_t *sysp = msgb_sysprim(msg);
+
+ sysp->id = Oc2g_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 oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
+ Oc2g_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(oc2gbts_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(oc2gbts_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 oc2gl1_hdl *hdl)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ Oc2g_Prim_t *sysp = msgb_sysprim(msg);
+ sysp->id = Oc2g_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 oc2gl1_hdl *hdl, uint32_t flags)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ Oc2g_Prim_t *sysp = msgb_sysprim(msg);
+
+ LOGP(DL1C, LOGL_INFO, "Tx SET-TRACE-FLAGS.req (0x%08x)\n",
+ flags);
+
+ sysp->id = Oc2g_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 oc2gl1_hdl *fl1h)
+{
+ int rc = 0;
+ char rev_maj = 0, rev_min = 0;
+
+ oc2gbts_rev_get(&rev_maj, &rev_min);
+ if (rc < 0)
+ return rc;
+ fl1h->hw_info.ver_major = (uint8_t)rev_maj;
+ fl1h->hw_info.ver_minor = (uint8_t)rev_min;
+
+ rc = oc2gbts_model_get();
+ if (rc < 0)
+ return rc;
+ fl1h->hw_info.options = (uint32_t)rc;
+
+ rc = oc2gbts_option_get(OC2GBTS_OPTION_BAND);
+ if (rc < 0)
+ return rc;
+
+ switch (rc) {
+ case OC2GBTS_BAND_850:
+ fl1h->hw_info.band_support = GSM_BAND_850;
+ break;
+ case OC2GBTS_BAND_900:
+ fl1h->hw_info.band_support = GSM_BAND_900;
+ break;
+ case OC2GBTS_BAND_1800:
+ fl1h->hw_info.band_support = GSM_BAND_1800;
+ break;
+ case OC2GBTS_BAND_1900:
+ fl1h->hw_info.band_support = GSM_BAND_1900;
+ break;
+ default:
+ return -1;
+ }
+ return 0;
+}
+
+struct oc2gl1_hdl *l1if_open(struct phy_instance *pinst)
+{
+ struct oc2gl1_hdl *fl1h;
+ int rc;
+
+ LOGP(DL1C, LOGL_INFO, "OC-2G BTS L1IF compiled against API headers "
+ "v%u.%u.%u\n", OC2G_API_VERSION >> 16,
+ (OC2G_API_VERSION >> 8) & 0xff,
+ OC2G_API_VERSION & 0xff);
+
+ fl1h = talloc_zero(pinst, struct oc2gl1_hdl);
+ if (!fl1h)
+ return NULL;
+ INIT_LLIST_HEAD(&fl1h->wlc_list);
+
+ fl1h->phy_inst = pinst;
+ fl1h->dsp_trace_f = pinst->u.oc2g.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 oc2gl1_hdl *fl1h)
+{
+ l1if_transport_close(MQ_L1_WRITE, fl1h);
+ l1if_transport_close(MQ_SYS_WRITE, fl1h);
+ return 0;
+}
+
+/* TODO(oramadan) MERGE */
+#ifdef MERGE_ME
+static void dsp_alive_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data)
+{
+ Oc2g_Prim_t *sysp = msgb_sysprim(resp);
+ Oc2g_IsAliveCnf_t *sac = &sysp->u.isAliveCnf;
+ struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
+
+ fl1h->hw_alive.dsp_alive_cnt++;
+ LOGP(DL1C, LOGL_DEBUG, "Rx SYS prim %s, status=%d (%d)\n",
+ get_value_string(oc2gbts_sysprim_names, sysp->id), sac->status, trx->nr);
+
+ msgb_free(resp);
+}
+
+static int dsp_alive_timer_cb(void *data)
+{
+ struct oc2gl1_hdl *fl1h = data;
+ struct gsm_bts_trx *trx = fl1h->phy_inst->trx;
+ struct msgb *msg = sysp_msgb_alloc();
+ int rc;
+ struct oml_alarm_list *alarm_sent;
+
+ Oc2g_Prim_t *sys_prim = msgb_sysprim(msg);
+ sys_prim->id = Oc2g_PrimId_IsAliveReq;
+
+ if (fl1h->hw_alive.dsp_alive_cnt == 0) {
+ /* check for the alarm has already sent or not */
+ llist_for_each_entry(alarm_sent, &fl1h->alarm_list, list) {
+ llist_del(&alarm_sent->list);
+ if (alarm_sent->alarm_signal != S_NM_OML_BTS_DSP_ALIVE_ALARM)
+ continue;
+
+ LOGP(DL1C, LOGL_ERROR, "Alarm %d has removed from sent alarm list (%d)\n", alarm_sent->alarm_signal, trx->nr);
+ exit(23);
+ }
+
+ LOGP(DL1C, LOGL_ERROR, "Timeout waiting for SYS prim %s primitive (%d)\n",
+ get_value_string(oc2gbts_sysprim_names, sys_prim->id + 1), trx->nr);
+
+ if( fl1h->phy_inst->trx ){
+ fl1h->phy_inst->trx->mo.obj_inst.trx_nr = fl1h->phy_inst->trx->nr;
+
+ alarm_sig_data.mo = &fl1h->phy_inst->trx->mo;
+ memcpy(alarm_sig_data.spare, &sys_prim->id, sizeof(unsigned int));
+ osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_DSP_ALIVE_ALARM, &alarm_sig_data);
+ if (!alarm_sig_data.rc) {
+ /* allocate new list of sent alarms */
+ alarm_sent = talloc_zero(fl1h, struct oml_alarm_list);
+ if (!alarm_sent)
+ return -EIO;
+
+ alarm_sent->alarm_signal = S_NM_OML_BTS_DSP_ALIVE_ALARM;
+ /* add alarm to sent list */
+ llist_add(&alarm_sent->list, &fl1h->alarm_list);
+ LOGP(DL1C, LOGL_ERROR, "Alarm %d has added to sent alarm list (%d)\n", alarm_sent->alarm_signal, trx->nr);
+ }
+ }
+ }
+
+ LOGP(DL1C, LOGL_DEBUG, "Tx SYS prim %s (%d)\n",
+ get_value_string(oc2gbts_sysprim_names, sys_prim->id), trx->nr);
+
+ rc = l1if_req_compl(fl1h, msg, dsp_alive_compl_cb, NULL);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_FATAL, "Failed to send %s primitive\n", get_value_string(oc2gbts_sysprim_names, sys_prim->id));
+ return -EIO;
+ }
+
+ /* restart timer */
+ fl1h->hw_alive.dsp_alive_cnt = 0;
+ osmo_timer_schedule(&fl1h->hw_alive.dsp_alive_timer, fl1h->hw_alive.dsp_alive_period, 0);
+
+ return 0;
+}
+#endif
+
+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.oc2g.hdl = l1if_open(pinst);
+ if (!pinst->u.oc2g.hdl) {
+ LOGP(DL1C, LOGL_FATAL, "Cannot open L1 interface\n");
+ return -EIO;
+ }
+
+ /* Set default PHY parameters */
+ if (!pinst->u.oc2g.max_cell_size)
+ pinst->u.oc2g.max_cell_size = OC2G_BTS_MAX_CELL_SIZE_DEFAULT;
+
+ if (!pinst->u.oc2g.pedestal_mode)
+ pinst->u.oc2g.pedestal_mode = OC2G_BTS_PEDESTAL_MODE_DEFAULT;
+
+ if (!pinst->u.oc2g.dsp_alive_period)
+ pinst->u.oc2g.dsp_alive_period = OC2G_BTS_DSP_ALIVE_TMR_DEFAULT;
+
+ if (!pinst->u.oc2g.tx_pwr_adj_mode)
+ pinst->u.oc2g.tx_pwr_adj_mode = OC2G_BTS_TX_PWR_ADJ_DEFAULT;
+
+ if (!pinst->u.oc2g.tx_pwr_red_8psk)
+ pinst->u.oc2g.tx_pwr_red_8psk = OC2G_BTS_TX_RED_PWR_8PSK_DEFAULT;
+
+ if (!pinst->u.oc2g.tx_c0_idle_pwr_red)
+ pinst->u.oc2g.tx_c0_idle_pwr_red = OC2G_BTS_TX_C0_IDLE_RED_PWR_DEFAULT;
+
+ struct oc2gl1_hdl *fl1h = pinst->u.oc2g.hdl;
+ fl1h->dsp_trace_f = dsp_trace;
+
+ l1if_reset(pinst->u.oc2g.hdl);
+
+ phy_link_state_set(plink, PHY_LINK_CONNECTED);
+
+ /* TODO (oramadan) MERGE)
+ / * Send first IS_ALIVE primitive * /
+ struct msgb *msg = sysp_msgb_alloc();
+ int rc;
+
+ Oc2g_Prim_t *sys_prim = msgb_sysprim(msg);
+ sys_prim->id = Oc2g_PrimId_IsAliveReq;
+
+ rc = l1if_req_compl(fl1h, msg, dsp_alive_compl_cb, NULL);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_FATAL, "Failed to send %s primitive\n", get_value_string(oc2gbts_sysprim_names, sys_prim->id));
+ return -EIO;
+ }
+
+ /* initialize DSP heart beat alive timer * /
+ fl1h->hw_alive.dsp_alive_timer.cb = dsp_alive_timer_cb;
+ fl1h->hw_alive.dsp_alive_timer.data = fl1h;
+ fl1h->hw_alive.dsp_alive_cnt = 0;
+ fl1h->hw_alive.dsp_alive_period = pinst->u.oc2g.dsp_alive_period;
+ osmo_timer_schedule(&fl1h->hw_alive.dsp_alive_timer, fl1h->hw_alive.dsp_alive_period, 0); */
+ return 0;
+}
diff --git a/src/osmo-bts-oc2g/l1_if.h b/src/osmo-bts-oc2g/l1_if.h
new file mode 100644
index 00000000..38699e01
--- /dev/null
+++ b/src/osmo-bts-oc2g/l1_if.h
@@ -0,0 +1,145 @@
+#ifndef _L1_IF_H
+#define _L1_IF_H
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/gsmtap_util.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmo-bts/phy_link.h>
+
+#include <nrw/oc2g/gsml1prim.h>
+#include <nrw/oc2g/gsml1types.h>
+
+#include <stdbool.h>
+
+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 oc2gl1_hdl {
+ struct gsm_time gsm_time;
+ HANDLE 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 llist_head alarm_list; /* list of sent alarms */
+
+ 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;
+ uint32_t options;
+ } hw_info;
+
+ struct calib_send_state st;
+
+ uint8_t last_rf_mute[8];
+
+ struct {
+ struct osmo_timer_list dsp_alive_timer;
+ unsigned int dsp_alive_cnt;
+ uint8_t dsp_alive_period;
+ } hw_alive;
+};
+
+#define msgb_l1prim(msg) ((GsmL1_Prim_t *)(msg)->l1h)
+#define msgb_sysprim(msg) ((Oc2g_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 oc2gl1_hdl *fl1h, struct msgb *msg,
+ l1if_compl_cb *cb, void *cb_data);
+int l1if_gsm_req_compl(struct oc2gl1_hdl *fl1h, struct msgb *msg,
+ l1if_compl_cb *cb, void *cb_data);
+
+struct oc2gl1_hdl *l1if_open(struct phy_instance *pinst);
+int l1if_close(struct oc2gl1_hdl *hdl);
+int l1if_reset(struct oc2gl1_hdl *hdl);
+int l1if_activate_rf(struct oc2gl1_hdl *hdl, int on);
+int l1if_set_trace_flags(struct oc2gl1_hdl *hdl, uint32_t flags);
+int l1if_set_txpower(struct oc2gl1_hdl *fl1h, float tx_power);
+int l1if_set_txpower_backoff_8psk(struct oc2gl1_hdl *fl1h, uint8_t backoff);
+int l1if_set_txpower_c0_idle_pwr_red(struct oc2gl1_hdl *fl1h, uint8_t red);
+int l1if_set_max_cell_size(struct oc2gl1_hdl *fl1h, uint8_t cell_size);
+int l1if_mute_rf(struct oc2gl1_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 oc2gl1_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 oc2gl1_hdl *fl1h);
+
+/* public helpers for test */
+int bts_check_for_ciph_cmd(struct oc2gl1_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 oc2gl1_hdl *trx_oc2gl1_hdl(struct gsm_bts_trx *trx)
+{
+ struct phy_instance *pinst = trx_phy_instance(trx);
+ OSMO_ASSERT(pinst);
+ return pinst->u.oc2g.hdl;
+}
+
+static inline struct gsm_bts_trx *oc2gl1_hdl_trx(struct oc2gl1_hdl *fl1h)
+{
+ OSMO_ASSERT(fl1h->phy_inst);
+ return fl1h->phy_inst->trx;
+}
+
+#endif /* _L1_IF_H */
diff --git a/src/osmo-bts-oc2g/l1_transp.h b/src/osmo-bts-oc2g/l1_transp.h
new file mode 100644
index 00000000..5af79dcb
--- /dev/null
+++ b/src/osmo-bts-oc2g/l1_transp.h
@@ -0,0 +1,14 @@
+#ifndef _L1_TRANSP_H
+#define _L1_TRANSP_H
+
+#include <osmocom/core/msgb.h>
+
+/* functions a transport calls on arrival of primitive from BTS */
+int l1if_handle_l1prim(int wq, struct oc2gl1_hdl *fl1h, struct msgb *msg);
+int l1if_handle_sysprim(struct oc2gl1_hdl *fl1h, struct msgb *msg);
+
+/* functions exported by a transport */
+int l1if_transport_open(int q, struct oc2gl1_hdl *fl1h);
+int l1if_transport_close(int q, struct oc2gl1_hdl *fl1h);
+
+#endif /* _L1_TRANSP_H */
diff --git a/src/osmo-bts-oc2g/l1_transp_hw.c b/src/osmo-bts-oc2g/l1_transp_hw.c
new file mode 100644
index 00000000..e1d46581
--- /dev/null
+++ b/src/osmo-bts-oc2g/l1_transp_hw.c
@@ -0,0 +1,326 @@
+/* Interface handler for Nuran Wireless OC-2G L1 (real hardware) */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <assert.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/gsm_data.h>
+
+#include <nrw/oc2g/oc2g.h>
+#include <nrw/oc2g/gsml1prim.h>
+#include <nrw/oc2g/gsml1const.h>
+#include <nrw/oc2g/gsml1types.h>
+
+#include "oc2gbts.h"
+#include "l1_if.h"
+#include "l1_transp.h"
+
+
+#define DEV_SYS_DSP2ARM_NAME "/dev/msgq/oc2g_dsp2arm_trx"
+#define DEV_SYS_ARM2DSP_NAME "/dev/msgq/oc2g_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 OC2GBTS_PRIM_SIZE
+ */
+osmo_static_assert(sizeof(GsmL1_Prim_t) + 128 <= OC2GBTS_PRIM_SIZE, l1_prim)
+osmo_static_assert(sizeof(Oc2g_Prim_t) + 128 <= OC2GBTS_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(Oc2g_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 oc2gl1_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 oc2gl1_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 oc2gl1_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-oc2g/main.c b/src/osmo-bts-oc2g/main.c
new file mode 100644
index 00000000..9777c092
--- /dev/null
+++ b/src/osmo-bts-oc2g/main.c
@@ -0,0 +1,240 @@
+/* Main program for NuRAN Wireless OC-2G BTS */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org>
+ *
+ * Based on sysmoBTS:
+ * (C) 2011-2013 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sched.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/ports.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/vty.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/l1sap.h>
+
+static int write_status_file(char *status_file, char *status_str)
+{
+ FILE *outf;
+ char tmp[PATH_MAX+1];
+
+ snprintf(tmp, sizeof(tmp)-1, "/var/run/osmo-bts/%s", status_file);
+ tmp[PATH_MAX-1] = '\0';
+
+ outf = fopen(tmp, "w");
+ if (!outf)
+ return -1;
+
+ fprintf(outf, "%s\n", status_str);
+
+ fclose(outf);
+
+ return 0;
+}
+
+/*NTQD: Change how rx_nr is handle in multi-trx*/
+#define OC2GBTS_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/oc2gbts_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_OC2G;
+ bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3);
+ /* specific default values for OC2G platform */
+
+ /* TODO(oramadan) MERGE
+ bts->oc2g.led_ctrl_mode = OC2G_BTS_LED_CTRL_MODE_DEFAULT;
+ /* RTP drift threshold default * /
+ bts->oc2g.rtp_drift_thres_ms = OC2G_BTS_RTP_DRIFT_THRES_DEFAULT;
+ */
+
+ 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(OC2GBTS_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)
+{
+ /* update status file */
+ write_status_file("state", "");
+
+ 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);
+
+ oc2gbts_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()
+{
+ printf(get_hwversion_desc());
+ printf("\n");
+}
+
+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)
+{
+ /* write to status file */
+ write_status_file("state", "ABIS DOWN");
+
+ /* for now, we simply terminate the program and re-spawn */
+ bts_shutdown(bts, "Abis close");
+}
+
+int main(int argc, char **argv)
+{
+ /* create status file with initial state */
+ write_status_file("state", "ABIS DOWN");
+
+ return bts_main(argc, argv);
+}
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_bid.c b/src/osmo-bts-oc2g/misc/oc2gbts_bid.c
new file mode 100644
index 00000000..6eaa9c69
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_bid.c
@@ -0,0 +1,175 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include "oc2gbts_bid.h"
+
+#define BOARD_REV_MAJ_SYSFS "/var/oc2g/platform/rev/major"
+#define BOARD_REV_MIN_SYSFS "/var/oc2g/platform/rev/minor"
+#define BOARD_OPT_SYSFS "/var/oc2g/platform/option"
+
+static const int option_type_mask[_NUM_OPTION_TYPES] = {
+ [OC2GBTS_OPTION_OCXO] = 0x07,
+ [OC2GBTS_OPTION_FPGA] = 0x03,
+ [OC2GBTS_OPTION_PA] = 0x01,
+ [OC2GBTS_OPTION_BAND] = 0x03,
+ [OC2GBTS_OPTION_TX_ATT] = 0x01,
+ [OC2GBTS_OPTION_TX_FIL] = 0x01,
+ [OC2GBTS_OPTION_RX_ATT] = 0x01,
+ [OC2GBTS_OPTION_RMS_FWD] = 0x01,
+ [OC2GBTS_OPTION_RMS_REFL] = 0x01,
+ [OC2GBTS_OPTION_DDR_32B] = 0x01,
+ [OC2GBTS_OPTION_DDR_ECC] = 0x01,
+ [OC2GBTS_OPTION_PA_TEMP] = 0x01,
+};
+
+static const int option_type_shift[_NUM_OPTION_TYPES] = {
+ [OC2GBTS_OPTION_OCXO] = 0,
+ [OC2GBTS_OPTION_FPGA] = 3,
+ [OC2GBTS_OPTION_PA] = 5,
+ [OC2GBTS_OPTION_BAND] = 6,
+ [OC2GBTS_OPTION_TX_ATT] = 8,
+ [OC2GBTS_OPTION_TX_FIL] = 9,
+ [OC2GBTS_OPTION_RX_ATT] = 10,
+ [OC2GBTS_OPTION_RMS_FWD] = 11,
+ [OC2GBTS_OPTION_RMS_REFL] = 12,
+ [OC2GBTS_OPTION_DDR_32B] = 13,
+ [OC2GBTS_OPTION_DDR_ECC] = 14,
+ [OC2GBTS_OPTION_PA_TEMP] = 15,
+};
+
+
+static char board_rev_maj = -1;
+static char board_rev_min = -1;
+static int board_option = -1;
+
+void oc2gbts_rev_get(char *rev_maj, char *rev_min)
+{
+ FILE *fp;
+ char rev;
+
+ *rev_maj = 0;
+ *rev_min = 0;
+
+ if (board_rev_maj != -1 && board_rev_min != -1) {
+ *rev_maj = board_rev_maj;
+ *rev_min = board_rev_min;
+ }
+
+ fp = fopen(BOARD_REV_MAJ_SYSFS, "r");
+ if (fp == NULL) return;
+
+ if (fscanf(fp, "%c", &rev) != 1) {
+ fclose(fp);
+ return;
+ }
+
+ fclose(fp);
+
+ *rev_maj = board_rev_maj = rev;
+
+ fp = fopen(BOARD_REV_MIN_SYSFS, "r");
+ if (fp == NULL) return;
+
+ if (fscanf(fp, "%c", &rev) != 1) {
+ fclose(fp);
+ return;
+ }
+
+ fclose(fp);
+
+ *rev_min = board_rev_min = rev;
+}
+
+const char* get_hwversion_desc()
+{
+ int rev;
+ int model;
+ size_t len;
+ static char model_name[64] = {0, };
+ len = snprintf(model_name, sizeof(model_name), "NuRAN OC-2G BTS");
+
+ char rev_maj = 0, rev_min = 0;
+
+ int rc = 0;
+ oc2gbts_rev_get(&rev_maj, &rev_min);
+ if (rc < 0)
+ return rc;
+ if (rev >= 0) {
+ len += snprintf(model_name + len, sizeof(model_name) - len,
+ " Rev %d.%d", (uint8_t)rev_maj, (uint8_t)rev_min);
+ }
+
+ model = oc2gbts_model_get();
+ if (model >= 0) {
+ snprintf(model_name + len, sizeof(model_name) - len,
+ "%s (%05X)", model_name, model);
+ }
+ return model_name;
+}
+
+int oc2gbts_model_get(void)
+{
+ FILE *fp;
+ int opt;
+
+
+ if (board_option == -1) {
+ fp = fopen(BOARD_OPT_SYSFS, "r");
+ if (fp == NULL) {
+ return -1;
+ }
+
+ if (fscanf(fp, "%X", &opt) != 1) {
+ fclose( fp );
+ return -1;
+ }
+ fclose(fp);
+
+ board_option = opt;
+ }
+ return board_option;
+}
+
+int oc2gbts_option_get(enum oc2gbts_option_type type)
+{
+ int rc;
+ int option;
+
+ if (type >= _NUM_OPTION_TYPES) {
+ return -EINVAL;
+ }
+
+ if (board_option == -1) {
+ rc = oc2gbts_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-oc2g/misc/oc2gbts_bid.h b/src/osmo-bts-oc2g/misc/oc2gbts_bid.h
new file mode 100644
index 00000000..00cf3899
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_bid.h
@@ -0,0 +1,47 @@
+#ifndef _OC2GBTS_BOARD_H
+#define _OC2GBTS_BOARD_H
+
+#include <stdint.h>
+
+enum oc2gbts_option_type {
+ OC2GBTS_OPTION_OCXO,
+ OC2GBTS_OPTION_FPGA,
+ OC2GBTS_OPTION_PA,
+ OC2GBTS_OPTION_BAND,
+ OC2GBTS_OPTION_TX_ATT,
+ OC2GBTS_OPTION_TX_FIL,
+ OC2GBTS_OPTION_RX_ATT,
+ OC2GBTS_OPTION_RMS_FWD,
+ OC2GBTS_OPTION_RMS_REFL,
+ OC2GBTS_OPTION_DDR_32B,
+ OC2GBTS_OPTION_DDR_ECC,
+ OC2GBTS_OPTION_PA_TEMP,
+ _NUM_OPTION_TYPES
+};
+
+enum oc2gbts_ocxo_type {
+ OC2GBTS_OCXO_BILAY_NVG45AV2072,
+ OC2GBTS_OCXO_TAITIEN_NJ26M003,
+ _NUM_OCXO_TYPES
+};
+
+enum oc2gbts_fpga_type {
+ OC2GBTS_FPGA_35T,
+ OC2GBTS_FPGA_50T,
+ OC2GBTS_FPGA_75T,
+ OC2GBTS_FPGA_100T,
+ _NUM_FPGA_TYPES
+};
+
+enum oc2gbts_gsm_band {
+ OC2GBTS_BAND_850,
+ OC2GBTS_BAND_900,
+ OC2GBTS_BAND_1800,
+ OC2GBTS_BAND_1900,
+};
+
+void oc2gbts_rev_get(char *rev_maj, char *rev_min);
+int oc2gbts_model_get(void);
+int oc2gbts_option_get(enum oc2gbts_option_type type);
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_bts.c b/src/osmo-bts-oc2g/misc/oc2gbts_bts.c
new file mode 100644
index 00000000..b3dae76e
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_bts.c
@@ -0,0 +1,129 @@
+/* Copyright (C) 2016 by NuRAN Wireless <support@nuranwireless.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <ifaddrs.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include "oc2gbts_mgr.h"
+#include "oc2gbts_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-oc2g > /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 oc2gbts_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.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-oc2g/misc/oc2gbts_bts.h b/src/osmo-bts-oc2g/misc/oc2gbts_bts.h
new file mode 100644
index 00000000..b003bdcd
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_bts.h
@@ -0,0 +1,21 @@
+#ifndef _OC2GBTS_BTS_H_
+#define _OC2GBTS_BTS_H_
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <string.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <osmo-bts/logging.h>
+
+/* public function prototypes */
+void check_bts_led_pattern(uint8_t *led);
+int check_sensor_led_pattern( struct oc2gbts_mgr_instance *mgr, uint8_t *led);
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_clock.c b/src/osmo-bts-oc2g/misc/oc2gbts_clock.c
new file mode 100644
index 00000000..acbbbc5c
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_clock.c
@@ -0,0 +1,263 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include "oc2gbts_clock.h"
+
+#define CLKERR_ERR_SYSFS "/var/oc2g/clkerr/clkerr1_average"
+#define CLKERR_ACC_SYSFS "/var/oc2g/clkerr/clkerr1_average_accuracy"
+#define CLKERR_INT_SYSFS "/var/oc2g/clkerr/clkerr1_average_interval"
+#define CLKERR_FLT_SYSFS "/var/oc2g/clkerr/clkerr1_fault"
+#define CLKERR_RFS_SYSFS "/var/oc2g/clkerr/refresh"
+#define CLKERR_RST_SYSFS "/var/oc2g/clkerr/reset"
+
+#define OCXODAC_VAL_SYSFS "/var/oc2g/ocxo/voltage"
+#define OCXODAC_ROM_SYSFS "/var/oc2g/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 oc2gbts_clock_err_open(void)
+{
+ int rc;
+ int fault;
+
+ if (clkerr_fd_err < 0) {
+ clkerr_fd_err = open(CLKERR_ERR_SYSFS, O_RDONLY);
+ if (clkerr_fd_err < 0) {
+ oc2gbts_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) {
+ oc2gbts_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) {
+ oc2gbts_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) {
+ oc2gbts_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) {
+ oc2gbts_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) {
+ oc2gbts_clock_err_close();
+ return clkerr_fd_reset;
+ }
+ }
+ return 0;
+}
+
+void oc2gbts_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 oc2gbts_clock_err_reset(void)
+{
+ return sysfs_write_val(clkerr_fd_reset, 1);
+}
+
+int oc2gbts_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 oc2gbts_clock_dac_open(void)
+{
+ if (ocxodac_fd_value < 0) {
+ ocxodac_fd_value = open(OCXODAC_VAL_SYSFS, O_RDWR);
+ if (ocxodac_fd_value < 0) {
+ oc2gbts_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) {
+ oc2gbts_clock_dac_close();
+ return ocxodac_fd_save;
+ }
+ }
+ return 0;
+}
+
+void oc2gbts_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 oc2gbts_clock_dac_get(int *dac_value)
+{
+ return sysfs_read_val(ocxodac_fd_value, dac_value);
+}
+
+int oc2gbts_clock_dac_set(int dac_value)
+{
+ return sysfs_write_val(ocxodac_fd_value, dac_value);
+}
+
+int oc2gbts_clock_dac_save(void)
+{
+ return sysfs_write_val(ocxodac_fd_save, 1);
+}
+
+
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_clock.h b/src/osmo-bts-oc2g/misc/oc2gbts_clock.h
new file mode 100644
index 00000000..1444b5cf
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_clock.h
@@ -0,0 +1,16 @@
+#ifndef _OC2GBTS_CLOCK_H
+#define _OC2GBTS_CLOCK_H
+
+int oc2gbts_clock_err_open(void);
+void oc2gbts_clock_err_close(void);
+int oc2gbts_clock_err_reset(void);
+int oc2gbts_clock_err_get(int *fault, int *error_ppt,
+ int *accuracy_ppq, int *interval_sec);
+
+int oc2gbts_clock_dac_open(void);
+void oc2gbts_clock_dac_close(void);
+int oc2gbts_clock_dac_get(int *dac_value);
+int oc2gbts_clock_dac_set(int dac_value);
+int oc2gbts_clock_dac_save(void);
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_led.c b/src/osmo-bts-oc2g/misc/oc2gbts_led.c
new file mode 100644
index 00000000..b8758b8e
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_led.c
@@ -0,0 +1,332 @@
+/* Copyright (C) 2016 by NuRAN Wireless <support@nuranwireless.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include "oc2gbts_led.h"
+#include "oc2gbts_bts.h"
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/linuxlist.h>
+
+static struct oc2gbts_led led_entries[] = {
+ {
+ .name = "led0",
+ .fullname = "led red",
+ .path = "/var/oc2g/leds/led0/brightness"
+ },
+ {
+ .name = "led1",
+ .fullname = "led green",
+ .path = "/var/oc2g/leds/led1/brightness"
+ }
+};
+
+static const struct value_string oc2gbts_led_strs[] = {
+ { OC2GBTS_LED_RED, "LED red" },
+ { OC2GBTS_LED_GREEN, "LED green" },
+ { OC2GBTS_LED_ORANGE, "LED orange" },
+ { OC2GBTS_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_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 oc2gbts_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()
+{
+ oc2gbts_led_write(led_entries[0].path, "1");
+ oc2gbts_led_write(led_entries[1].path, "0");
+}
+
+static void led_set_green()
+{
+ oc2gbts_led_write(led_entries[0].path, "0");
+ oc2gbts_led_write(led_entries[1].path, "1");
+}
+
+static void led_set_orange()
+{
+ oc2gbts_led_write(led_entries[0].path, "1");
+ oc2gbts_led_write(led_entries[1].path, "1");
+}
+
+static void led_set_off()
+{
+ oc2gbts_led_write(led_entries[0].path, "0");
+ oc2gbts_led_write(led_entries[1].path, "0");
+}
+
+static void led_sleep( struct oc2gbts_mgr_instance *mgr, struct oc2gbts_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->oc2gbts_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(oc2gbts_led_strs, led_timer->idx),
+ led_timer->param.sleep_sec,
+ led_timer->param.sleep_usec);
+
+ switch (led_timer->idx) {
+ case OC2GBTS_LED_RED:
+ led_set_red();
+ break;
+ case OC2GBTS_LED_GREEN:
+ led_set_green();
+ break;
+ case OC2GBTS_LED_ORANGE:
+ led_set_orange();
+ break;
+ case OC2GBTS_LED_OFF:
+ led_set_off();
+ break;
+ default:
+ led_set_off();
+ }
+}
+
+static void led_sleep_cb(void *_data) {
+ struct oc2gbts_mgr_instance *mgr = _data;
+ struct oc2gbts_led_timer_list *led_list;
+
+ /* make sure the timer list is not empty */
+ if (llist_empty(&mgr->oc2gbts_leds.list))
+ return;
+
+ llist_for_each_entry(led_list, &mgr->oc2gbts_leds.list, list) {
+ if (led_list->led_timer.idx == mgr->oc2gbts_leds.active_timer) {
+ LOGP(DTEMP, LOGL_DEBUG,"Delete expired %s timer %d sec + %d usec\n",
+ get_value_string(oc2gbts_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->oc2gbts_leds.list);
+ break;
+ }
+ }
+
+ /* Execute next timer */
+ led_list = llist_first_entry(&mgr->oc2gbts_leds.list, struct oc2gbts_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(oc2gbts_led_strs, led_list->led_timer.idx),
+ led_list->led_timer.param.sleep_sec,
+ led_list->led_timer.param.sleep_usec,
+ llist_count(&mgr->oc2gbts_leds.list));
+
+ led_sleep(mgr, &led_list->led_timer, led_sleep_cb);
+ }
+
+}
+
+static void delete_led_timer_entries(struct oc2gbts_mgr_instance *mgr)
+{
+ struct oc2gbts_led_timer_list *led_list, *led_list2;
+
+ if (llist_empty(&mgr->oc2gbts_leds.list))
+ return;
+
+ llist_for_each_entry_safe(led_list, led_list2, &mgr->oc2gbts_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(oc2gbts_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 oc2gbts_mgr_instance *mgr, char *cmdstr)
+{
+ double sec, int_sec, frac_sec;
+ struct oc2gbts_sleep_time led_param;
+
+ led_param.sleep_sec = 0;
+ led_param.sleep_usec = 0;
+
+ if (strstr(cmdstr, "set red") != NULL)
+ mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_RED;
+ else if (strstr(cmdstr, "set green") != NULL)
+ mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_GREEN;
+ else if (strstr(cmdstr, "set orange") != NULL)
+ mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_ORANGE;
+ else if (strstr(cmdstr, "set off") != NULL)
+ mgr->oc2gbts_leds.led_idx = OC2GBTS_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->oc2gbts_leds.led_idx >= OC2GBTS_LED_RED) && (mgr->oc2gbts_leds.led_idx < _OC2GBTS_LED_MAX)) {
+ struct oc2gbts_led_timer_list *led_list;
+
+ /* allocate timer entry */
+ led_list = talloc_zero(tall_mgr_ctx, struct oc2gbts_led_timer_list);
+ if (led_list) {
+ led_list->led_timer.idx = mgr->oc2gbts_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->oc2gbts_leds.list);
+
+ LOGP(DTEMP, LOGL_DEBUG,"Add %s timer to list, %d sec + %d usec, total entries=%d\n",
+ get_value_string(oc2gbts_led_strs, mgr->oc2gbts_leds.led_idx),
+ led_list->led_timer.param.sleep_sec,
+ led_list->led_timer.param.sleep_usec,
+ llist_count(&mgr->oc2gbts_leds.list));
+ }
+ }
+ } else
+ return -1;
+
+ return 0;
+}
+
+static int parse_led_pattern(char *pattern, struct oc2gbts_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 oc2gbts_mgr_instance *mgr, int pattern_id)
+{
+ int rc;
+ struct oc2gbts_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->oc2gbts_leds.last_pattern_id)
+ return;
+
+ mgr->oc2gbts_leds.last_pattern_id = pattern_id;
+
+ LOGP(DTEMP, LOGL_INFO, "blink pattern command : %d\n", pattern_id);
+ LOGP(DTEMP, LOGL_INFO, "%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->oc2gbts_leds.list))
+ return;
+
+ /* Start the first LED timer in the list */
+ led_list = llist_first_entry(&mgr->oc2gbts_leds.list, struct oc2gbts_led_timer_list, list);
+ if (led_list) {
+ LOGP(DTEMP, LOGL_DEBUG,"Execute timer %s for %d sec + %d usec\n",
+ get_value_string(oc2gbts_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 oc2gbts_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-oc2g/misc/oc2gbts_led.h b/src/osmo-bts-oc2g/misc/oc2gbts_led.h
new file mode 100644
index 00000000..cb627e3c
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_led.h
@@ -0,0 +1,22 @@
+#ifndef _OC2GBTS_LED_H
+#define _OC2GBTS_LED_H
+
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <math.h>
+#include <sys/stat.h>
+
+#include <osmo-bts/logging.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/utils.h>
+
+#include "oc2gbts_mgr.h"
+
+/* public function prototypes */
+void led_set(struct oc2gbts_mgr_instance *mgr, int pattern_id);
+
+void select_led_pattern(struct oc2gbts_mgr_instance *mgr);
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr.c
new file mode 100644
index 00000000..45ecc65d
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr.c
@@ -0,0 +1,353 @@
+/* Main program for NuRAN Wireless OC-2G BTS management daemon */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_mgr.c
+ * (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ * (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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <sys/signal.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/ports.h>
+
+#include "misc/oc2gbts_misc.h"
+#include "misc/oc2gbts_mgr.h"
+#include "misc/oc2gbts_par.h"
+#include "misc/oc2gbts_bid.h"
+#include "misc/oc2gbts_power.h"
+#include "misc/oc2gbts_swd.h"
+
+#include "oc2gbts_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 oc2gbts_mgr_instance manager = {
+ .config_file = "oc2gbts-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,
+ },
+ .tx_temp_limit = {
+ .thresh_warn_max = 80,
+ .thresh_crit_max = 85,
+ .thresh_warn_min = -20,
+ },
+ .pa_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 = 30,
+ .thresh_crit_max = 40,
+ },
+ .pa_pwr_limit = {
+ .thresh_warn_max = 20,
+ .thresh_crit_max = 30,
+ }
+ },
+ .vswr = {
+ .vswr_limit = {
+ .thresh_warn_max = 3000,
+ .thresh_crit_max = 5000,
+ }
+ },
+ .gps = {
+ .gps_fix_limit = {
+ .thresh_warn_max = 7,
+ }
+ },
+ .state = {
+ .action_norm = SENSOR_ACT_NORM_PA_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)
+{
+ oc2gbts_check_temp(no_rom_write);
+ oc2gbts_check_power(no_rom_write);
+ oc2gbts_check_vswr(no_rom_write);
+ osmo_timer_schedule(&sensor_timer, SENSOR_TIMER_SECS, 0);
+ /* TODO checks if oc2gbts_check_temp/oc2gbts_check_power/oc2gbts_check_vswr went ok */
+ oc2gbts_swd_event(&manager, SWD_CHECK_SENSOR);
+}
+
+static struct osmo_timer_list hours_timer;
+static void hours_timer_cb(void *unused)
+{
+ oc2gbts_update_hours(no_rom_write);
+
+ osmo_timer_schedule(&hours_timer, HOURS_TIMER_SECS, 0);
+ /* TODO: validates if oc2gbts_update_hours went correctly */
+ oc2gbts_swd_event(&manager, SWD_UPDATE_HOURS);
+}
+
+static void print_help(void)
+{
+ printf("oc2gbts-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:
+ oc2gbts_check_temp(no_rom_write);
+ oc2gbts_check_power(no_rom_write);
+ oc2gbts_check_vswr(no_rom_write);
+ oc2gbts_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),
+};
+
+static int mgr_log_init(void)
+{
+ osmo_init_logging(&mgr_log_info);
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ void *tall_msgb_ctx;
+ int rc;
+ pthread_t tid;
+
+ tall_mgr_ctx = talloc_named_const(NULL, 1, "bts manager");
+ tall_msgb_ctx = talloc_named_const(tall_mgr_ctx, 1, "msgb");
+ msgb_set_talloc_ctx(tall_msgb_ctx);
+
+ mgr_log_init();
+
+ osmo_init_ignore_signals();
+ signal(SIGINT, &signal_handler);
+ signal(SIGUSR1, &signal_handler);
+ signal(SIGUSR2, &signal_handler);
+
+ rc = parse_options(argc, argv);
+ if (rc < 0)
+ exit(2);
+
+ oc2gbts_mgr_vty_init();
+ logging_vty_add_cmds(&mgr_log_info);
+ rc = oc2gbts_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.oc2gbts_leds.list);
+ INIT_LLIST_HEAD(&manager.alarms.list);
+
+ /* Initialize the service watchdog notification for SWD_LAST event(s) */
+ if (oc2gbts_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);
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ /* Enable the PAs */
+ rc = oc2gbts_power_set(OC2GBTS_POWER_PA, 1);
+ if (rc < 0) {
+ exit(3);
+ }
+ }
+ /* handle broadcast messages for ipaccess-find */
+ if (oc2gbts_mgr_nl_init() != 0)
+ exit(3);
+
+ /* Initialize the sensor control */
+ oc2gbts_mgr_sensor_init(&manager);
+
+ if (oc2gbts_mgr_calib_init(&manager) != 0)
+ exit(3);
+
+ if (oc2gbts_mgr_control_init(&manager))
+ 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);
+ oc2gbts_swd_event(&manager, SWD_MAINLOOP);
+ }
+}
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr.h b/src/osmo-bts-oc2g/misc/oc2gbts_mgr.h
new file mode 100644
index 00000000..1f50fb6b
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr.h
@@ -0,0 +1,398 @@
+#ifndef _OC2GBTS_MGR_H
+#define _OC2GBTS_MGR_H
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+
+#include <stdint.h>
+#include <gps.h>
+
+#define OC2GBTS_SENSOR_TIMER_DURATION 60
+#define OC2GBTS_PREVENT_TIMER_DURATION 15 * 60
+#define OC2GBTS_PREVENT_TIMER_SHORT_DURATION 5 * 60
+#define OC2GBTS_PREVENT_TIMER_NONE 0
+#define OC2GBTS_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_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 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_PA_OFF = 0x2,
+ SENSOR_ACT_BTS_SRV_OFF = 0x10,
+};
+
+/* actions only for normal state */
+enum {
+#if 0
+ SENSOR_ACT_NORM_PW_CONTRL = 0x1,
+#endif
+ SENSOR_ACT_NORM_PA_ON = 0x2,
+ SENSOR_ACT_NORM_BTS_SRV_ON= 0x10,
+};
+
+enum oc2gbts_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 oc2gbts_leds_name {
+ OC2GBTS_LED_RED = 0,
+ OC2GBTS_LED_GREEN,
+ OC2GBTS_LED_ORANGE,
+ OC2GBTS_LED_OFF,
+ _OC2GBTS_LED_MAX
+};
+
+struct oc2gbts_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 oc2gbts_temp_limit {
+ int thresh_warn_max;
+ int thresh_crit_max;
+ int thresh_warn_min;
+};
+
+struct oc2gbts_volt_limit {
+ int thresh_warn_max;
+ int thresh_crit_max;
+ int thresh_warn_min;
+ int thresh_crit_min;
+};
+
+struct oc2gbts_pwr_limit {
+ int thresh_warn_max;
+ int thresh_crit_max;
+};
+
+struct oc2gbts_vswr_limit {
+ int thresh_warn_max;
+ int thresh_crit_max;
+};
+
+struct oc2gbts_gps_fix_limit {
+ int thresh_warn_max;
+};
+
+struct oc2gbts_sleep_time {
+ int sleep_sec;
+ int sleep_usec;
+};
+
+struct oc2gbts_led_timer {
+ uint8_t idx;
+ struct osmo_timer_list timer;
+ struct oc2gbts_sleep_time param;
+};
+
+struct oc2gbts_led_timer_list {
+ struct llist_head list;
+ struct oc2gbts_led_timer led_timer;
+};
+
+struct oc2gbts_preventive_list {
+ struct llist_head list;
+ struct oc2gbts_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_TX_TEMP_NODE,
+ LIMIT_PA_TEMP_NODE,
+ LIMIT_SUPPLY_VOLT_NODE,
+ LIMIT_VSWR_NODE,
+ LIMIT_SUPPLY_PWR_NODE,
+ LIMIT_PA_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 oc2gbts_mgr_instance {
+ const char *config_file;
+
+ struct {
+ struct oc2gbts_temp_limit supply_temp_limit;
+ struct oc2gbts_temp_limit soc_temp_limit;
+ struct oc2gbts_temp_limit fpga_temp_limit;
+ struct oc2gbts_temp_limit rmsdet_temp_limit;
+ struct oc2gbts_temp_limit ocxo_temp_limit;
+ struct oc2gbts_temp_limit tx_temp_limit;
+ struct oc2gbts_temp_limit pa_temp_limit;
+ } temp;
+
+ struct {
+ struct oc2gbts_volt_limit supply_volt_limit;
+ } volt;
+
+ struct {
+ struct oc2gbts_pwr_limit supply_pwr_limit;
+ struct oc2gbts_pwr_limit pa_pwr_limit;
+ } pwr;
+
+ struct {
+ struct oc2gbts_vswr_limit vswr_limit;
+ int last_vswr;
+ } vswr;
+
+ struct {
+ struct oc2gbts_gps_fix_limit gps_fix_limit;
+ int last_update;
+ time_t last_gps_fix;
+ time_t gps_fix_now;
+ int gps_open;
+ struct osmo_fd gpsfd;
+ struct gps_data_t gpsdata;
+ struct osmo_timer_list fix_timeout;
+ } gps;
+
+ struct {
+ int action_norm;
+ int action_warn;
+ int action_crit;
+ int action_comb;
+
+ enum oc2gbts_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;
+ } oc2gbts_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;
+ } oc2gbts_ctrl;
+
+ struct oc2gbts_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 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 oc2gbts_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_TRX_CRIT_MAX_ALARM = (1 << 5),
+ S_MGR_TEMP_PA_CRIT_MAX_ALARM = (1 << 6),
+ S_MGR_SUPPLY_CRIT_MAX_ALARM = (1 << 7),
+ S_MGR_SUPPLY_CRIT_MIN_ALARM = (1 << 8),
+ S_MGR_VSWR_CRIT_MAX_ALARM = (1 << 9),
+ S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM = (1 << 10),
+ S_MGR_PWR_PA_CRIT_MAX_ALARM = (1 << 11),
+ _S_MGR_CRIT_ALARM_MAX,
+};
+
+enum oc2gbts_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 << 1),
+ S_MGR_TEMP_SOC_WARN_MIN_ALARM = (1 << 2),
+ S_MGR_TEMP_SOC_WARN_MAX_ALARM = (1 << 3),
+ S_MGR_TEMP_FPGA_WARN_MIN_ALARM = (1 << 4),
+ S_MGR_TEMP_FPGA_WARN_MAX_ALARM = (1 << 5),
+ S_MGR_TEMP_RMS_DET_WARN_MIN_ALARM = (1 << 6),
+ S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM = (1 << 7),
+ S_MGR_TEMP_OCXO_WARN_MIN_ALARM = (1 << 8),
+ S_MGR_TEMP_OCXO_WARN_MAX_ALARM = (1 << 9),
+ S_MGR_TEMP_TRX_WARN_MIN_ALARM = (1 << 10),
+ S_MGR_TEMP_TRX_WARN_MAX_ALARM = (1 << 11),
+ S_MGR_TEMP_PA_WARN_MIN_ALARM = (1 << 12),
+ S_MGR_TEMP_PA_WARN_MAX_ALARM = (1 << 13),
+ S_MGR_SUPPLY_WARN_MIN_ALARM = (1 << 14),
+ S_MGR_SUPPLY_WARN_MAX_ALARM = (1 << 15),
+ S_MGR_VSWR_WARN_MAX_ALARM = (1 << 16),
+ S_MGR_PWR_SUPPLY_WARN_MAX_ALARM = (1 << 17),
+ S_MGR_PWR_PA_WARN_MAX_ALARM = (1 << 18),
+ S_MGR_GPS_FIX_WARN_ALARM = (1 << 19),
+ _S_MGR_WARN_ALARM_MAX,
+};
+
+enum oc2gbts_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_TRX_MAX_FAIL = 0x4105,
+ NM_EVT_CAUSE_CRIT_TEMP_PA_MAX_FAIL = 0x4106,
+ NM_EVT_CAUSE_CRIT_SUPPLY_MAX_FAIL = 0x4107,
+ NM_EVT_CAUSE_CRIT_SUPPLY_MIN_FAIL = 0x4108,
+ NM_EVT_CAUSE_CRIT_VSWR_MAX_FAIL = 0x4109,
+ NM_EVT_CAUSE_CRIT_PWR_SUPPLY_MAX_FAIL = 0x410A,
+ NM_EVT_CAUSE_CRIT_PWR_PA_MAX_FAIL = 0x410B,
+ /* 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_TRX_LOW_FAIL = 0x440A,
+ NM_EVT_CAUSE_WARN_TEMP_TRX_HIGH_FAIL = 0x440B,
+ NM_EVT_CAUSE_WARN_TEMP_PA_LOW_FAIL = 0x440C,
+ NM_EVT_CAUSE_WARN_TEMP_PA_HIGH_FAIL = 0x440D,
+ NM_EVT_CAUSE_WARN_SUPPLY_LOW_FAIL = 0x440E,
+ NM_EVT_CAUSE_WARN_SUPPLY_HIGH_FAIL = 0x440F,
+ NM_EVT_CAUSE_WARN_VSWR_HIGH_FAIL = 0x4410,
+ NM_EVT_CAUSE_WARN_PWR_SUPPLY_HIGH_FAIL = 0x4411,
+ NM_EVT_CAUSE_WARN_PWR_PA_HIGH_FAIL = 0x4412,
+ NM_EVT_CAUSE_WARN_GPS_FIX_FAIL = 0x4413,
+};
+
+/* 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 oc2gbts_mgr_vty_init(void);
+int oc2gbts_mgr_parse_config(struct oc2gbts_mgr_instance *mgr);
+int oc2gbts_mgr_nl_init(void);
+int oc2gbts_mgr_sensor_init(struct oc2gbts_mgr_instance *mgr);
+const char *oc2gbts_mgr_sensor_get_state(enum oc2gbts_sensor_state state);
+
+int oc2gbts_mgr_calib_init(struct oc2gbts_mgr_instance *mgr);
+int oc2gbts_mgr_control_init(struct oc2gbts_mgr_instance *mgr);
+int oc2gbts_mgr_calib_run(struct oc2gbts_mgr_instance *mgr);
+void oc2gbts_mgr_dispatch_alarm(struct oc2gbts_mgr_instance *mgr, const int cause, const char *key, const char *text);
+void handle_alert_actions(struct oc2gbts_mgr_instance *mgr);
+void handle_ceased_actions(struct oc2gbts_mgr_instance *mgr);
+void handle_warn_actions(struct oc2gbts_mgr_instance *mgr);
+extern void *tall_mgr_ctx;
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c
new file mode 100644
index 00000000..96162621
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c
@@ -0,0 +1,750 @@
+/* OCXO calibration control for OC-2G BTS management daemon */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "misc/oc2gbts_mgr.h"
+#include "misc/oc2gbts_misc.h"
+#include "misc/oc2gbts_clock.h"
+#include "misc/oc2gbts_swd.h"
+#include "misc/oc2gbts_par.h"
+#include "misc/oc2gbts_led.h"
+#include "osmo-bts/msg_utils.h"
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/select.h>
+
+#include <osmocom/ctrl/control_cmd.h>
+#include <osmocom/ctrl/ports.h>
+
+#include <osmocom/gsm/ipa.h>
+#include <osmocom/gsm/protocol/ipaccess.h>
+
+#include <osmocom/abis/abis.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/abis/ipa.h>
+
+#include <time.h>
+#include <sys/sysinfo.h>
+#include <errno.h>
+
+static void calib_adjust(struct oc2gbts_mgr_instance *mgr);
+static void calib_state_reset(struct oc2gbts_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,
+ CALIB_GPS_WAIT_FOR_FIX,
+};
+
+enum calib_result {
+ CALIB_FAIL_START,
+ CALIB_FAIL_GPSFIX,
+ CALIB_FAIL_CLKERR,
+ CALIB_FAIL_OCXODAC,
+ CALIB_SUCCESS,
+};
+
+static int oc2gbts_par_get_uptime(void *ctx, int *ret)
+{
+ char *fpath;
+ FILE *fp;
+ int rc;
+
+ fpath = talloc_asprintf(ctx, "%s", UPTIME_TMP_PATH);
+ if (!fpath)
+ return NULL;
+
+ fp = fopen(fpath, "r");
+ if (!fp)
+ fprintf(stderr, "Failed to open %s due to '%s' error\n", fpath, strerror(errno));
+
+ talloc_free(fpath);
+
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fscanf(fp, "%d", ret);
+ if (rc != 1) {
+ fclose(fp);
+ return -EIO;
+ }
+ fclose(fp);
+
+ return 0;
+}
+
+static int oc2gbts_par_set_uptime(void *ctx, int val)
+{
+ char *fpath;
+ FILE *fp;
+ int rc;
+
+ fpath = talloc_asprintf(ctx, "%s", UPTIME_TMP_PATH);
+ if (!fpath)
+ return NULL;
+
+ fp = fopen(fpath, "w");
+ if (!fp)
+ fprintf(stderr, "Failed to open %s due to '%s' error\n", fpath, strerror(errno));
+
+ talloc_free(fpath);
+
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fprintf(fp, "%d", val);
+ if (rc < 0) {
+ fclose(fp);
+ return -EIO;
+ }
+ fsync(fp);
+ fclose(fp);
+
+ return 0;
+}
+
+static void mgr_gps_close(struct oc2gbts_mgr_instance *mgr)
+{
+ if (!mgr->gps.gps_open)
+ return;
+
+ osmo_timer_del(&mgr->gps.fix_timeout);
+
+ osmo_fd_unregister(&mgr->gps.gpsfd);
+ gps_close(&mgr->gps.gpsdata);
+ memset(&mgr->gps.gpsdata, 0, sizeof(mgr->gps.gpsdata));
+ mgr->gps.gps_open = 0;
+}
+
+static void mgr_gps_checkfix(struct oc2gbts_mgr_instance *mgr)
+{
+ struct gps_data_t *data = &mgr->gps.gpsdata;
+
+ /* No 3D fix yet */
+ if (data->fix.mode < MODE_3D) {
+ LOGP(DCALIB, LOGL_DEBUG, "Fix mode not enough: %d\n",
+ data->fix.mode);
+ return;
+ }
+
+ /* Satellite used checking */
+ if (data->satellites_used < 1) {
+ LOGP(DCALIB, LOGL_DEBUG, "Not enough satellites used: %d\n",
+ data->satellites_used);
+ return;
+ }
+
+ mgr->gps.gps_fix_now = (time_t) data->fix.time;
+ LOGP(DCALIB, LOGL_INFO, "Got a GPS fix, satellites used: %d, timestamp: %ld\n",
+ data->satellites_used, mgr->gps.gps_fix_now);
+ osmo_timer_del(&mgr->gps.fix_timeout);
+ mgr_gps_close(mgr);
+}
+
+static int mgr_gps_read(struct osmo_fd *fd, unsigned int what)
+{
+ int rc;
+ struct oc2gbts_mgr_instance *mgr = fd->data;
+
+ rc = gps_read(&mgr->gps.gpsdata);
+ if (rc == -1) {
+ LOGP(DCALIB, LOGL_ERROR, "gpsd vanished during read.\n");
+ calib_state_reset(mgr, CALIB_FAIL_GPSFIX);
+ return -1;
+ }
+
+ if (rc > 0)
+ mgr_gps_checkfix(mgr);
+ return 0;
+}
+
+static void mgr_gps_fix_timeout(void *_data)
+{
+ struct oc2gbts_mgr_instance *mgr = _data;
+
+ LOGP(DCALIB, LOGL_ERROR, "Failed to acquire GPS fix.\n");
+ mgr_gps_close(mgr);
+}
+
+static void mgr_gps_open(struct oc2gbts_mgr_instance *mgr)
+{
+ int rc;
+
+ if (mgr->gps.gps_open)
+ return;
+
+ rc = gps_open("localhost", DEFAULT_GPSD_PORT, &mgr->gps.gpsdata);
+ if (rc != 0) {
+ LOGP(DCALIB, LOGL_ERROR, "Failed to connect to GPS %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_GPSFIX);
+ return;
+ }
+
+ mgr->gps.gps_open = 1;
+ gps_stream(&mgr->gps.gpsdata, WATCH_ENABLE, NULL);
+
+ mgr->gps.gpsfd.data = mgr;
+ mgr->gps.gpsfd.cb = mgr_gps_read;
+ mgr->gps.gpsfd.when = BSC_FD_READ | BSC_FD_EXCEPT;
+ mgr->gps.gpsfd.fd = mgr->gps.gpsdata.gps_fd;
+ if (osmo_fd_register(&mgr->gps.gpsfd) < 0) {
+ LOGP(DCALIB, LOGL_ERROR, "Failed to register GPSD fd\n");
+ calib_state_reset(mgr, CALIB_FAIL_GPSFIX);
+ }
+
+ mgr->calib.state = CALIB_GPS_WAIT_FOR_FIX;
+ mgr->gps.fix_timeout.data = mgr;
+ mgr->gps.fix_timeout.cb = mgr_gps_fix_timeout;
+ osmo_timer_schedule(&mgr->gps.fix_timeout, 60, 0);
+ LOGP(DCALIB, LOGL_INFO, "Opened the GPSD connection waiting for fix: %d\n",
+ mgr->gps.gpsfd.fd);
+}
+
+/* OC2G CTRL interface related functions */
+static void send_ctrl_cmd(struct oc2gbts_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->oc2gbts_ctrl.bts_conn, msg);
+}
+
+static void send_set_ctrl_cmd_int(struct oc2gbts_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->oc2gbts_ctrl.last_seqno++, key, val);
+ msg->l2h = msgb_put(msg, ret);
+ return send_ctrl_cmd(mgr, msg);
+}
+
+static void send_set_ctrl_cmd(struct oc2gbts_mgr_instance *mgr, const char *key, const int val, const char *text)
+{
+ struct msgb *msg;
+ int ret;
+
+ msg = msgb_alloc_headroom(1024, 128, "CTRL SET");
+ ret = snprintf((char *) msg->data, 4096, "SET %u %s %d, %s",
+ mgr->oc2gbts_ctrl.last_seqno++, key, val, text);
+ msg->l2h = msgb_put(msg, ret);
+ return send_ctrl_cmd(mgr, msg);
+}
+
+static void calib_start(struct oc2gbts_mgr_instance *mgr)
+{
+ int rc;
+
+ rc = oc2gbts_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 = oc2gbts_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 int get_uptime(int *uptime)
+{
+ struct sysinfo s_info;
+ int rc;
+ rc = sysinfo(&s_info);
+ if(!rc)
+ *uptime = s_info.uptime /(60 * 60);
+
+ return rc;
+}
+
+static void calib_adjust(struct oc2gbts_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;
+ int now = 0;
+
+ /* Get GPS time via GPSD */
+ mgr_gps_open(mgr);
+
+ rc = oc2gbts_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 up time */
+ rc = get_uptime(&now);
+ if (rc < 0)
+ LOGP(DTEMP, LOGL_ERROR, "Unable to read up time hours: %d (%s)\n", rc, strerror(errno));
+
+ /* read last up time */
+ rc = oc2gbts_par_get_uptime(tall_mgr_ctx, &mgr->gps.last_update);
+ if (rc < 0)
+ LOGP(DCALIB, LOGL_NOTICE, "Last GPS 3D fix can not read (%d). Last GPS 3D fix sets to zero\n", rc);
+
+ if (fault) {
+ LOGP(DCALIB, LOGL_NOTICE, "GPS has no fix, warn_flags=0x%08x, last=%d, now=%d\n",
+ mgr->oc2gbts_ctrl.warn_flags, mgr->gps.last_update, now);
+ if (now >= mgr->gps.last_update + mgr->gps.gps_fix_limit.thresh_warn_max * 24) {
+ if (!(mgr->oc2gbts_ctrl.warn_flags & S_MGR_GPS_FIX_WARN_ALARM)) {
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_GPS_FIX_WARN_ALARM;
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_GPS_FIX_FAIL, "oc2g-oml-alert", "GPS 3D fix has been lost");
+
+ LOGP(DCALIB, LOGL_NOTICE, "GPS has no fix since the last verification, warn_flags=0x%08x, last=%d, now=%d\n",
+ mgr->oc2gbts_ctrl.warn_flags, mgr->gps.last_update, now);
+
+ /* schedule LED pattern for GPS fix lost */
+ mgr->alarms.gps_fix_lost = 1;
+ /* update LED pattern */
+ select_led_pattern(mgr);
+ }
+ } else {
+ /* read from last GPS 3D fix timestamp */
+ rc = oc2gbts_par_get_gps_fix(tall_mgr_ctx, &mgr->gps.last_gps_fix);
+ if (rc < 0)
+ LOGP(DCALIB, LOGL_NOTICE, "Last GPS 3D fix timestamp can not read (%d)\n", rc);
+
+ if (difftime(mgr->gps.gps_fix_now, mgr->gps.last_gps_fix) > mgr->gps.gps_fix_limit.thresh_warn_max * 24 * 60 * 60) {
+ if (!(mgr->oc2gbts_ctrl.warn_flags & S_MGR_GPS_FIX_WARN_ALARM)) {
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_GPS_FIX_WARN_ALARM;
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_GPS_FIX_FAIL, "oc2g-oml-alert", "GPS 3D fix has been lost");
+
+ LOGP(DCALIB, LOGL_NOTICE, "GPS has no fix since the last known GPS fix, warn_flags=0x%08x, gps_last=%ld, gps_now=%ld\n",
+ mgr->oc2gbts_ctrl.warn_flags, mgr->gps.last_gps_fix, mgr->gps.gps_fix_now);
+
+ /* schedule LED pattern for GPS fix lost */
+ mgr->alarms.gps_fix_lost = 1;
+ /* update LED pattern */
+ select_led_pattern(mgr);
+ }
+ }
+ }
+
+ rc = oc2gbts_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;
+ }
+
+ calib_state_reset(mgr, CALIB_FAIL_GPSFIX);
+ return;
+ }
+
+ if (!interval_sec) {
+ LOGP(DCALIB, LOGL_INFO, "Skipping this iteration, no integration time\n");
+ calib_state_reset(mgr, CALIB_SUCCESS);
+ return;
+ }
+
+ /* We got GPS 3D fix */
+ LOGP(DCALIB, LOGL_DEBUG, "Got GPS 3D fix warn_flags=0x%08x, uptime_last=%d, uptime_now=%d, gps_last=%ld, gps_now=%ld\n",
+ mgr->oc2gbts_ctrl.warn_flags, mgr->gps.last_update, now, mgr->gps.last_gps_fix, mgr->gps.gps_fix_now);
+
+ if (mgr->oc2gbts_ctrl.warn_flags & S_MGR_GPS_FIX_WARN_ALARM) {
+ /* Store GPS fix as soon as we send ceased alarm */
+ LOGP(DCALIB, LOGL_NOTICE, "Store GPS fix as soon as we send ceased alarm last=%ld, now=%ld\n",
+ mgr->gps.last_gps_fix , mgr->gps.gps_fix_now);
+ rc = oc2gbts_par_set_gps_fix(tall_mgr_ctx, mgr->gps.gps_fix_now);
+ if (rc < 0)
+ LOGP(DCALIB, LOGL_ERROR, "Failed to store GPS 3D fix to storage %d\n", rc);
+
+ /* Store last up time */
+ rc = oc2gbts_par_set_uptime(tall_mgr_ctx, now);
+ if (rc < 0)
+ LOGP(DCALIB, LOGL_ERROR, "Failed to store uptime to storage %d\n", rc);
+
+ mgr->gps.last_update = now;
+
+ /* schedule LED pattern for GPS fix resume */
+ mgr->alarms.gps_fix_lost = 0;
+ /* update LED pattern */
+ select_led_pattern(mgr);
+ /* send ceased alarm if possible */
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_GPS_FIX_FAIL, "oc2g-oml-ceased", "GPS 3D fix has been lost");
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_GPS_FIX_WARN_ALARM;
+
+ }
+ /* Store GPS fix at every hour */
+ if (now > mgr->gps.last_update) {
+ /* Store GPS fix every 60 minutes */
+ LOGP(DCALIB, LOGL_INFO, "Store GPS fix every hour last=%ld, now=%ld\n",
+ mgr->gps.last_gps_fix , mgr->gps.gps_fix_now);
+ rc = oc2gbts_par_set_gps_fix(tall_mgr_ctx, mgr->gps.gps_fix_now);
+ if (rc < 0)
+ LOGP(DCALIB, LOGL_ERROR, "Failed to store GPS 3D fix to storage %d\n", rc);
+
+ /* Store last up time every 60 minutes */
+ rc = oc2gbts_par_set_uptime(tall_mgr_ctx, now);
+ if (rc < 0)
+ LOGP(DCALIB, LOGL_ERROR, "Failed to store uptime to storage %d\n", rc);
+
+ /* update last uptime */
+ mgr->gps.last_update = now;
+ }
+
+ rc = oc2gbts_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_INFO,
+ "Calibration ERR(%f PPB) ACC(%f PPB) INT(%d) DAC(%d)\n",
+ error_ppt / 1000., accuracy_ppq / 1000000., interval_sec, dac_value);
+
+ /* 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_INFO,
+ "Going to apply %d as new clock setting.\n",
+ new_dac_value);
+
+ rc = oc2gbts_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 = oc2gbts_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_INFO, "Saving OCXO DAC value to memory... val = %d\n", dac_value);
+ rc = oc2gbts_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 = oc2gbts_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);
+ }
+ }
+
+ calib_state_reset(mgr, CALIB_SUCCESS);
+ return;
+}
+
+static void calib_close(struct oc2gbts_mgr_instance *mgr)
+{
+ oc2gbts_clock_err_close();
+ oc2gbts_clock_dac_close();
+}
+
+static void calib_state_reset(struct oc2gbts_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? */
+ oc2gbts_swd_event(mgr, SWD_CHECK_CALIB);
+ }
+
+ mgr->calib.state = CALIB_INITIAL;
+ calib_close(mgr);
+}
+
+static int calib_run(struct oc2gbts_mgr_instance *mgr, int from_loop)
+{
+ if (mgr->calib.state != CALIB_INITIAL) {
+ LOGP(DCALIB, LOGL_ERROR, "Calib is already in progress.\n");
+ return -1;
+ }
+
+ /* Validates if we have a bts connection */
+ if (mgr->oc2gbts_ctrl.is_up) {
+ LOGP(DCALIB, LOGL_DEBUG, "Bts connection is up.\n");
+ oc2gbts_swd_event(mgr, SWD_CHECK_BTS_CONNECTION);
+ }
+
+ 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 oc2gbts_mgr_instance *mgr = _data;
+
+ LOGP(DCALIB, LOGL_INFO, "Going to calibrate the system.\n");
+ rc = calib_run(mgr, 1);
+ if (rc != 0) {
+ calib_state_reset(mgr, CALIB_FAIL_START);
+ }
+}
+
+int oc2gbts_mgr_calib_run(struct oc2gbts_mgr_instance *mgr)
+{
+ return calib_run(mgr, 0);
+}
+
+static void schedule_bts_connect(struct oc2gbts_mgr_instance *mgr)
+{
+ DEBUGP(DLCTRL, "Scheduling BTS connect\n");
+ osmo_timer_schedule(&mgr->oc2gbts_ctrl.recon_timer, 1, 0);
+}
+
+/* link to BSC has gone up or down */
+static void bts_updown_cb(struct ipa_client_conn *link, int up)
+{
+ struct oc2gbts_mgr_instance *mgr = link->data;
+
+ LOGP(DLCTRL, LOGL_INFO, "BTS connection %s\n", up ? "up" : "down");
+
+ if (up) {
+ mgr->oc2gbts_ctrl.is_up = 1;
+ mgr->oc2gbts_ctrl.last_seqno = 0;
+ /* handle any pending alarm */
+ handle_alert_actions(mgr);
+ handle_warn_actions(mgr);
+ } else {
+ mgr->oc2gbts_ctrl.is_up = 0;
+ schedule_bts_connect(mgr);
+ }
+
+}
+
+/* BTS re-connect timer call-back */
+static void bts_recon_timer_cb(void *data)
+{
+ int rc;
+ struct oc2gbts_mgr_instance *mgr = data;
+
+ /* update LED pattern */
+ select_led_pattern(mgr);
+
+ /* The connection failures are to be expected during boot */
+ mgr->oc2gbts_ctrl.bts_conn->ofd->when |= BSC_FD_WRITE;
+ rc = ipa_client_conn_open(mgr->oc2gbts_ctrl.bts_conn);
+ if (rc < 0) {
+ LOGP(DLCTRL, LOGL_NOTICE, "Failed to connect to BTS.\n");
+ schedule_bts_connect(mgr);
+ }
+}
+
+static void oc2gbts_handle_ctrl(struct oc2gbts_mgr_instance *mgr, struct msgb *msg)
+{
+ struct ctrl_cmd *cmd = ctrl_cmd_parse(tall_mgr_ctx, msg);
+ int cause = atoi(cmd->reply);
+
+ if (!cmd) {
+ LOGP(DCALIB, LOGL_ERROR, "Failed to parse command/response\n");
+ return;
+ }
+
+ switch (cmd->type) {
+ case CTRL_TYPE_GET_REPLY:
+ LOGP(DCALIB, LOGL_INFO, "Got GET_REPLY from BTS cause=0x%x\n", cause);
+ break;
+ case CTRL_TYPE_SET_REPLY:
+ LOGP(DCALIB, LOGL_INFO, "Got SET_REPLY from BTS cause=0x%x\n", cause);
+ break;
+ default:
+ LOGP(DCALIB, LOGL_ERROR,
+ "Unhandled CTRL response: %d. Resetting state\n",
+ cmd->type);
+ break;
+ }
+
+ talloc_free(cmd);
+ return;
+}
+
+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;
+
+ LOGP(DLCTRL, LOGL_DEBUG, "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_OSMO:
+ hh_ext = (struct ipaccess_head_ext *) hh->data;
+ switch (hh_ext->proto) {
+ case IPAC_PROTO_EXT_CTRL:
+ oc2gbts_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;
+}
+
+int oc2gbts_mgr_calib_init(struct oc2gbts_mgr_instance *mgr)
+{
+ int rc;
+
+ /* initialize last uptime */
+ mgr->gps.last_update = 0;
+ rc = oc2gbts_par_set_uptime(tall_mgr_ctx, mgr->gps.last_update);
+ if (rc < 0)
+ LOGP(DCALIB, LOGL_ERROR, "Failed to store uptime to storage %d\n", rc);
+
+ /* get last GPS 3D fix timestamp */
+ mgr->gps.last_gps_fix = 0;
+ rc = oc2gbts_par_get_gps_fix(tall_mgr_ctx, &mgr->gps.last_gps_fix);
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR, "Failed to get last GPS 3D fix timestamp from storage. Create it anyway %d\n", rc);
+ rc = oc2gbts_par_set_gps_fix(tall_mgr_ctx, mgr->gps.last_gps_fix);
+ if (rc < 0)
+ LOGP(DCALIB, LOGL_ERROR, "Failed to store initial GPS fix to storage %d\n", rc);
+ }
+
+ 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 rc;
+}
+
+int oc2gbts_mgr_control_init(struct oc2gbts_mgr_instance *mgr)
+{
+ mgr->oc2gbts_ctrl.bts_conn = ipa_client_conn_create(tall_mgr_ctx, NULL, 0,
+ "127.0.0.1", OSMO_CTRL_PORT_BTS,
+ bts_updown_cb, bts_read_cb,
+ NULL, mgr);
+ if (!mgr->oc2gbts_ctrl.bts_conn) {
+ LOGP(DLCTRL, LOGL_ERROR, "Failed to create IPA connection to BTS\n");
+ return -1;
+ }
+
+ mgr->oc2gbts_ctrl.recon_timer.cb = bts_recon_timer_cb;
+ mgr->oc2gbts_ctrl.recon_timer.data = mgr;
+ schedule_bts_connect(mgr);
+
+ return 0;
+}
+
+void oc2gbts_mgr_dispatch_alarm(struct oc2gbts_mgr_instance *mgr, const int cause, const char *key, const char *text)
+{
+ /* Make sure the control link is ready before sending alarm */
+ if (mgr->oc2gbts_ctrl.bts_conn->state != IPA_CLIENT_LINK_STATE_CONNECTED) {
+ LOGP(DLCTRL, LOGL_NOTICE, "MGR losts connection to BTS.\n");
+ LOGP(DLCTRL, LOGL_NOTICE, "MGR drops an alert cause=0x%x, text=%s to BTS\n", cause, text);
+ return;
+ }
+
+ LOGP(DLCTRL, LOGL_DEBUG, "MGR sends an alert cause=0x%x, text=%s to BTS\n", cause, text);
+ send_set_ctrl_cmd(mgr, key, cause, text);
+ return;
+}
+
+
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c
new file mode 100644
index 00000000..db67caf2
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c
@@ -0,0 +1,208 @@
+/* NetworkListen for NuRAN OC-2G BTS management daemon */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "misc/oc2gbts_mgr.h"
+#include "misc/oc2gbts_misc.h"
+#include "misc/oc2gbts_nl.h"
+#include "misc/oc2gbts_par.h"
+#include "misc/oc2gbts_bid.h"
+
+#include <osmo-bts/logging.h>
+
+#include <osmocom/gsm/protocol/ipaccess.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/select.h>
+
+#include <arpa/inet.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#define ETH0_ADDR_SYSFS "/var/oc2g/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;
+ char rev_maj, rev_min;
+
+ /* 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 */
+ oc2gbts_par_get_int(OC2GBTS_PAR_SERNR, &serno);
+ snprintf(ser_str, sizeof(ser_str), "%d", serno);
+
+ /* fetch the model and trx number */
+ snprintf(model_name, sizeof(model_name), "OC-2G BTS");
+
+ oc2gbts_rev_get(&rev_maj, &rev_min);
+ snprintf(model_name, sizeof(model_name), "%s Rev %c.%c",
+ model_name, rev_maj, rev_min);
+
+ model = oc2gbts_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 oc2gbts_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-oc2g/misc/oc2gbts_mgr_temp.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_temp.c
new file mode 100644
index 00000000..f9efd9cd
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_temp.c
@@ -0,0 +1,980 @@
+/* Temperature control for NuRAN OC-2G BTS management daemon */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <inttypes.h>
+#include "misc/oc2gbts_mgr.h"
+#include "misc/oc2gbts_misc.h"
+#include "misc/oc2gbts_temp.h"
+#include "misc/oc2gbts_power.h"
+#include "misc/oc2gbts_led.h"
+#include "misc/oc2gbts_swd.h"
+#include "misc/oc2gbts_bid.h"
+#include "limits.h"
+
+#include <osmo-bts/logging.h>
+
+#include <osmocom/core/timer.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/linuxlist.h>
+
+struct oc2gbts_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 }
+};
+
+/* private function prototype */
+static void sensor_ctrl_check(struct oc2gbts_mgr_instance *mgr);
+
+const char *oc2gbts_mgr_sensor_get_state(enum oc2gbts_sensor_state state)
+{
+ return get_value_string(state_names, state);
+}
+
+static int next_state(enum oc2gbts_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_PA_ON) {
+ if (oc2gbts_power_set(OC2GBTS_POWER_PA, 1) != 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to switch on the PA\n");
+ } else {
+ LOGP(DTEMP, LOGL_INFO,
+ "Switched on the PA as normal action.\n");
+ }
+ }
+
+ if (actions & SENSOR_ACT_NORM_BTS_SRV_ON) {
+ LOGP(DTEMP, LOGL_INFO,
+ "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_PA_OFF) {
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ if (oc2gbts_power_set(OC2GBTS_POWER_PA, 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 & 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");
+ }
+}
+
+void handle_ceased_actions(struct oc2gbts_mgr_instance *mgr)
+{ int i;
+ uint32_t cause;
+
+ if (!mgr->oc2gbts_ctrl.is_up)
+ return;
+
+ LOGP(DTEMP, LOGL_DEBUG, "handle_ceased_actions in state %s, warn_flags=0x%x, crit_flags=0x%x\n",
+ oc2gbts_mgr_sensor_get_state(mgr->state.state),
+ mgr->oc2gbts_ctrl.warn_flags,
+ mgr->oc2gbts_ctrl.crit_flags);
+
+ for (i = 0; i < 32; i++) {
+ cause = 1 << i;
+ /* clear warning flag without sending ceased alarm */
+ if (mgr->oc2gbts_ctrl.warn_flags & cause)
+ mgr->oc2gbts_ctrl.warn_flags &= ~cause;
+
+ /* clear warning flag with sending ceased alarm */
+ if (mgr->oc2gbts_ctrl.crit_flags & cause) {
+ /* clear associated flag */
+ mgr->oc2gbts_ctrl.crit_flags &= ~cause;
+ /* dispatch ceased alarm */
+ switch (cause) {
+ case S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_SUPPLY_MAX_FAIL, "oc2g-oml-ceased", "Main power supply temperature is too high");
+ break;
+ case S_MGR_TEMP_SOC_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_SOC_MAX_FAIL, "oc2g-oml-ceased", "SoC temperature is too high");
+ break;
+ case S_MGR_TEMP_FPGA_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_FPGA_MAX_FAIL, "oc2g-oml-ceased", "FPGA temperature is too high");
+ break;
+ case S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_RMS_DET_MAX_FAIL, "oc2g-oml-ceased", "RMS detector temperature is too high");
+ break;
+ case S_MGR_TEMP_OCXO_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_OCXO_MAX_FAIL, "oc2g-oml-ceased", "OCXO temperature is too high");
+ break;
+ case S_MGR_TEMP_TRX_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_TRX_MAX_FAIL, "oc2g-oml-ceased", "TRX temperature is too high");
+ break;
+ case S_MGR_TEMP_PA_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_PA_MAX_FAIL, "oc2g-oml-ceased", "PA temperature is too high");
+ break;
+ case S_MGR_SUPPLY_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_SUPPLY_MAX_FAIL, "oc2g-oml-ceased", "Power supply voltage is too high");
+ break;
+ case S_MGR_SUPPLY_CRIT_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_SUPPLY_MIN_FAIL, "oc2g-oml-ceased", "Power supply voltage is too low");
+ break;
+ case S_MGR_VSWR_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_VSWR_MAX_FAIL, "oc2g-oml-ceased", "VSWR is too high");
+ break;
+ case S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_PWR_SUPPLY_MAX_FAIL, "oc2g-oml-ceased", "Power supply consumption is too high");
+ break;
+ case S_MGR_PWR_PA_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_PWR_PA_MAX_FAIL, "oc2g-oml-ceased", "PA power consumption is too high");
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ return;
+}
+
+void handle_alert_actions(struct oc2gbts_mgr_instance *mgr)
+{ int i;
+ uint32_t cause;
+
+ if (!mgr->oc2gbts_ctrl.is_up)
+ return;
+
+ LOGP(DTEMP, LOGL_DEBUG, "handle_alert_actions in state %s, crit_flags=0x%x\n",
+ oc2gbts_mgr_sensor_get_state(mgr->state.state),
+ mgr->oc2gbts_ctrl.crit_flags);
+
+ for (i = 0; i < 32; i++) {
+ cause = 1 << i;
+ if (mgr->oc2gbts_ctrl.crit_flags & cause) {
+ switch(cause) {
+ case S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_SUPPLY_MAX_FAIL, "oc2g-oml-alert", "Main power supply temperature is too high");
+ break;
+ case S_MGR_TEMP_SOC_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_SOC_MAX_FAIL, "oc2g-oml-alert", "SoC temperature is too high");
+ break;
+ case S_MGR_TEMP_FPGA_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_FPGA_MAX_FAIL, "oc2g-oml-alert", "FPGA temperature is too high");
+ break;
+ case S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_RMS_DET_MAX_FAIL, "oc2g-oml-alert", "RMS detector temperature is too high");
+ break;
+ case S_MGR_TEMP_OCXO_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_OCXO_MAX_FAIL, "oc2g-oml-alert", "OCXO temperature is too high");
+ break;
+ case S_MGR_TEMP_TRX_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_TRX_MAX_FAIL, "oc2g-oml-alert", "TRX temperature is too high");
+ break;
+ case S_MGR_TEMP_PA_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_PA_MAX_FAIL, "oc2g-oml-alert", "PA temperature is too high");
+ break;
+ case S_MGR_SUPPLY_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_SUPPLY_MAX_FAIL, "oc2g-oml-alert", "Power supply voltage is too high");
+ break;
+ case S_MGR_SUPPLY_CRIT_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_SUPPLY_MIN_FAIL, "oc2g-oml-alert", "Power supply voltage is too low");
+ break;
+ case S_MGR_VSWR_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_VSWR_MAX_FAIL, "oc2g-oml-alert", "VSWR is too high");
+ break;
+ case S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_PWR_SUPPLY_MAX_FAIL, "oc2g-oml-alert", "Power supply consumption is too high");
+ break;
+ case S_MGR_PWR_PA_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_PWR_PA_MAX_FAIL, "oc2g-oml-alert", "PA power consumption is too high");
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ return;
+}
+
+void handle_warn_actions(struct oc2gbts_mgr_instance *mgr)
+{ int i;
+ uint32_t cause;
+
+ if (!mgr->oc2gbts_ctrl.is_up)
+ return;
+
+ LOGP(DTEMP, LOGL_DEBUG, "handle_warn_actions in state %s, warn_flags=0x%x\n",
+ oc2gbts_mgr_sensor_get_state(mgr->state.state),
+ mgr->oc2gbts_ctrl.warn_flags);
+
+ for (i = 0; i < 32; i++) {
+ cause = 1 << i;
+ if (mgr->oc2gbts_ctrl.warn_flags & cause) {
+ switch(cause) {
+ case S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_SUPPLY_HIGH_FAIL, "oc2g-oml-alert", "Main power supply temperature is high");
+ break;
+ case S_MGR_TEMP_SUPPLY_WARN_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_SUPPLY_LOW_FAIL, "oc2g-oml-alert", "Main power supply temperature is low");
+ break;
+ case S_MGR_TEMP_SOC_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_SOC_HIGH_FAIL, "oc2g-oml-alert", "SoC temperature is high");
+ break;
+ case S_MGR_TEMP_SOC_WARN_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_SOC_LOW_FAIL, "oc2g-oml-alert", "SoC temperature is low");
+ break;
+ case S_MGR_TEMP_FPGA_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_FPGA_HIGH_FAIL, "oc2g-oml-alert", "FPGA temperature is high");
+ break;
+ case S_MGR_TEMP_FPGA_WARN_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_FPGA_LOW_FAIL, "oc2g-oml-alert", "FPGA temperature is low");
+ break;
+ case S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_RMS_DET_HIGH_FAIL, "oc2g-oml-alert", "RMS detector temperature is high");
+ break;
+ case S_MGR_TEMP_RMS_DET_WARN_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_RMS_DET_LOW_FAIL, "oc2g-oml-alert", "RMS detector temperature is low");
+ break;
+ case S_MGR_TEMP_OCXO_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_OCXO_HIGH_FAIL, "oc2g-oml-alert", "OCXO temperature is high");
+ break;
+ case S_MGR_TEMP_OCXO_WARN_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_OCXO_LOW_FAIL, "oc2g-oml-alert", "OCXO temperature is low");
+ break;
+ case S_MGR_TEMP_TRX_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_TRX_HIGH_FAIL, "oc2g-oml-alert", "TRX temperature is high");
+ break;
+ case S_MGR_TEMP_TRX_WARN_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_TRX_LOW_FAIL, "oc2g-oml-alert", "TRX temperature is low");
+ break;
+ case S_MGR_TEMP_PA_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_PA_HIGH_FAIL, "oc2g-oml-alert", "PA temperature is high");
+ break;
+ case S_MGR_TEMP_PA_WARN_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_PA_LOW_FAIL, "oc2g-oml-alert", "PA temperature is low");
+ break;
+ case S_MGR_SUPPLY_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_SUPPLY_HIGH_FAIL, "oc2g-oml-alert", "Power supply voltage is high");
+ break;
+ case S_MGR_SUPPLY_WARN_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_SUPPLY_LOW_FAIL, "oc2g-oml-alert", "Power supply voltage is low");
+ break;
+ case S_MGR_VSWR_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_VSWR_HIGH_FAIL, "oc2g-oml-alert", "VSWR is high");
+ break;
+ case S_MGR_PWR_SUPPLY_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_PWR_SUPPLY_HIGH_FAIL, "oc2g-oml-alert", "Power supply consumption is high");
+ break;
+ case S_MGR_PWR_PA_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_PWR_PA_HIGH_FAIL, "oc2g-oml-alert", "PA power consumption is high");
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ return;
+}
+
+/**
+ * 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 oc2gbts_mgr_instance *manager)
+{
+ LOGP(DTEMP, LOGL_NOTICE, "System is back to normal state.\n");
+ handle_ceased_actions(manager);
+ handle_normal_actions(manager->state.action_norm);
+}
+
+static void execute_warning_act(struct oc2gbts_mgr_instance *manager)
+{
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning state.\n");
+ handle_warn_actions(manager);
+ handle_actions(manager->state.action_warn);
+}
+
+/* Preventive timer call-back */
+static void preventive_timer_cb(void *_data)
+{
+ struct oc2gbts_mgr_instance *mgr = _data;
+
+ /* Delete current preventive timer if possible */
+ osmo_timer_del(&mgr->alarms.preventive_timer);
+
+ LOGP(DTEMP, LOGL_DEBUG, "Preventive timer expired in %d sec, retry=%d\n",
+ mgr->alarms.preventive_duration,
+ mgr->alarms.preventive_retry);
+
+ /* Turn on PA and clear action flag */
+ if (mgr->state.action_comb & SENSOR_ACT_PA_OFF) {
+ mgr->state.action_comb &= ~SENSOR_ACT_PA_OFF;
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ if (oc2gbts_power_set(OC2GBTS_POWER_PA, 1))
+ LOGP(DTEMP, LOGL_ERROR, "Failed to switch on the PA\n");
+ else
+ LOGP(DTEMP, LOGL_DEBUG, "Re-enable PA after preventive timer expired in %d sec\n",
+ mgr->alarms.preventive_duration);
+ }
+ }
+
+ /* restart check sensor timer */
+ osmo_timer_del(&sensor_ctrl_timer);
+ osmo_timer_schedule(&sensor_ctrl_timer, OC2GBTS_SENSOR_TIMER_DURATION, 0);
+
+ return;
+
+}
+
+static void execute_preventive_act(struct oc2gbts_mgr_instance *manager)
+{
+ struct oc2gbts_preventive_list *prevent_list, *prevent_list2;
+
+ /* update LED pattern */
+ select_led_pattern(manager);
+
+ /* do nothing if the preventive action list is empty */
+ if (llist_empty(&manager->alarms.list))
+ return;
+
+ llist_for_each_entry_safe(prevent_list, prevent_list2, &manager->alarms.list, list) {
+ /* Delete the timer in list and perform action*/
+ if (prevent_list) {
+ /* Delete current preventive timer if possible */
+ osmo_timer_del(&manager->alarms.preventive_timer);
+
+ /* Start/restart preventive timer */
+ if (prevent_list->param.sleep_sec) {
+ manager->alarms.preventive_timer.cb = preventive_timer_cb;
+ manager->alarms.preventive_timer.data = manager;
+ osmo_timer_schedule(&manager->alarms.preventive_timer, prevent_list->param.sleep_sec, 0);
+
+ LOGP(DTEMP, LOGL_DEBUG,"Preventive timer scheduled for %d sec, preventive flags=0x%x\n",
+ prevent_list->param.sleep_sec,
+ prevent_list->action_flag);
+ }
+ /* Update active flags */
+ manager->state.action_comb |= prevent_list->action_flag;
+
+ /* Turn off PA */
+ if (manager->state.action_comb & SENSOR_ACT_PA_OFF) {
+ if (oc2gbts_power_set(OC2GBTS_POWER_PA, 0))
+ LOGP(DTEMP, LOGL_ERROR, "Failed to switch off the PA\n");
+ }
+
+ /* Delete this preventive entry */
+ llist_del(&prevent_list->list);
+ talloc_free(prevent_list);
+ LOGP(DTEMP, LOGL_DEBUG,"Deleted preventive entry from list, entries left=%d\n",
+ llist_count(&manager->alarms.list));
+
+ /* stay in last state is preventive active has exceed maximum number of retries */
+ if (manager->alarms.preventive_retry > OC2GBTS_PREVENT_RETRY)
+ LOGP(DTEMP, LOGL_NOTICE, "Maximum number of preventive active exceed\n");
+ else
+ /* increase retry counter */
+ manager->alarms.preventive_retry++;
+ }
+ }
+ return;
+}
+
+static void execute_critical_act(struct oc2gbts_mgr_instance *manager)
+{
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical warning.\n");
+ handle_alert_actions(manager);
+ handle_actions(manager->state.action_crit);
+
+}
+
+static void oc2gbts_mgr_sensor_handle(struct oc2gbts_mgr_instance *manager,
+ int critical, int warning)
+{
+ int new_state = next_state(manager->state.state, critical, warning);
+
+ /* run preventive action if it is possible */
+ execute_preventive_act(manager);
+
+ /* Nothing changed */
+ if (new_state < 0)
+ return;
+ LOGP(DTEMP, LOGL_INFO, "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);
+ /* reset alarms */
+ manager->alarms.temp_high = 0;
+ manager->alarms.temp_max = 0;
+ manager->alarms.vswr_high = 0;
+ manager->alarms.vswr_max = 0;
+ manager->alarms.supply_low = 0;
+ manager->alarms.supply_min = 0;
+ manager->alarms.supply_pwr_high = 0;
+ manager->alarms.supply_pwr_max = 0;
+ manager->alarms.pa_pwr_max = 0;
+ manager->alarms.pa_pwr_high = 0;
+ manager->state.action_comb = 0;
+ manager->alarms.preventive_retry = 0;
+ /* update LED pattern */
+ select_led_pattern(manager);
+ break;
+ case STATE_WARNING_HYST:
+ /* do nothing? Maybe start to increase transmit power? */
+ break;
+ case STATE_WARNING:
+ execute_warning_act(manager);
+ /* update LED pattern */
+ select_led_pattern(manager);
+ break;
+ case STATE_CRITICAL:
+ execute_critical_act(manager);
+ /* update LED pattern */
+ select_led_pattern(manager);
+ break;
+ };
+}
+
+static void schedule_preventive_action(struct oc2gbts_mgr_instance *mgr, int action, int duration)
+{
+ struct oc2gbts_preventive_list *prevent_list;
+
+ /* add to pending list */
+ prevent_list = talloc_zero(tall_mgr_ctx, struct oc2gbts_preventive_list);
+ if (prevent_list) {
+ prevent_list->action_flag = action;
+ prevent_list->param.sleep_sec = duration;
+ prevent_list->param.sleep_usec = 0;
+ llist_add_tail(&prevent_list->list, &mgr->alarms.list);
+ LOGP(DTEMP, LOGL_DEBUG,"Added preventive action to list, duration=%d sec, total entries=%d\n",
+ prevent_list->param.sleep_sec,
+ llist_count(&mgr->alarms.list));
+ }
+ return;
+}
+
+static void sensor_ctrl_check(struct oc2gbts_mgr_instance *mgr)
+{
+ int rc;
+ int temp, volt, vswr, power = 0;
+ int warn_thresh_passed = 0;
+ int crit_thresh_passed = 0;
+ int action = 0;
+
+ LOGP(DTEMP, LOGL_INFO, "Going to check the temperature.\n");
+
+ /* Read the current supply temperature */
+ rc = oc2gbts_temp_get(OC2GBTS_TEMP_SUPPLY, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "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) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because supply temperature is over %d\n", mgr->temp.supply_temp_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.temp_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ if (temp < mgr->temp.supply_temp_limit.thresh_warn_min){
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because supply temperature is under %d\n", mgr->temp.supply_temp_limit.thresh_warn_min);
+ warn_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_SUPPLY_WARN_MIN_ALARM;
+ }
+ if (temp > mgr->temp.supply_temp_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because supply temperature is over %d\n", mgr->temp.supply_temp_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.temp_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "Supply temperature is: %d\n", temp);
+ }
+
+ /* Read the current SoC temperature */
+ rc = oc2gbts_temp_get(OC2GBTS_TEMP_SOC, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "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) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because SoC temperature is over %d\n", mgr->temp.soc_temp_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.temp_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_SOC_WARN_MAX_ALARM;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ if (temp < mgr->temp.soc_temp_limit.thresh_warn_min){
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because SoC temperature is under %d\n", mgr->temp.soc_temp_limit.thresh_warn_min);
+ warn_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_SOC_WARN_MIN_ALARM;
+ }
+ if (temp > mgr->temp.soc_temp_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because SoC temperature is over %d\n", mgr->temp.soc_temp_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.temp_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_SOC_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_SOC_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "SoC temperature is: %d\n", temp);
+ }
+
+ /* Read the current fpga temperature */
+ rc = oc2gbts_temp_get(OC2GBTS_TEMP_FPGA, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "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) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because fpga temperature is over %d\n", mgr->temp.fpga_temp_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.temp_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_FPGA_WARN_MAX_ALARM;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ if (temp < mgr->temp.fpga_temp_limit.thresh_warn_min) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because fpga temperature is under %d\n", mgr->temp.fpga_temp_limit.thresh_warn_min);
+ warn_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_FPGA_WARN_MIN_ALARM;
+ }
+ if (temp > mgr->temp.fpga_temp_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because fpga temperature is over %d\n", mgr->temp.fpga_temp_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.temp_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_FPGA_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_FPGA_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "FPGA temperature is: %d\n", temp);
+ }
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) || oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ /* Read the current RMS detector temperature */
+ rc = oc2gbts_temp_get(OC2GBTS_TEMP_RMSDET, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "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) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because RMS detector temperature is over %d\n", mgr->temp.rmsdet_temp_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.temp_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ if (temp < mgr->temp.rmsdet_temp_limit.thresh_warn_min) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because RMS detector temperature is under %d\n", mgr->temp.rmsdet_temp_limit.thresh_warn_min);
+ warn_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_RMS_DET_WARN_MIN_ALARM;
+ }
+ if (temp > mgr->temp.rmsdet_temp_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because RMS detector temperature is over %d\n", mgr->temp.rmsdet_temp_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.temp_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "RMS detector temperature is: %d\n", temp);
+ }
+ }
+
+ /* Read the current OCXO temperature */
+ rc = oc2gbts_temp_get(OC2GBTS_TEMP_OCXO, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "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) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because OCXO temperature is over %d\n", mgr->temp.ocxo_temp_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.temp_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_OCXO_WARN_MAX_ALARM;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ if (temp < mgr->temp.ocxo_temp_limit.thresh_warn_min) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because OCXO temperature is under %d\n", mgr->temp.ocxo_temp_limit.thresh_warn_min);
+ warn_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_OCXO_WARN_MIN_ALARM;
+ }
+ if (temp > mgr->temp.ocxo_temp_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because OCXO temperature is over %d\n", mgr->temp.ocxo_temp_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.temp_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_OCXO_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_OCXO_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "OCXO temperature is: %d\n", temp);
+ }
+
+ /* Read the current TX temperature */
+ rc = oc2gbts_temp_get(OC2GBTS_TEMP_TX, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the TX temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ temp = temp / 1000;
+ if (temp > mgr->temp.tx_temp_limit.thresh_warn_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because TX temperature is over %d\n", mgr->temp.tx_temp_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.temp_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_TRX_WARN_MAX_ALARM;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ if (temp < mgr->temp.tx_temp_limit.thresh_warn_min) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because TX temperature is under %d\n", mgr->temp.tx_temp_limit.thresh_warn_min);
+ warn_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_TRX_WARN_MIN_ALARM;
+ }
+ if (temp > mgr->temp.tx_temp_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because TX temperature is over %d\n", mgr->temp.tx_temp_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.temp_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_TRX_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_TRX_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "TX temperature is: %d\n", temp);
+ }
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) {
+ /* Read the current PA temperature */
+ rc = oc2gbts_temp_get(OC2GBTS_TEMP_PA, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the PA temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ temp = temp / 1000;
+ if (temp > mgr->temp.pa_temp_limit.thresh_warn_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because PA temperature because is over %d\n", mgr->temp.pa_temp_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.temp_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_PA_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ if (temp < mgr->temp.pa_temp_limit.thresh_warn_min) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because PA temperature because is under %d\n", mgr->temp.pa_temp_limit.thresh_warn_min);
+ warn_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_PA_WARN_MIN_ALARM;
+ }
+ if (temp > mgr->temp.pa_temp_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because PA temperature because is over %d\n", mgr->temp.pa_temp_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.temp_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_PA_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_PA_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "PA temperature is: %d\n", temp);
+ }
+ }
+
+ /* Read the current main supply voltage */
+ if (oc2gbts_power_get(OC2GBTS_POWER_SUPPLY)) {
+ rc = oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY, OC2GBTS_POWER_VOLTAGE, &volt);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the main supply voltage. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ if (volt > mgr->volt.supply_volt_limit.thresh_warn_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because supply voltage is over %d\n", mgr->volt.supply_volt_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_SUPPLY_WARN_MAX_ALARM;
+ }
+ if (volt < mgr->volt.supply_volt_limit.thresh_warn_min) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because supply voltage is under %d\n", mgr->volt.supply_volt_limit.thresh_warn_min);
+ warn_thresh_passed = 1;
+ mgr->alarms.supply_low = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_SUPPLY_WARN_MIN_ALARM;
+ }
+ if (volt > mgr->volt.supply_volt_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because supply voltage is over %d\n", mgr->volt.supply_volt_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_SUPPLY_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_SUPPLY_WARN_MAX_ALARM;
+ }
+
+ if (volt < mgr->volt.supply_volt_limit.thresh_crit_min) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because supply voltage is under %d\n", mgr->volt.supply_volt_limit.thresh_crit_min);
+ crit_thresh_passed = 1;
+ mgr->alarms.supply_min = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_SUPPLY_CRIT_MIN_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_SUPPLY_WARN_MIN_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_NONE);
+ }
+ LOGP(DTEMP, LOGL_INFO, "Main supply voltage is: %d\n", volt);
+ }
+ }
+
+ /* Read the main supply power consumption */
+ if (oc2gbts_power_get(OC2GBTS_POWER_SUPPLY)) {
+ rc = oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY, OC2GBTS_POWER_POWER, &power);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the power supply current. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ power /= 1000000;
+ if (power > mgr->pwr.supply_pwr_limit.thresh_warn_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because main supply power consumption is over %d\n", mgr->pwr.supply_pwr_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.supply_pwr_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_PWR_SUPPLY_WARN_MAX_ALARM;
+ }
+ if (power > mgr->pwr.supply_pwr_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because main supply power consumption is over %d\n", mgr->pwr.supply_pwr_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_PWR_SUPPLY_WARN_MAX_ALARM;
+
+ if (oc2gbts_power_get(OC2GBTS_POWER_PA)) {
+ mgr->alarms.supply_pwr_max = 1;
+ /* schedule to turn off PA */
+ action = SENSOR_ACT_PA_OFF;
+ /* repeat same alarm to BSC */
+ handle_alert_actions(mgr);
+ }
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_SHORT_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "Main supply current power consumption is: %d\n", power);
+ }
+ } else {
+ /* keep last state */
+ if (mgr->oc2gbts_ctrl.crit_flags & S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM) {
+ warn_thresh_passed = 1;
+ crit_thresh_passed = 1;
+ }
+ }
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ /* Read the current PA power consumption */
+ if (oc2gbts_power_get(OC2GBTS_POWER_PA)) {
+ rc = oc2gbts_power_sensor_get(OC2GBTS_POWER_PA, OC2GBTS_POWER_POWER, &power);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the PA power. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ power /= 1000000;
+ if (power > mgr->pwr.pa_pwr_limit.thresh_warn_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because PA power consumption is over %d\n", mgr->pwr.pa_pwr_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.pa_pwr_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_PWR_PA_WARN_MAX_ALARM;
+ }
+ if (power > mgr->pwr.pa_pwr_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because PA power consumption is over %d\n", mgr->pwr.pa_pwr_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.pa_pwr_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_PWR_PA_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_PWR_PA_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_SHORT_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "PA power consumption is: %d\n", power);
+ }
+ } else {
+ /* keep last state */
+ if (mgr->oc2gbts_ctrl.crit_flags & S_MGR_PWR_PA_CRIT_MAX_ALARM) {
+ warn_thresh_passed = 1;
+ crit_thresh_passed = 1;
+ }
+ }
+ }
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ /* Read the current VSWR of powered ON PA*/
+ if (oc2gbts_power_get(OC2GBTS_POWER_PA)) {
+ rc = oc2gbts_vswr_get(OC2GBTS_VSWR, &vswr);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the VSWR. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ if ((vswr > mgr->vswr.vswr_limit.thresh_warn_max) && (mgr->vswr.last_vswr > mgr->vswr.vswr_limit.thresh_warn_max)) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because VSWR is over %d\n", mgr->vswr.vswr_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.vswr_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_VSWR_WARN_MAX_ALARM;
+ }
+ if ((vswr > mgr->vswr.vswr_limit.thresh_crit_max) && (mgr->vswr.last_vswr > mgr->vswr.vswr_limit.thresh_crit_max)) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because VSWR is over %d\n", mgr->vswr.vswr_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.vswr_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_VSWR_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_VSWR_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "VSWR is: current = %d, last = %d\n", vswr, mgr->vswr.last_vswr);
+
+ /* update last VSWR */
+ mgr->vswr.last_vswr = vswr;
+ }
+ } else {
+ /* keep last state */
+ if (mgr->oc2gbts_ctrl.crit_flags & S_MGR_VSWR_CRIT_MAX_ALARM) {
+ warn_thresh_passed = 1;
+ crit_thresh_passed = 1;
+ }
+ }
+ }
+
+ select_led_pattern(mgr);
+ oc2gbts_mgr_sensor_handle(mgr, crit_thresh_passed, warn_thresh_passed);
+}
+
+static void sensor_ctrl_check_cb(void *_data)
+{
+ struct oc2gbts_mgr_instance *mgr = _data;
+ sensor_ctrl_check(mgr);
+ /* Check every minute? XXX make it configurable! */
+ osmo_timer_schedule(&sensor_ctrl_timer, OC2GBTS_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? */
+ oc2gbts_swd_event(mgr, SWD_CHECK_TEMP_SENSOR);
+}
+
+int oc2gbts_mgr_sensor_init(struct oc2gbts_mgr_instance *mgr)
+{
+ int rc = 0;
+
+ /* always enable PA GPIO for OC-2G */
+ if (!oc2gbts_power_get(OC2GBTS_POWER_PA)) {
+ rc = oc2gbts_power_set(OC2GBTS_POWER_PA, 1);
+ if (!rc)
+ LOGP(DTEMP, LOGL_ERROR, "Failed to set GPIO for internal PA\n");
+ }
+
+ s_mgr = mgr;
+ sensor_ctrl_timer.cb = sensor_ctrl_check_cb;
+ sensor_ctrl_timer.data = s_mgr;
+ sensor_ctrl_check_cb(s_mgr);
+ return rc;
+}
+
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_vty.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_vty.c
new file mode 100644
index 00000000..ef527394
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_vty.c
@@ -0,0 +1,984 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_mgr_vty.c
+ * (C) 2014 by oc2gcom - s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * Author: Alvaro Neira Ayuso <anayuso@oc2gcom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <inttypes.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/misc.h>
+
+#include <osmo-bts/logging.h>
+
+#include "oc2gbts_misc.h"
+#include "oc2gbts_mgr.h"
+#include "oc2gbts_temp.h"
+#include "oc2gbts_power.h"
+#include "oc2gbts_bid.h"
+#include "oc2gbts_led.h"
+#include "btsconfig.h"
+
+static struct oc2gbts_mgr_instance *s_mgr;
+
+static const char copyright[] =
+ "(C) 2012 by Harald Welte <laforge@gnumonks.org>\r\n"
+ "(C) 2014 by Holger Hans Peter Freyther\r\n"
+ "(C) 2015 by Yves Godin <support@nuranwireless.com>\r\n"
+ "License AGPLv3+: GNU AGPL version 2 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
+ "This is free software: you are free to change and redistribute it.\r\n"
+ "There is NO WARRANTY, to the extent permitted by law.\r\n";
+
+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_TX_TEMP_NODE:
+ case LIMIT_PA_TEMP_NODE:
+ case LIMIT_SUPPLY_VOLT_NODE:
+ case LIMIT_VSWR_NODE:
+ case LIMIT_SUPPLY_PWR_NODE:
+ case LIMIT_PA_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_TX_TEMP_NODE:
+ case LIMIT_PA_TEMP_NODE:
+ case LIMIT_SUPPLY_VOLT_NODE:
+ case LIMIT_VSWR_NODE:
+ case LIMIT_SUPPLY_PWR_NODE:
+ case LIMIT_PA_PWR_NODE:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static struct vty_app_info vty_info = {
+ .name = "oc2gbts-mgr",
+ .version = PACKAGE_VERSION,
+ .go_parent_cb = go_to_parent,
+ .is_config_node = is_config_node,
+ .copyright = copyright,
+};
+
+
+#define MGR_STR "Configure oc2gbts-mgr\n"
+
+static struct cmd_node mgr_node = {
+ MGR_NODE,
+ "%s(oc2gbts-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_tx_temp_node = {
+ LIMIT_TX_TEMP_NODE,
+ "%s(limit-tx-temp)# ",
+ 1,
+};
+static struct cmd_node limit_pa_temp_node = {
+ LIMIT_PA_TEMP_NODE,
+ "%s(limit-pa-temp)# ",
+ 1,
+};
+static struct cmd_node limit_supply_volt_node = {
+ LIMIT_SUPPLY_VOLT_NODE,
+ "%s(limit-supply-volt)# ",
+ 1,
+};
+static struct cmd_node limit_vswr_node = {
+ LIMIT_VSWR_NODE,
+ "%s(limit-vswr)# ",
+ 1,
+};
+static struct cmd_node limit_supply_pwr_node = {
+ LIMIT_SUPPLY_PWR_NODE,
+ "%s(limit-supply-pwr)# ",
+ 1,
+};
+static struct cmd_node limit_pa_pwr_node = {
+ LIMIT_PA_PWR_NODE,
+ "%s(limit-pa-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,
+ "oc2gbts-mgr",
+ MGR_STR)
+{
+ vty->node = MGR_NODE;
+ return CMD_SUCCESS;
+}
+
+static void write_volt_limit(struct vty *vty, const char *name,
+ struct oc2gbts_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 oc2gbts_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 oc2gbts_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, " %spa-on%s",
+ (actions & SENSOR_ACT_NORM_PA_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, " %spa-off%s",
+ (actions & SENSOR_ACT_PA_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, "oc2gbts-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 vswr", &s_mgr->vswr.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(tx_temp, "TX TEMP\n", LIMIT_TX_TEMP_NODE, tx_temp_limit)
+CFG_LIMIT_TEMP(pa_temp, "PA TEMP\n", LIMIT_PA_TEMP_NODE, pa_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(vswr, "VSWR\n", LIMIT_VSWR_NODE, 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(pa_pwr, "PA PWR\n", LIMIT_PA_PWR_NODE, pa_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 oc2gbts_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 oc2gbts_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 oc2gbts_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" "Warning level\n" "Range\n")
+{
+ struct oc2gbts_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 oc2gbts_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" "Warning level\n" "Range\n")
+{
+ struct oc2gbts_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_pa_on, cfg_action_pa_on_cmd,
+ "pa-on",
+ "Switch the Power Amplifier on\n")
+{
+ int *action = vty->index;
+ *action |= SENSOR_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 &= ~SENSOR_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 oc2gbts.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 oc2gbts.service\n")
+{
+ int *action = vty->index;
+ *action &= ~SENSOR_ACT_NORM_BTS_SRV_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 |= SENSOR_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 &= ~SENSOR_ACT_PA_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_bts_srv_off, cfg_action_bts_srv_off_cmd,
+ "bts-service-off",
+ "Stop the systemd oc2gbts.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 oc2gbts.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->oc2gbts_ctrl.warn_flags, VTY_NEWLINE);
+ vty_out(vty, "Critical alarm flags: 0x%08x%s",
+ s_mgr->oc2gbts_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",
+ oc2gbts_mgr_sensor_get_state(s_mgr->state.state), VTY_NEWLINE);
+ vty_out(vty, "Current Temperatures%s", VTY_NEWLINE);
+ oc2gbts_temp_get(OC2GBTS_TEMP_SUPPLY, &temp);
+ vty_out(vty, " Main Supply : %4.2f Celcius%s",
+ temp/ 1000.0f,
+ VTY_NEWLINE);
+ oc2gbts_temp_get(OC2GBTS_TEMP_SOC, &temp);
+ vty_out(vty, " SoC : %4.2f Celcius%s",
+ temp / 1000.0f,
+ VTY_NEWLINE);
+ oc2gbts_temp_get(OC2GBTS_TEMP_FPGA, &temp);
+ vty_out(vty, " FPGA : %4.2f Celcius%s",
+ temp / 1000.0f,
+ VTY_NEWLINE);
+ if (oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) ||
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ oc2gbts_temp_get(OC2GBTS_TEMP_RMSDET, &temp);
+ vty_out(vty, " RMSDet : %4.2f Celcius%s",
+ temp / 1000.0f,
+ VTY_NEWLINE);
+ }
+ oc2gbts_temp_get(OC2GBTS_TEMP_OCXO, &temp);
+ vty_out(vty, " OCXO : %4.2f Celcius%s",
+ temp / 1000.0f,
+ VTY_NEWLINE);
+ oc2gbts_temp_get(OC2GBTS_TEMP_TX, &temp);
+ vty_out(vty, " TX : %4.2f Celcius%s",
+ temp / 1000.0f,
+ VTY_NEWLINE);
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) {
+ oc2gbts_temp_get(OC2GBTS_TEMP_PA, &temp);
+ vty_out(vty, " Power Amp : %4.2f Celcius%s",
+ temp / 1000.0f,
+ VTY_NEWLINE);
+ }
+ vty_out(vty, "Power Status%s", VTY_NEWLINE);
+ oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY,
+ OC2GBTS_POWER_VOLTAGE, &volt);
+ oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY,
+ OC2GBTS_POWER_CURRENT, &current);
+ oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY,
+ OC2GBTS_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);
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ oc2gbts_power_sensor_get(OC2GBTS_POWER_PA,
+ OC2GBTS_POWER_VOLTAGE, &volt);
+ oc2gbts_power_sensor_get(OC2GBTS_POWER_PA,
+ OC2GBTS_POWER_CURRENT, &current);
+ oc2gbts_power_sensor_get(OC2GBTS_POWER_PA,
+ OC2GBTS_POWER_POWER, &power);
+ vty_out(vty, " Power Amp : %s [%6.2f Vdc, %4.2f A, %6.2f W]%s",
+ oc2gbts_power_get(OC2GBTS_POWER_PA) ? "ON " : "OFF",
+ volt /1000.0f,
+ current /1000.0f,
+ power /1000000.0f,
+ VTY_NEWLINE);
+ }
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ vty_out(vty, "VSWR Status%s", VTY_NEWLINE);
+ oc2gbts_vswr_get(OC2GBTS_VSWR, &vswr);
+ vty_out(vty, " VSWR : %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);
+ if (oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) ||
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ 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, " TX%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->temp.tx_temp_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->temp.tx_temp_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->temp.tx_temp_limit.thresh_warn_min, VTY_NEWLINE);
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) {
+ vty_out(vty, " PA%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->temp.pa_temp_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->temp.pa_temp_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->temp.pa_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);
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ vty_out(vty, " PA power (W)%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->pwr.pa_pwr_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->pwr.pa_pwr_limit.thresh_warn_max, VTY_NEWLINE);
+ }
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ vty_out(vty, "VSWR limits%s", VTY_NEWLINE);
+ vty_out(vty, " TX%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->vswr.vswr_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->vswr.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 (oc2gbts_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(tx, tx_temp_limit, "TX TEMP\n", thresh_warn_max, warning, max)
+LIMIT_TEMP(tx, tx_temp_limit, "TX TEMP\n", thresh_crit_max, critical, max)
+LIMIT_TEMP(tx, tx_temp_limit, "TX TEMP\n", thresh_warn_min, warning, min)
+LIMIT_TEMP(pa, pa_temp_limit, "PA TEMP\n", thresh_warn_max, warning, max)
+LIMIT_TEMP(pa, pa_temp_limit, "PA TEMP\n", thresh_crit_max, critical, max)
+LIMIT_TEMP(pa, pa_temp_limit, "PA 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(pa, pa_pwr_limit, "PA PWR\n", thresh_warn_max, warning, max)
+LIMIT_PWR(pa, pa_pwr_limit, "PA PWR\n", thresh_crit_max, critical, max)
+#undef LIMIT_PWR
+
+#define LIMIT_VSWR(limit, expl, variable, criticity, min_max) \
+DEFUN(limit_vswr_##variable, limit_vswr_##variable##_cmd, \
+ "limit vswr " #criticity " " #min_max " <1000-200000>", \
+ "Limit to reach\n" expl) \
+{ \
+ s_mgr->vswr.limit.variable = atoi(argv[0]); \
+ return CMD_SUCCESS; \
+}
+
+LIMIT_VSWR(vswr_limit, "VSWR\n", thresh_warn_max, warning, max)
+LIMIT_VSWR(vswr_limit, "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)
+{
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ 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);
+}
+
+static void register_action(int act)
+{
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ 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);
+}
+
+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_tx_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_tx_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_tx_thresh_warn_min_cmd);
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) {
+ install_element(ENABLE_NODE, &limit_temp_pa_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_pa_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_pa_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);
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ install_element(ENABLE_NODE, &limit_pwr_pa_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_pwr_pa_thresh_crit_max_cmd);
+ }
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ install_element(ENABLE_NODE, &limit_vswr_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_vswr_thresh_crit_max_cmd);
+ }
+
+ install_element(ENABLE_NODE, &limit_gpsfix_thresh_warn_max_cmd);
+}
+
+int oc2gbts_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);
+ vty_install_default(MGR_NODE);
+
+ /* install the limit nodes */
+ install_node(&limit_supply_temp_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_supply_temp_cmd);
+ vty_install_default(LIMIT_SUPPLY_TEMP_NODE);
+
+ install_node(&limit_soc_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_soc_temp_cmd);
+ vty_install_default(LIMIT_SOC_NODE);
+
+ install_node(&limit_fpga_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_fpga_temp_cmd);
+ vty_install_default(LIMIT_FPGA_NODE);
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) ||
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ install_node(&limit_rmsdet_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_rmsdet_temp_cmd);
+ vty_install_default(LIMIT_RMSDET_NODE);
+ }
+
+ install_node(&limit_ocxo_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_ocxo_temp_cmd);
+ vty_install_default(LIMIT_OCXO_NODE);
+
+ install_node(&limit_tx_temp_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_tx_temp_cmd);
+ vty_install_default(LIMIT_TX_TEMP_NODE);
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) {
+ install_node(&limit_pa_temp_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_pa_temp_cmd);
+ vty_install_default(LIMIT_PA_TEMP_NODE);
+ }
+
+ 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);
+ vty_install_default(LIMIT_SUPPLY_VOLT_NODE);
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ install_node(&limit_vswr_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_vswr_cmd);
+ register_limit(LIMIT_VSWR_NODE, MGR_LIMIT_TYPE_VSWR);
+ vty_install_default(LIMIT_VSWR_NODE);
+ }
+
+ 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);
+ vty_install_default(LIMIT_SUPPLY_PWR_NODE);
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ install_node(&limit_pa_pwr_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_pa_pwr_cmd);
+ vty_install_default(LIMIT_PA_PWR_NODE);
+ }
+
+ install_node(&limit_gps_fix_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_gps_fix_cmd);
+ vty_install_default(LIMIT_GPS_FIX_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);
+ vty_install_default(ACT_WARN_NODE);
+
+ install_node(&act_crit_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_action_critical_cmd);
+ register_action(ACT_CRIT_NODE);
+ vty_install_default(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 oc2gbts_mgr_parse_config(struct oc2gbts_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-oc2g/misc/oc2gbts_misc.c b/src/osmo-bts-oc2g/misc/oc2gbts_misc.c
new file mode 100644
index 00000000..bacf07bd
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_misc.c
@@ -0,0 +1,381 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_misc.c
+ * (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <time.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/application.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+
+#include "oc2gbts_mgr.h"
+#include "btsconfig.h"
+#include "oc2gbts_misc.h"
+#include "oc2gbts_par.h"
+#include "oc2gbts_temp.h"
+#include "oc2gbts_power.h"
+#include "oc2gbts_bid.h"
+
+/*********************************************************************
+ * Temperature handling
+ *********************************************************************/
+
+static const struct {
+ const char *name;
+ int has_max;
+ enum oc2gbts_temp_sensor sensor;
+ enum oc2gbts_par ee_par;
+} temp_data[] = {
+ {
+ .name = "supply_temp",
+ .has_max = 1,
+ .sensor = OC2GBTS_TEMP_SUPPLY,
+ .ee_par = OC2GBTS_PAR_TEMP_SUPPLY_MAX,
+ }, {
+ .name = "soc_temp",
+ .has_max = 0,
+ .sensor = OC2GBTS_TEMP_SOC,
+ .ee_par = OC2GBTS_PAR_TEMP_SOC_MAX,
+ }, {
+ .name = "fpga_temp",
+ .has_max = 0,
+ .sensor = OC2GBTS_TEMP_FPGA,
+ .ee_par = OC2GBTS_PAR_TEMP_FPGA_MAX,
+
+ }, {
+ .name = "rmsdet_temp",
+ .has_max = 1,
+ .sensor = OC2GBTS_TEMP_RMSDET,
+ .ee_par = OC2GBTS_PAR_TEMP_RMSDET_MAX,
+ }, {
+ .name = "ocxo_temp",
+ .has_max = 1,
+ .sensor = OC2GBTS_TEMP_OCXO,
+ .ee_par = OC2GBTS_PAR_TEMP_OCXO_MAX,
+ }, {
+ .name = "tx_temp",
+ .has_max = 0,
+ .sensor = OC2GBTS_TEMP_TX,
+ .ee_par = OC2GBTS_PAR_TEMP_TX_MAX,
+ }, {
+ .name = "pa_temp",
+ .has_max = 1,
+ .sensor = OC2GBTS_TEMP_PA,
+ .ee_par = OC2GBTS_PAR_TEMP_PA_MAX,
+ }
+};
+
+static const struct {
+ const char *name;
+ int has_max;
+ enum oc2gbts_power_source sensor_source;
+ enum oc2gbts_power_type sensor_type;
+ enum oc2gbts_par ee_par;
+} power_data[] = {
+ {
+ .name = "supply_volt",
+ .has_max = 1,
+ .sensor_source = OC2GBTS_POWER_SUPPLY,
+ .sensor_type = OC2GBTS_POWER_VOLTAGE,
+ .ee_par = OC2GBTS_PAR_VOLT_SUPPLY_MAX,
+ }, {
+ .name = "supply_pwr",
+ .has_max = 1,
+ .sensor_source = OC2GBTS_POWER_SUPPLY,
+ .sensor_type = OC2GBTS_POWER_POWER,
+ .ee_par = OC2GBTS_PAR_PWR_SUPPLY_MAX,
+ }, {
+ .name = "pa_pwr",
+ .has_max = 1,
+ .sensor_source = OC2GBTS_POWER_PA,
+ .sensor_type = OC2GBTS_POWER_POWER,
+ .ee_par = OC2GBTS_PAR_PWR_PA_MAX,
+ }
+};
+
+static const struct {
+ const char *name;
+ int has_max;
+ enum oc2gbts_vswr_sensor sensor;
+ enum oc2gbts_par ee_par;
+} vswr_data[] = {
+ {
+ .name = "vswr",
+ .has_max = 0,
+ .sensor = OC2GBTS_VSWR,
+ .ee_par = OC2GBTS_PAR_VSWR_MAX,
+ }
+};
+
+static const struct value_string power_unit_strs[] = {
+ { OC2GBTS_POWER_POWER, "W" },
+ { OC2GBTS_POWER_VOLTAGE, "V" },
+ { 0, NULL }
+};
+
+void oc2gbts_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 = -99;
+
+ if (temp_data[i].sensor == OC2GBTS_TEMP_PA &&
+ !oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP))
+ continue;
+
+ rc = oc2gbts_par_get_int(temp_data[i].ee_par, &ret);
+ temp_old[i] = ret * 1000;
+
+ oc2gbts_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)\n", temp_data[i].sensor);
+ 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 = oc2gbts_par_set_int(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 oc2gbts_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 = 0;
+
+ if (power_data[i].sensor_source == OC2GBTS_POWER_PA &&
+ !oc2gbts_option_get(OC2GBTS_OPTION_PA))
+ continue;
+
+ rc = oc2gbts_par_get_int(power_data[i].ee_par, &ret);
+ switch(power_data[i].sensor_type) {
+ case OC2GBTS_POWER_VOLTAGE:
+ div_ratio = 1000;
+ break;
+ case OC2GBTS_POWER_POWER:
+ div_ratio = 1000000;
+ break;
+ default:
+ div_ratio = 1000;
+ }
+ power_old[i] = ret * div_ratio;
+
+ oc2gbts_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 = oc2gbts_par_set_int(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 oc2gbts_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 = 0;
+
+ if (vswr_data[i].sensor == OC2GBTS_VSWR &&
+ (!oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) ||
+ !oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)))
+ continue;
+
+ rc = oc2gbts_par_get_int(vswr_data[i].ee_par, &ret);
+ vswr_old[i] = ret * 1000;
+
+ oc2gbts_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 = oc2gbts_par_set_int(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 oc2gbts_update_hours(int no_rom_write)
+{
+ time_t now = time(NULL);
+ int rc, op_hrs = 0;
+
+ /* first time after start of manager program */
+ if (last_update == 0) {
+ last_update = now;
+
+ rc = oc2gbts_par_get_int(OC2GBTS_PAR_HOURS, &op_hrs);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR, "Unable to read "
+ "operational hours: %d (%s)\n", rc,
+ strerror(errno));
+ /* create a new file anyway */
+ if (!no_rom_write)
+ rc = oc2gbts_par_set_int(OC2GBTS_PAR_HOURS, op_hrs);
+
+ return rc;
+ }
+
+ LOGP(DTEMP, LOGL_INFO, "Total hours of Operation: %u\n",
+ op_hrs);
+
+ return 0;
+ }
+
+ if (now >= last_update + 3600) {
+ rc = oc2gbts_par_get_int(OC2GBTS_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 = oc2gbts_par_set_int(OC2GBTS_PAR_HOURS, op_hrs);
+ if (rc < 0)
+ return rc;
+ }
+
+ last_update = now;
+ }
+
+ return 0;
+}
+
+/*********************************************************************
+ * Firmware reloading
+ *********************************************************************/
+
+static const char *fw_sysfs[_NUM_FW] = {
+ [OC2GBTS_FW_DSP] = "/sys/kernel/debug/remoteproc/remoteproc0/recovery",
+};
+
+int oc2gbts_firmware_reload(enum oc2gbts_firmware_type type)
+{
+ int fd;
+ int rc;
+
+ switch (type) {
+ case OC2GBTS_FW_DSP:
+ 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-oc2g/misc/oc2gbts_misc.h b/src/osmo-bts-oc2g/misc/oc2gbts_misc.h
new file mode 100644
index 00000000..78315679
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_misc.h
@@ -0,0 +1,17 @@
+#ifndef _OC2GBTS_MISC_H
+#define _OC2GBTS_MISC_H
+
+#include <stdint.h>
+
+void oc2gbts_check_temp(int no_rom_write);
+void oc2gbts_check_power(int no_rom_write);
+void oc2gbts_check_vswr(int no_rom_write);
+
+int oc2gbts_update_hours(int no_rom_write);
+
+enum oc2gbts_firmware_type {
+ OC2GBTS_FW_DSP,
+ _NUM_FW
+};
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_nl.c b/src/osmo-bts-oc2g/misc/oc2gbts_nl.c
new file mode 100644
index 00000000..39f64aae
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_nl.c
@@ -0,0 +1,123 @@
+/* Helper for netlink */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <arpa/inet.h>
+#include <netinet/ip.h>
+
+#include <sys/socket.h>
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#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-oc2g/misc/oc2gbts_nl.h b/src/osmo-bts-oc2g/misc/oc2gbts_nl.h
new file mode 100644
index 00000000..340cf117
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_nl.h
@@ -0,0 +1,27 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+#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-oc2g/misc/oc2gbts_par.c b/src/osmo-bts-oc2g/misc/oc2gbts_par.c
new file mode 100644
index 00000000..f3550243
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_par.c
@@ -0,0 +1,249 @@
+/* oc2gbts - access to hardware related parameters */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_par.c
+ * (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/talloc.h>
+
+#include "oc2gbts_par.h"
+
+const struct value_string oc2gbts_par_names[_NUM_OC2GBTS_PAR+1] = {
+ { OC2GBTS_PAR_TEMP_SUPPLY_MAX, "temp-supply-max" },
+ { OC2GBTS_PAR_TEMP_SOC_MAX, "temp-soc-max" },
+ { OC2GBTS_PAR_TEMP_FPGA_MAX, "temp-fpga-max" },
+ { OC2GBTS_PAR_TEMP_RMSDET_MAX, "temp-rmsdet-max" },
+ { OC2GBTS_PAR_TEMP_OCXO_MAX, "temp-ocxo-max" },
+ { OC2GBTS_PAR_TEMP_TX_MAX, "temp-tx-max" },
+ { OC2GBTS_PAR_TEMP_PA_MAX, "temp-pa-max" },
+ { OC2GBTS_PAR_VOLT_SUPPLY_MAX, "volt-supply-max" },
+ { OC2GBTS_PAR_PWR_SUPPLY_MAX, "pwr-supply-max" },
+ { OC2GBTS_PAR_PWR_PA_MAX, "pwr-pa-max" },
+ { OC2GBTS_PAR_VSWR_MAX, "vswr-max" },
+ { OC2GBTS_PAR_GPS_FIX, "gps-fix" },
+ { OC2GBTS_PAR_SERNR, "serial-nr" },
+ { OC2GBTS_PAR_HOURS, "hours-running" },
+ { OC2GBTS_PAR_BOOTS, "boot-count" },
+ { OC2GBTS_PAR_KEY, "key" },
+ { 0, NULL }
+};
+
+int oc2gbts_par_is_int(enum oc2gbts_par par)
+{
+ switch (par) {
+ case OC2GBTS_PAR_TEMP_SUPPLY_MAX:
+ case OC2GBTS_PAR_TEMP_SOC_MAX:
+ case OC2GBTS_PAR_TEMP_FPGA_MAX:
+ case OC2GBTS_PAR_TEMP_RMSDET_MAX:
+ case OC2GBTS_PAR_TEMP_OCXO_MAX:
+ case OC2GBTS_PAR_TEMP_TX_MAX:
+ case OC2GBTS_PAR_TEMP_PA_MAX:
+ case OC2GBTS_PAR_VOLT_SUPPLY_MAX:
+ case OC2GBTS_PAR_VSWR_MAX:
+ case OC2GBTS_PAR_SERNR:
+ case OC2GBTS_PAR_HOURS:
+ case OC2GBTS_PAR_BOOTS:
+ case OC2GBTS_PAR_PWR_SUPPLY_MAX:
+ case OC2GBTS_PAR_PWR_PA_MAX:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+FILE *oc2gbts_par_get_path(void *ctx, enum oc2gbts_par par, const char* mode)
+{
+ char *fpath;
+ FILE *fp;
+
+ if (par >= _NUM_OC2GBTS_PAR)
+ return NULL;
+
+ fpath = talloc_asprintf(ctx, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_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 oc2gbts_par_get_int(enum oc2gbts_par par, int *ret)
+{
+ char fpath[PATH_MAX];
+ FILE *fp;
+ int rc;
+
+ if (par >= _NUM_OC2GBTS_PAR)
+ return -ENODEV;
+
+ snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par));
+ fpath[sizeof(fpath)-1] = '\0';
+
+ fp = fopen(fpath, "r");
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fscanf(fp, "%d", ret);
+ if (rc != 1) {
+ fclose(fp);
+ return -EIO;
+ }
+ fclose(fp);
+ return 0;
+}
+
+int oc2gbts_par_set_int(enum oc2gbts_par par, int val)
+{
+ char fpath[PATH_MAX];
+ FILE *fp;
+ int rc;
+
+ if (par >= _NUM_OC2GBTS_PAR)
+ return -ENODEV;
+
+ snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par));
+ fpath[sizeof(fpath)-1] = '\0';
+
+ fp = fopen(fpath, "w");
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fprintf(fp, "%d", val);
+ if (rc < 0) {
+ fclose(fp);
+ return -EIO;
+ }
+ fsync(fp);
+ fclose(fp);
+ return 0;
+}
+
+int oc2gbts_par_get_buf(enum oc2gbts_par par, uint8_t *buf,
+ unsigned int size)
+{
+ char fpath[PATH_MAX];
+ FILE *fp;
+ int rc;
+
+ if (par >= _NUM_OC2GBTS_PAR)
+ return -ENODEV;
+
+ snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par));
+ fpath[sizeof(fpath)-1] = '\0';
+
+ fp = fopen(fpath, "rb");
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fread(buf, 1, size, fp);
+
+ fclose(fp);
+
+ return rc;
+}
+
+int oc2gbts_par_set_buf(enum oc2gbts_par par, const uint8_t *buf,
+ unsigned int size)
+{
+ char fpath[PATH_MAX];
+ FILE *fp;
+ int rc;
+
+ if (par >= _NUM_OC2GBTS_PAR)
+ return -ENODEV;
+
+ snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par));
+ fpath[sizeof(fpath)-1] = '\0';
+
+ fp = fopen(fpath, "wb");
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fwrite(buf, 1, size, fp);
+
+ fsync(fp);
+ fclose(fp);
+
+ return rc;
+}
+
+int oc2gbts_par_get_gps_fix(void *ctx, time_t *ret)
+{
+ FILE *fp;
+ int rc;
+
+ fp = oc2gbts_par_get_path(ctx, OC2GBTS_PAR_GPS_FIX, "r");
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fscanf(fp, "%ld", ret);
+ if (rc != 1) {
+ fclose(fp);
+ return -EIO;
+ }
+ fclose(fp);
+
+ return 0;
+}
+
+int oc2gbts_par_set_gps_fix(void *ctx, time_t val)
+{
+ FILE *fp;
+ int rc;
+
+ fp = oc2gbts_par_get_path(ctx, OC2GBTS_PAR_GPS_FIX, "w");
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fprintf(fp, "%ld", val);
+ if (rc < 0) {
+ fclose(fp);
+ return -EIO;
+ }
+ fsync(fp);
+ fclose(fp);
+
+ return 0;
+}
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_par.h b/src/osmo-bts-oc2g/misc/oc2gbts_par.h
new file mode 100644
index 00000000..588a3c32
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_par.h
@@ -0,0 +1,43 @@
+#ifndef _OC2GBTS_PAR_H
+#define _OC2GBTS_PAR_H
+
+#include <osmocom/core/utils.h>
+
+#define FACTORY_ROM_PATH "/mnt/rom/factory"
+#define USER_ROM_PATH "/var/run/oc2gbts-mgr"
+#define UPTIME_TMP_PATH "/tmp/uptime"
+
+enum oc2gbts_par {
+ OC2GBTS_PAR_TEMP_SUPPLY_MAX,
+ OC2GBTS_PAR_TEMP_SOC_MAX,
+ OC2GBTS_PAR_TEMP_FPGA_MAX,
+ OC2GBTS_PAR_TEMP_RMSDET_MAX,
+ OC2GBTS_PAR_TEMP_OCXO_MAX,
+ OC2GBTS_PAR_TEMP_TX_MAX,
+ OC2GBTS_PAR_TEMP_PA_MAX,
+ OC2GBTS_PAR_VOLT_SUPPLY_MAX,
+ OC2GBTS_PAR_PWR_SUPPLY_MAX,
+ OC2GBTS_PAR_PWR_PA_MAX,
+ OC2GBTS_PAR_VSWR_MAX,
+ OC2GBTS_PAR_GPS_FIX,
+ OC2GBTS_PAR_SERNR,
+ OC2GBTS_PAR_HOURS,
+ OC2GBTS_PAR_BOOTS,
+ OC2GBTS_PAR_KEY,
+ _NUM_OC2GBTS_PAR
+};
+
+extern const struct value_string oc2gbts_par_names[_NUM_OC2GBTS_PAR+1];
+
+int oc2gbts_par_get_int(enum oc2gbts_par par, int *ret);
+int oc2gbts_par_set_int(enum oc2gbts_par par, int val);
+int oc2gbts_par_get_buf(enum oc2gbts_par par, uint8_t *buf,
+ unsigned int size);
+int oc2gbts_par_set_buf(enum oc2gbts_par par, const uint8_t *buf,
+ unsigned int size);
+
+int oc2gbts_par_is_int(enum oc2gbts_par par);
+int oc2gbts_par_get_gps_fix(void *ctx, time_t *ret);
+int oc2gbts_par_set_gps_fix(void *ctx, time_t val);
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_power.c b/src/osmo-bts-oc2g/misc/oc2gbts_power.c
new file mode 100644
index 00000000..4e2fc95a
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_power.c
@@ -0,0 +1,177 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include "oc2gbts_power.h"
+
+static const char *power_enable_devs[_NUM_POWER_SOURCES] = {
+ [OC2GBTS_POWER_PA] = "/var/oc2g/pa-state/pa0/state",
+};
+
+static const char *power_sensor_devs[_NUM_POWER_SOURCES] = {
+ [OC2GBTS_POWER_SUPPLY] = "/var/oc2g/pwr-sense/main-supply/",
+ [OC2GBTS_POWER_PA] = "/var/oc2g/pwr-sense/pa0/",
+};
+
+static const char *power_sensor_type_str[_NUM_POWER_TYPES] = {
+ [OC2GBTS_POWER_POWER] = "power",
+ [OC2GBTS_POWER_VOLTAGE] = "voltage",
+ [OC2GBTS_POWER_CURRENT] = "current",
+};
+
+int oc2gbts_power_sensor_get(
+ enum oc2gbts_power_source source,
+ enum oc2gbts_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 oc2gbts_power_set(
+ enum oc2gbts_power_source source,
+ int en)
+{
+ int fd;
+ int rc;
+
+ if (source != OC2GBTS_POWER_PA) {
+ return -EINVAL;
+ }
+
+ 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 oc2gbts_power_get(
+ enum oc2gbts_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] = {
+ [OC2GBTS_VSWR] = "/var/oc2g/vswr/tx0/vswr",
+};
+
+int oc2gbts_vswr_get(enum oc2gbts_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-oc2g/misc/oc2gbts_power.h b/src/osmo-bts-oc2g/misc/oc2gbts_power.h
new file mode 100644
index 00000000..3229f243
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_power.h
@@ -0,0 +1,36 @@
+#ifndef _OC2GBTS_POWER_H
+#define _OC2GBTS_POWER_H
+
+enum oc2gbts_power_source {
+ OC2GBTS_POWER_SUPPLY,
+ OC2GBTS_POWER_PA,
+ _NUM_POWER_SOURCES
+};
+
+enum oc2gbts_power_type {
+ OC2GBTS_POWER_POWER,
+ OC2GBTS_POWER_VOLTAGE,
+ OC2GBTS_POWER_CURRENT,
+ _NUM_POWER_TYPES
+};
+
+int oc2gbts_power_sensor_get(
+ enum oc2gbts_power_source source,
+ enum oc2gbts_power_type type,
+ int *volt);
+
+int oc2gbts_power_set(
+ enum oc2gbts_power_source source,
+ int en);
+
+int oc2gbts_power_get(
+ enum oc2gbts_power_source source);
+
+enum oc2gbts_vswr_sensor {
+ OC2GBTS_VSWR,
+ _NUM_VSWR_SENSORS
+};
+
+int oc2gbts_vswr_get(enum oc2gbts_vswr_sensor sensor, int *vswr);
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_swd.c b/src/osmo-bts-oc2g/misc/oc2gbts_swd.c
new file mode 100644
index 00000000..59b795ac
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_swd.c
@@ -0,0 +1,178 @@
+/* Systemd service wd notification for OC-2G BTS management daemon */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "misc/oc2gbts_mgr.h"
+#include "misc/oc2gbts_swd.h"
+#include <osmocom/core/logging.h>
+
+/* Needed for service watchdog notification */
+#include <systemd/sd-daemon.h>
+
+/* 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 oc2gbts_mgr_instance *mgr);
+static void swd_process(struct oc2gbts_mgr_instance *mgr);
+static void swd_close(struct oc2gbts_mgr_instance *mgr);
+static void swd_state_reset(struct oc2gbts_mgr_instance *mgr, int reason);
+static int swd_run(struct oc2gbts_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 oc2gbts_mgr_instance *mgr)
+{
+ swd_process(mgr);
+}
+
+static void swd_process(struct oc2gbts_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_INFO, "Watchdog notification attempt\n");
+ notify = 1;
+ }
+ else {
+ LOGP(DSWD, LOGL_INFO, "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 oc2gbts_mgr_instance *mgr)
+{
+}
+
+static void swd_state_reset(struct oc2gbts_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 oc2gbts_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 oc2gbts_mgr_instance *mgr = _data;
+
+ LOGP(DSWD, LOGL_INFO, "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 'oc2gbts_swd_event'
+*/
+int oc2gbts_swd_init(struct oc2gbts_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 oc2gbts_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 oc2gbts_swd_event(struct oc2gbts_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-oc2g/misc/oc2gbts_swd.h b/src/osmo-bts-oc2g/misc/oc2gbts_swd.h
new file mode 100644
index 00000000..313712ac
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_swd.h
@@ -0,0 +1,7 @@
+#ifndef _OC2GBTS_SWD_H
+#define _OC2GBTS_SWD_H
+
+int oc2gbts_swd_init(struct oc2gbts_mgr_instance *mgr, int swd_num_events);
+int oc2gbts_swd_event(struct oc2gbts_mgr_instance *mgr, enum mgr_swd_events swd_event);
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_temp.c b/src/osmo-bts-oc2g/misc/oc2gbts_temp.c
new file mode 100644
index 00000000..8425dda3
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_temp.c
@@ -0,0 +1,71 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include <osmocom/core/utils.h>
+
+#include "oc2gbts_temp.h"
+
+static const char *temp_devs[_NUM_TEMP_SENSORS] = {
+ [OC2GBTS_TEMP_SUPPLY] = "/var/oc2g/temp/main-supply/temp",
+ [OC2GBTS_TEMP_SOC] = "/var/oc2g/temp/cpu/temp",
+ [OC2GBTS_TEMP_FPGA] = "/var/oc2g/temp/fpga/temp",
+ [OC2GBTS_TEMP_RMSDET] = "/var/oc2g/temp/rmsdet/temp",
+ [OC2GBTS_TEMP_OCXO] = "/var/oc2g/temp/ocxo/temp",
+ [OC2GBTS_TEMP_TX] = "/var/oc2g/temp/tx0/temp",
+ [OC2GBTS_TEMP_PA] = "/var/oc2g/temp/pa0/temp",
+};
+
+int oc2gbts_temp_get(enum oc2gbts_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-oc2g/misc/oc2gbts_temp.h b/src/osmo-bts-oc2g/misc/oc2gbts_temp.h
new file mode 100644
index 00000000..6d5dfca8
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_temp.h
@@ -0,0 +1,26 @@
+#ifndef _OC2GBTS_TEMP_H
+#define _OC2GBTS_TEMP_H
+
+enum oc2gbts_temp_sensor {
+ OC2GBTS_TEMP_SUPPLY,
+ OC2GBTS_TEMP_SOC,
+ OC2GBTS_TEMP_FPGA,
+ OC2GBTS_TEMP_RMSDET,
+ OC2GBTS_TEMP_OCXO,
+ OC2GBTS_TEMP_TX,
+ OC2GBTS_TEMP_PA,
+ _NUM_TEMP_SENSORS
+};
+
+enum oc2gbts_temp_type {
+ OC2GBTS_TEMP_INPUT,
+ OC2GBTS_TEMP_LOWEST,
+ OC2GBTS_TEMP_HIGHEST,
+ OC2GBTS_TEMP_FAULT,
+ _NUM_TEMP_TYPES
+};
+
+int oc2gbts_temp_get(enum oc2gbts_temp_sensor sensor, int *temp);
+
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_util.c b/src/osmo-bts-oc2g/misc/oc2gbts_util.c
new file mode 100644
index 00000000..b71f0383
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_util.c
@@ -0,0 +1,158 @@
+/* oc2gbts-util - access to hardware related parameters */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_misc.c
+ * (C) 2012-2013 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+
+
+#include "oc2gbts_par.h"
+
+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 = oc2gbts_par_names;
+
+ printf("oc2gbts-util [--void-warranty -r | -w value] param_name\n");
+ printf("Possible param names:\n");
+
+ for (; par->str != NULL; par += 1) {
+ if (!oc2gbts_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 oc2gbts_par par;
+ int rc, val;
+
+ 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(oc2gbts_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 = oc2gbts_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 = oc2gbts_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 = oc2gbts_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-oc2g/oc2gbts.c b/src/osmo-bts-oc2g/oc2gbts.c
new file mode 100644
index 00000000..012d705c
--- /dev/null
+++ b/src/osmo-bts-oc2g/oc2gbts.c
@@ -0,0 +1,361 @@
+/* NuRAN Wireless OC-2G L1 API related definitions */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ * based on:
+ * sysmobts.c
+ * (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <nrw/oc2g/oc2g.h>
+#include <nrw/oc2g/gsml1const.h>
+#include <nrw/oc2g/gsml1dbg.h>
+
+#include "oc2gbts.h"
+
+enum l1prim_type oc2gbts_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 oc2gbts_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 oc2gbts_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 oc2gbts_get_sysprim_type(Oc2g_PrimId_t id)
+{
+ switch (id) {
+ case Oc2g_PrimId_SystemInfoReq: return L1P_T_REQ;
+ case Oc2g_PrimId_SystemInfoCnf: return L1P_T_CONF;
+ case Oc2g_PrimId_SystemFailureInd: return L1P_T_IND;
+ case Oc2g_PrimId_ActivateRfReq: return L1P_T_REQ;
+ case Oc2g_PrimId_ActivateRfCnf: return L1P_T_CONF;
+ case Oc2g_PrimId_DeactivateRfReq: return L1P_T_REQ;
+ case Oc2g_PrimId_DeactivateRfCnf: return L1P_T_CONF;
+ case Oc2g_PrimId_SetTraceFlagsReq: return L1P_T_REQ;
+ case Oc2g_PrimId_Layer1ResetReq: return L1P_T_REQ;
+ case Oc2g_PrimId_Layer1ResetCnf: return L1P_T_CONF;
+ case Oc2g_PrimId_SetCalibTblReq: return L1P_T_REQ;
+ case Oc2g_PrimId_SetCalibTblCnf: return L1P_T_CONF;
+ case Oc2g_PrimId_MuteRfReq: return L1P_T_REQ;
+ case Oc2g_PrimId_MuteRfCnf: return L1P_T_CONF;
+ case Oc2g_PrimId_SetRxAttenReq: return L1P_T_REQ;
+ case Oc2g_PrimId_SetRxAttenCnf: return L1P_T_CONF;
+ case Oc2g_PrimId_IsAliveReq: return L1P_T_REQ;
+ case Oc2g_PrimId_IsAliveCnf: return L1P_T_CONF;
+ case Oc2g_PrimId_SetMaxCellSizeReq: return L1P_T_REQ;
+ case Oc2g_PrimId_SetMaxCellSizeCnf: return L1P_T_CONF;
+ case Oc2g_PrimId_SetC0IdleSlotPowerReductionReq: return L1P_T_REQ;
+ case Oc2g_PrimId_SetC0IdleSlotPowerReductionCnf: return L1P_T_CONF;
+ default: return L1P_T_INVALID;
+ }
+}
+
+const struct value_string oc2gbts_sysprim_names[Oc2g_PrimId_NUM+1] = {
+ { Oc2g_PrimId_SystemInfoReq, "SYSTEM-INFO.req" },
+ { Oc2g_PrimId_SystemInfoCnf, "SYSTEM-INFO.conf" },
+ { Oc2g_PrimId_SystemFailureInd, "SYSTEM-FAILURE.ind" },
+ { Oc2g_PrimId_ActivateRfReq, "ACTIVATE-RF.req" },
+ { Oc2g_PrimId_ActivateRfCnf, "ACTIVATE-RF.conf" },
+ { Oc2g_PrimId_DeactivateRfReq, "DEACTIVATE-RF.req" },
+ { Oc2g_PrimId_DeactivateRfCnf, "DEACTIVATE-RF.conf" },
+ { Oc2g_PrimId_SetTraceFlagsReq, "SET-TRACE-FLAGS.req" },
+ { Oc2g_PrimId_Layer1ResetReq, "LAYER1-RESET.req" },
+ { Oc2g_PrimId_Layer1ResetCnf, "LAYER1-RESET.conf" },
+ { Oc2g_PrimId_SetCalibTblReq, "SET-CALIB.req" },
+ { Oc2g_PrimId_SetCalibTblCnf, "SET-CALIB.cnf" },
+ { Oc2g_PrimId_MuteRfReq, "MUTE-RF.req" },
+ { Oc2g_PrimId_MuteRfCnf, "MUTE-RF.cnf" },
+ { Oc2g_PrimId_SetRxAttenReq, "SET-RX-ATTEN.req" },
+ { Oc2g_PrimId_SetRxAttenCnf, "SET-RX-ATTEN-CNF.cnf" },
+ { Oc2g_PrimId_IsAliveReq, "IS-ALIVE.req" },
+ { Oc2g_PrimId_IsAliveCnf, "IS-ALIVE-CNF.cnf" },
+ { Oc2g_PrimId_SetMaxCellSizeReq, "SET-MAX-CELL-SIZE.req" },
+ { Oc2g_PrimId_SetMaxCellSizeCnf, "SET-MAX-CELL-SIZE.cnf" },
+ { Oc2g_PrimId_SetC0IdleSlotPowerReductionReq, "SET-C0-IDLE-PWR-RED.req" },
+ { Oc2g_PrimId_SetC0IdleSlotPowerReductionCnf, "SET-C0-IDLE-PWR-RED.cnf" },
+ { 0, NULL }
+};
+
+Oc2g_PrimId_t oc2gbts_get_sysprim_conf(Oc2g_PrimId_t id)
+{
+ switch (id) {
+ case Oc2g_PrimId_SystemInfoReq: return Oc2g_PrimId_SystemInfoCnf;
+ case Oc2g_PrimId_ActivateRfReq: return Oc2g_PrimId_ActivateRfCnf;
+ case Oc2g_PrimId_DeactivateRfReq: return Oc2g_PrimId_DeactivateRfCnf;
+ case Oc2g_PrimId_Layer1ResetReq: return Oc2g_PrimId_Layer1ResetCnf;
+ case Oc2g_PrimId_SetCalibTblReq: return Oc2g_PrimId_SetCalibTblCnf;
+ case Oc2g_PrimId_MuteRfReq: return Oc2g_PrimId_MuteRfCnf;
+ case Oc2g_PrimId_SetRxAttenReq: return Oc2g_PrimId_SetRxAttenCnf;
+ case Oc2g_PrimId_IsAliveReq: return Oc2g_PrimId_IsAliveCnf;
+ case Oc2g_PrimId_SetMaxCellSizeReq: return Oc2g_PrimId_SetMaxCellSizeCnf;
+ case Oc2g_PrimId_SetC0IdleSlotPowerReductionReq: return Oc2g_PrimId_SetC0IdleSlotPowerReductionCnf;
+ default: return -1; // Weak
+ }
+}
+
+const struct value_string oc2gbts_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 oc2gbts_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 oc2gbts_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 oc2gbts_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 oc2gbts_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 oc2gbts_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 oc2gbts_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
+};
+
+const struct value_string oc2gbts_rsl_ho_causes[] = {
+ { IPAC_HO_RQD_CAUSE_L_RXLEV_UL_H, "L_RXLEV_UL_H" },
+ { IPAC_HO_RQD_CAUSE_L_RXLEV_DL_H, "L_RXLEV_DL_H" },
+ { IPAC_HO_RQD_CAUSE_L_RXQUAL_UL_H, "L_RXQUAL_UL_H" },
+ { IPAC_HO_RQD_CAUSE_L_RXQUAL_DL_H, "L_RXQUAL_DL_H" },
+ { IPAC_HO_RQD_CAUSE_RXLEV_UL_IH, "RXLEV_UL_IH" },
+ { IPAC_HO_RQD_CAUSE_RXLEV_DL_IH, "RXLEV_DL_IH" },
+ { IPAC_HO_RQD_CAUSE_MAX_MS_RANGE, "MAX_MS_RANGE" },
+ { IPAC_HO_RQD_CAUSE_POWER_BUDGET, "POWER_BUDGET" },
+ { IPAC_HO_RQD_CAUSE_ENQUIRY, "ENQUIRY" },
+ { IPAC_HO_RQD_CAUSE_ENQUIRY_FAILED, "ENQUIRY_FAILED" },
+ { 0, NULL }
+};
diff --git a/src/osmo-bts-oc2g/oc2gbts.h b/src/osmo-bts-oc2g/oc2gbts.h
new file mode 100644
index 00000000..9eb87452
--- /dev/null
+++ b/src/osmo-bts-oc2g/oc2gbts.h
@@ -0,0 +1,92 @@
+#ifndef OC2GBTS_H
+#define OC2GBTS_H
+
+#include <stdlib.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/protocol/gsm_08_58.h>
+
+#include <nrw/oc2g/oc2g.h>
+#include <nrw/oc2g/gsml1const.h>
+
+/*
+ * Depending on the firmware version either GsmL1_Prim_t or Oc2g_Prim_t
+ * is the bigger struct. For earlier firmware versions the GsmL1_Prim_t was the
+ * bigger struct.
+ */
+#define OC2GBTS_PRIM_SIZE \
+ (OSMO_MAX(sizeof(Oc2g_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 oc2g_pedestal_mode{
+ OC2G_PEDESTAL_OFF = 0,
+ OC2G_PEDESTAL_ON,
+};
+
+enum oc2g_led_control_mode{
+ OC2G_LED_CONTROL_BTS = 0,
+ OC2G_LED_CONTROL_EXT,
+};
+
+enum oc2g_auto_pwr_adjust_mode{
+ OC2G_TX_PWR_ADJ_NONE = 0,
+ OC2G_TX_PWR_ADJ_AUTO,
+};
+
+enum l1prim_type oc2gbts_get_l1prim_type(GsmL1_PrimId_t id);
+const struct value_string oc2gbts_l1prim_names[GsmL1_PrimId_NUM+1];
+GsmL1_PrimId_t oc2gbts_get_l1prim_conf(GsmL1_PrimId_t id);
+
+enum l1prim_type oc2gbts_get_sysprim_type(Oc2g_PrimId_t id);
+const struct value_string oc2gbts_sysprim_names[Oc2g_PrimId_NUM+1];
+Oc2g_PrimId_t oc2gbts_get_sysprim_conf(Oc2g_PrimId_t id);
+
+const struct value_string oc2gbts_l1sapi_names[GsmL1_Sapi_NUM+1];
+const struct value_string oc2gbts_l1status_names[GSML1_STATUS_NUM+1];
+
+const struct value_string oc2gbts_tracef_names[29];
+const struct value_string oc2gbts_tracef_docs[29];
+
+const struct value_string oc2gbts_tch_pl_names[15];
+
+const struct value_string oc2gbts_clksrc_names[10];
+
+const struct value_string oc2gbts_dir_names[6];
+
+const struct value_string oc2gbts_rsl_ho_causes[IPAC_HO_RQD_CAUSE_MAX];
+
+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];
+
+/* OC2G default parameters */
+#define OC2G_BTS_MAX_CELL_SIZE_DEFAULT 166 /* 166 qbits is default value */
+#define OC2G_BTS_PEDESTAL_MODE_DEFAULT 0 /* Unused TS is off by default */
+#define OC2G_BTS_LED_CTRL_MODE_DEFAULT 0 /* LED is controlled by BTS by default */
+#define OC2G_BTS_DSP_ALIVE_TMR_DEFAULT 5 /* Default DSP alive timer is 5 seconds */
+#define OC2G_BTS_TX_PWR_ADJ_DEFAULT 0 /* Default Tx power auto adjustment is none */
+#define OC2G_BTS_TX_RED_PWR_8PSK_DEFAULT 0 /* Default 8-PSK maximum power level is 0 dB */
+#define OC2G_BTS_RTP_DRIFT_THRES_DEFAULT 0 /* Default RTP drift threshold is 0 ms (disabled) */
+#define OC2G_BTS_TX_C0_IDLE_RED_PWR_DEFAULT 0 /* Default C0 idle slot reduction power level is 0 dB */
+
+#endif /* OC2GBTS_H */
diff --git a/src/osmo-bts-oc2g/oc2gbts_vty.c b/src/osmo-bts-oc2g/oc2gbts_vty.c
new file mode 100644
index 00000000..4f7c45a1
--- /dev/null
+++ b/src/osmo-bts-oc2g/oc2gbts_vty.c
@@ -0,0 +1,733 @@
+/* VTY interface for NuRAN Wireless OC-2G */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org>
+ *
+ * Based on sysmoBTS:
+ * (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ * (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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <ctype.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/rate_ctr.h>
+
+#include <osmocom/gsm/tlv.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/misc.h>
+
+#include <osmocom/ctrl/control_cmd.h>
+#include <osmo-bts/signal.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/bts.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/vty.h>
+#include <osmo-bts/rsl.h>
+
+#include "oc2gbts.h"
+#include "l1_if.h"
+#include "utils.h"
+
+extern int lchan_activate(struct gsm_lchan *lchan);
+extern int rsl_tx_preproc_meas_res(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;
+
+static const struct value_string oc2g_pedestal_mode_strs[] = {
+ { OC2G_PEDESTAL_OFF, "off" },
+ { OC2G_PEDESTAL_ON, "on" },
+ { 0, NULL }
+};
+
+static const struct value_string oc2g_led_mode_strs[] = {
+ { OC2G_LED_CONTROL_BTS, "bts" },
+ { OC2G_LED_CONTROL_EXT, "external" },
+ { 0, NULL }
+};
+
+static const struct value_string oc2g_auto_adj_pwr_strs[] = {
+ { OC2G_TX_PWR_ADJ_NONE, "none" },
+ { OC2G_TX_PWR_ADJ_AUTO, "auto" },
+ { 0, NULL }
+};
+
+/* 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.oc2g.calib_path)
+ talloc_free(pinst->u.oc2g.calib_path);
+
+ pinst->u.oc2g.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(oc2gbts_tracef_names, argv[1]);
+ pinst->u.oc2g.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(oc2gbts_tracef_names, argv[1]);
+ pinst->u.oc2g.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 oc2gl1_hdl *fl1h;
+ int i;
+
+ if (!trx)
+ return CMD_WARNING;
+
+ fl1h = trx_oc2gl1_hdl(trx);
+
+ vty_out(vty, "Oc2g L1 DSP trace flags:%s", VTY_NEWLINE);
+ for (i = 0; i < ARRAY_SIZE(oc2gbts_tracef_names); i++) {
+ const char *endis;
+
+ if (oc2gbts_tracef_names[i].value == 0 &&
+ oc2gbts_tracef_names[i].str == NULL)
+ break;
+
+ if (fl1h->dsp_trace_f & oc2gbts_tracef_names[i].value)
+ endis = "enabled";
+ else
+ endis = "disabled";
+
+ vty_out(vty, "DSP Trace %-15s %s%s",
+ oc2gbts_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 oc2gl1_hdl *fl1h;
+ unsigned int flag ;
+
+ pinst = vty_get_phy_instance(vty, phy_nr, 0);
+ if (!pinst)
+ return CMD_WARNING;
+
+ fl1h = pinst->u.oc2g.hdl;
+ flag = get_string_value(oc2gbts_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 oc2gl1_hdl *fl1h;
+ unsigned int flag ;
+
+ pinst = vty_get_phy_instance(vty, phy_nr, 0);
+ if (!pinst)
+ return CMD_WARNING;
+
+ fl1h = pinst->u.oc2g.hdl;
+ flag = get_string_value(oc2gbts_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-0> 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 oc2gl1_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.oc2g.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.oc2g.minTxPower, VTY_NEWLINE);
+ vty_out(vty, "Max Tx Power: %d dBm%s", fl1h->phy_inst->u.oc2g.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;
+}
+
+DEFUN(cfg_phy_max_cell_size, cfg_phy_max_cell_size_cmd,
+ "max-cell-size <0-166>",
+ "Set the maximum cell size in qbits\n")
+{
+ struct phy_instance *pinst = vty->index;
+ int cell_size = (uint8_t)atoi(argv[0]);
+
+ if (( cell_size > 166 ) || ( cell_size < 0 )) {
+ vty_out(vty, "Max cell size must be between 0 and 166 qbits (%d) %s",
+ cell_size, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ pinst->u.oc2g.max_cell_size = (uint8_t)cell_size;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_pedestal_mode, cfg_phy_pedestal_mode_cmd,
+ "pedestal-mode (on|off)",
+ "Set unused time-slot transmission in pedestal mode\n"
+ "Transmission pedestal mode can be (off, on)\n")
+{
+ struct phy_instance *pinst = vty->index;
+ int val = get_string_value(oc2g_pedestal_mode_strs, argv[0]);
+
+ if((val < OC2G_PEDESTAL_OFF) || (val > OC2G_PEDESTAL_ON)) {
+ vty_out(vty, "Invalid unused time-slot transmission mode %d%s", val, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ pinst->u.oc2g.pedestal_mode = (uint8_t)val;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_dsp_alive_timer, cfg_phy_dsp_alive_timer_cmd,
+ "dsp-alive-period <0-60>",
+ "Set DSP alive timer period in second\n")
+{
+ struct phy_instance *pinst = vty->index;
+ uint8_t period = (uint8_t)atoi(argv[0]);
+
+ if (( period > 60 ) || ( period < 0 )) {
+ vty_out(vty, "DSP heart beat alive timer period must be between 0 and 60 seconds (%d) %s",
+ period, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ pinst->u.oc2g.dsp_alive_period = period;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_auto_tx_pwr_adj, cfg_phy_auto_tx_pwr_adj_cmd,
+ "pwr-adj-mode (none|auto)",
+ "Set output power adjustment mode\n")
+{
+ struct phy_instance *pinst = vty->index;
+ int val = get_string_value(oc2g_auto_adj_pwr_strs, argv[0]);
+
+ if((val < OC2G_TX_PWR_ADJ_NONE) || (val > OC2G_TX_PWR_ADJ_AUTO)) {
+ vty_out(vty, "Invalid output power adjustment mode %d%s", val, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ pinst->u.oc2g.tx_pwr_adj_mode = (uint8_t)val;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_tx_red_pwr_8psk, cfg_phy_tx_red_pwr_8psk_cmd,
+ "tx-red-pwr-8psk <0-40>",
+ "Set reduction output power for 8-PSK scheme in dB unit\n")
+{
+ struct phy_instance *pinst = vty->index;
+ int val = atoi(argv[0]);
+
+ if ((val > 40) || (val < 0)) {
+ vty_out(vty, "Reduction Tx power level must be between 0 and 40 dB (%d) %s",
+ val, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ pinst->u.oc2g.tx_pwr_red_8psk = (uint8_t)val;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_phy_c0_idle_red_pwr, cfg_phy_c0_idle_red_pwr_cmd,
+ "c0-idle-red-pwr <0-40>",
+ "Set reduction output power for C0 idle slot in dB unit\n")
+{
+ struct phy_instance *pinst = vty->index;
+ int val = atoi(argv[0]);
+
+ if ((val > 40) || (val < 0)) {
+ vty_out(vty, "Reduction Tx power level must be between 0 and 40 dB (%d) %s",
+ val, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ pinst->u.oc2g.tx_c0_idle_pwr_red = (uint8_t)val;
+ return CMD_SUCCESS;
+}
+
+DEFUN(trigger_ho_cause, trigger_ho_cause_cmd, "HIDDEN", TRX_STR)
+{
+ 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 trx_nr, ts_nr, lchan_nr;
+ uint8_t ho_cause;
+ uint8_t old_ho_cause;
+
+ /* get BTS pointer */
+ bts = gsm_bts_num(net, 0);
+ if (!bts) {
+ vty_out(vty, "Can not get BTS node %s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ /* get TRX pointer */
+ if (argc >= 1) {
+ trx_nr = atoi(argv[0]);
+ if (trx_nr >= bts->num_trx) {
+ vty_out(vty, "%% can't find TRX %s%s", argv[0],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ trx = gsm_bts_trx_num(bts, trx_nr);
+ }
+ /* get TS pointer */
+ if (argc >= 2) {
+ ts_nr = atoi(argv[1]);
+ if (ts_nr >= TRX_NR_TS) {
+ vty_out(vty, "%% can't find TS %s%s", argv[1],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ ts = &trx->ts[ts_nr];
+ }
+ /* get lchan pointer */
+ if (argc >= 3) {
+ lchan_nr = atoi(argv[2]);
+ if (lchan_nr >= TS_MAX_LCHAN) {
+ vty_out(vty, "%% can't find LCHAN %s%s", argv[2],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ lchan = &ts->lchan[lchan_nr];
+ }
+
+ /* get HO cause */
+ if (argc >= 4) {
+ ho_cause = get_string_value(oc2gbts_rsl_ho_causes, argv[3]);
+ if (ho_cause >= IPAC_HO_RQD_CAUSE_MAX) {
+ vty_out(vty, "%% can't find valid HO cause %s%s", argv[3],
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ } else {
+ vty_out(vty, "%% HO cause is not provided %s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ /* TODO(oramadan) MERGE
+ /* store recorded HO causes * /
+ old_ho_cause = lchan->meas_preproc.rec_ho_causes;
+
+ /* Apply new HO causes * /
+ lchan->meas_preproc.rec_ho_causes = 1 << (ho_cause - 1);
+
+ /* Send measuremnt report to BSC * /
+ rsl_tx_preproc_meas_res(lchan);
+
+ /* restore HO cause * /
+ lchan->meas_preproc.rec_ho_causes = old_ho_cause;
+ */
+
+ return CMD_SUCCESS;
+}
+
+/* TODO(oramadan) MERGE
+DEFUN(cfg_bts_rtp_drift_threshold, cfg_bts_rtp_drift_threshold_cmd,
+ "rtp-drift-threshold <0-10000>",
+ "RTP parameters\n"
+ "RTP timestamp drift threshold in ms\n")
+{
+ struct gsm_bts *bts = vty->index;
+ struct gsm_bts_role_bts *btsb = bts_role_bts(bts);
+
+ btsb->oc2g.rtp_drift_thres_ms = (unsigned int) atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+*/
+
+void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts)
+{
+ /* TODO(oramadan) MERGE
+ struct gsm_bts_role_bts *btsb = bts_role_bts(bts);
+
+ vty_out(vty, " led-control-mode %s%s",
+ get_value_string(oc2g_led_mode_strs, btsb->oc2g.led_ctrl_mode), VTY_NEWLINE);
+
+ vty_out(vty, " rtp-drift-threshold %d%s",
+ btsb->oc2g.rtp_drift_thres_ms, VTY_NEWLINE);
+ */
+
+}
+
+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.oc2g.dsp_trace_f & (1 << i)) {
+ const char *name;
+ name = get_value_string(oc2gbts_tracef_names, (1 << i));
+ vty_out(vty, " dsp-trace-flag %s%s", name,
+ VTY_NEWLINE);
+ }
+ }
+ if (pinst->u.oc2g.calib_path)
+ vty_out(vty, " trx-calibration-path %s%s",
+ pinst->u.oc2g.calib_path, VTY_NEWLINE);
+
+ vty_out(vty, " max-cell-size %d%s",
+ pinst->u.oc2g.max_cell_size, VTY_NEWLINE);
+
+ vty_out(vty, " pedestal-mode %s%s",
+ get_value_string(oc2g_pedestal_mode_strs, pinst->u.oc2g.pedestal_mode) , VTY_NEWLINE);
+
+ vty_out(vty, " dsp-alive-period %d%s",
+ pinst->u.oc2g.dsp_alive_period, VTY_NEWLINE);
+
+ vty_out(vty, " pwr-adj-mode %s%s",
+ get_value_string(oc2g_auto_adj_pwr_strs, pinst->u.oc2g.tx_pwr_adj_mode), VTY_NEWLINE);
+
+ vty_out(vty, " tx-red-pwr-8psk %d%s",
+ pinst->u.oc2g.tx_pwr_red_8psk, VTY_NEWLINE);
+
+ vty_out(vty, " c0-idle-red-pwr %d%s",
+ pinst->u.oc2g.tx_c0_idle_pwr_red, 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, oc2gbts_tracef_names,
+ "phy <0-1> dsp-trace-flag (",
+ "|",")", VTY_DO_LOWER);
+ dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, oc2gbts_tracef_docs,
+ TRX_STR DSP_TRACE_F_STR,
+ "\n", "", 0);
+
+ no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, oc2gbts_tracef_names,
+ "no phy <0-1> dsp-trace-flag (",
+ "|",")", VTY_DO_LOWER);
+ no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, oc2gbts_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,
+ oc2gbts_tracef_names,
+ "dsp-trace-flag (",
+ "|",")", VTY_DO_LOWER);
+ cfg_phy_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts,
+ oc2gbts_tracef_docs,
+ DSP_TRACE_F_STR,
+ "\n", "", 0);
+
+ cfg_phy_no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts,
+ oc2gbts_tracef_names,
+ "no dsp-trace-flag (",
+ "|",")", VTY_DO_LOWER);
+ cfg_phy_no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts,
+ oc2gbts_tracef_docs,
+ NO_STR DSP_TRACE_F_STR,
+ "\n", "", 0);
+
+ trigger_ho_cause_cmd.string = vty_cmd_string_from_valstr(bts,
+ oc2gbts_rsl_ho_causes,
+ "trigger-ho-cause trx <0-1> ts <0-7> lchan <0-1> cause (",
+ "|",")", VTY_DO_LOWER);
+
+ 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(ENABLE_NODE, &trigger_ho_cause_cmd);
+
+ install_element(BTS_NODE, &cfg_bts_auto_band_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_auto_band_cmd);
+ /* TODO(oramadan) MERGE
+ install_element(BTS_NODE, &cfg_bts_rtp_drift_threshold_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);
+ install_element(PHY_INST_NODE, &cfg_phy_pedestal_mode_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_max_cell_size_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_dsp_alive_timer_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_auto_tx_pwr_adj_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_tx_red_pwr_8psk_cmd);
+ install_element(PHY_INST_NODE, &cfg_phy_c0_idle_red_pwr_cmd);
+
+ return 0;
+}
+
+/* TODO(oramadan) MERGE
+/* OC2G BTS control interface * /
+CTRL_CMD_DEFINE_WO_NOVRF(oc2g_oml_alert, "oc2g-oml-alert");
+static int set_oc2g_oml_alert(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ int cause = atoi(cmd->value);
+ char *saveptr = NULL;
+
+ alarm_sig_data.mo = &bts->mo;
+ cause = atoi(strtok_r(cmd->value, ",", &saveptr));
+ alarm_sig_data.event_serverity = (cause >> 8) & 0x0F;
+ alarm_sig_data.add_text = strtok_r(NULL, "\n", &saveptr);
+ memcpy(alarm_sig_data.spare, &cause, sizeof(int));
+ LOGP(DLCTRL, LOGL_NOTICE, "BTS received MGR alarm cause=%d, text=%s\n", cause, alarm_sig_data.add_text);
+
+ /* dispatch OML alarm signal * /
+ osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_MGR_ALARM, &alarm_sig_data);
+
+ /* return with same alarm cause to MGR rather than OK string* /
+ cmd->reply = talloc_asprintf(cmd, "%d", cause);
+ return CTRL_CMD_REPLY;
+}
+
+CTRL_CMD_DEFINE_WO_NOVRF(oc2g_oml_ceased, "oc2g-oml-ceased");
+static int set_oc2g_oml_ceased(struct ctrl_cmd *cmd, void *data)
+{
+ struct gsm_bts *bts = cmd->node;
+ int cause = atoi(cmd->value);
+ char *saveptr = NULL;
+
+ alarm_sig_data.mo = &bts->mo;
+ cause = atoi(strtok_r(cmd->value, ",", &saveptr));
+ alarm_sig_data.add_text = strtok_r(NULL, "\n", &saveptr);
+ memcpy(alarm_sig_data.spare, &cause, sizeof(int));
+ LOGP(DLCTRL, LOGL_NOTICE, "BTS received MGR ceased alarm cause=%d, text=%s\n", cause, alarm_sig_data.add_text);
+
+ /* dispatch OML alarm signal * /
+ osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_MGR_CEASED_ALARM, &alarm_sig_data);
+ */
+
+ /* return with same alarm cause to MGR rather than OK string* /
+ cmd->reply = talloc_asprintf(cmd, "%d", cause);
+ return CTRL_CMD_REPLY;
+}
+*/
+
+int bts_model_ctrl_cmds_install(struct gsm_bts *bts)
+{
+ int rc = 0;
+
+
+ /* TODO(oramadan) MERGE
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_oc2g_oml_alert);
+ rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_oc2g_oml_ceased);
+ */
+
+ return 0;
+}
diff --git a/src/osmo-bts-oc2g/oml.c b/src/osmo-bts-oc2g/oml.c
new file mode 100644
index 00000000..65ebc1c9
--- /dev/null
+++ b/src/osmo-bts-oc2g/oml.c
@@ -0,0 +1,2078 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * (C) 2011 by Harald Welte <laforge@gnumonks.org>
+ * (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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+
+#include <nrw/oc2g/gsml1prim.h>
+#include <nrw/oc2g/gsml1const.h>
+#include <nrw/oc2g/gsml1types.h>
+#include <nrw/oc2g/oc2g.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/rsl.h>
+#include <osmo-bts/amr.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/phy_link.h>
+#include <osmo-bts/handover.h>
+#include <osmo-bts/l1sap.h>
+
+#include "l1_if.h"
+#include "oc2gbts.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 oc2gl1_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 = 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 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(oc2gbts_l1prim_names, l1p->id),
+ get_value_string(oc2gbts_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(oc2gbts_l1prim_names, l1p->id),
+ get_value_string(oc2gbts_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)
+{
+ Oc2g_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(oc2gbts_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 oc2gl1_hdl *fl1h = trx_oc2gl1_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(oc2gbts_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(oc2gbts_l1status_names, ic->status));
+ bts_shutdown(trx->bts, "MPH-INIT failure");
+ }
+
+ fl1h->hLayer1 = 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);
+
+ /* apply initial values for Tx power backoff for 8-PSK * /
+ trx->max_power_backoff_8psk = fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk;
+ l1if_set_txpower_backoff_8psk(fl1h, fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk);
+ LOGP(DL1C, LOGL_INFO, "%s Applied initial 8-PSK Tx power backoff of %d dB\n",
+ gsm_trx_name(fl1h->phy_inst->trx),
+ fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk);
+
+ /* apply initial values for Tx C0 idle slot power reduction * /
+ trx->c0_idle_power_red = fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red;
+ l1if_set_txpower_c0_idle_pwr_red(fl1h, fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red);
+ LOGP(DL1C, LOGL_INFO, "%s Applied initial C0 idle slot power reduction of %d dB\n",
+ gsm_trx_name(fl1h->phy_inst->trx),
+ fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red); */
+
+ /* 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 oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
+ struct msgb *msg;
+ GsmL1_MphInitReq_t *mi_req;
+ GsmL1_DeviceParam_t *dev_par;
+ int oc2g_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);
+ }
+
+ /* Update TRX band */
+ trx->bts->band = gsm_arfcn2band(trx->arfcn);
+
+ oc2g_band = oc2gbts_select_oc2g_band(trx, trx->arfcn);
+ if (oc2g_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 = oc2g_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 oc2gl1_hdl *fl1h = trx_oc2gl1_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 oc2gl1_hdl *fl1h = trx_oc2gl1_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 oc2gl1_hdl *fl1h = trx_oc2gl1_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 oc2gl1_hdl *fl1h = trx_oc2gl1_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(oc2gbts_l1sapi_names, ic->sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(oc2gbts_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(oc2gbts_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(oc2gbts_l1sapi_names, ic->sapi), ic->u8Tn,
+ get_value_string(oc2gbts_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 oc2gl1_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 oc2gl1_hdl *fl1h = trx_oc2gl1_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(oc2gbts_l1sapi_names, act_req->sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(oc2gbts_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 oc2gl1_hdl *fl1h = trx_oc2gl1_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 oc2gl1_hdl *fl1h = trx_oc2gl1_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 oc2gbts_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" },
+ { GsmL1_ConfigParamId_Set8pskPowerReduction, "Set 8PSK Tx power reduction" },
+ { 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(oc2gbts_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_txpower_backoff_8psk_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(oc2gbts_l1cfgt_names, cc->cfgParamId));
+
+ LOGPC(DL1C, LOGL_INFO, "Backoff %u dB\n",
+ cc->cfgParams.set8pskPowerReduction.u8PowerReduction);
+
+ msgb_free(l1_msg);
+
+ return 0;
+}
+
+static int chmod_max_cell_size_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ Oc2g_Prim_t *sysp = msgb_sysprim(resp);
+ Oc2g_SetMaxCellSizeCnf_t *sac = &sysp->u.setMaxCellSizeCnf;
+
+ LOGP(DL1C, LOGL_INFO, "%s Rx SYS prim %s -> %s\n",
+ gsm_trx_name(trx),
+ get_value_string(oc2gbts_sysprim_names, sysp->id),
+ get_value_string(oc2gbts_l1status_names, sac->status));
+
+ msgb_free(resp);
+
+ return 0;
+}
+
+static int chmod_c0_idle_pwr_red_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ Oc2g_Prim_t *sysp = msgb_sysprim(resp);
+ Oc2g_SetC0IdleSlotPowerReductionCnf_t *sac = &sysp->u.setC0IdleSlotPowerReductionCnf;
+
+ LOGP(DL1C, LOGL_INFO, "%s Rx SYS prim %s -> %s\n",
+ gsm_trx_name(trx),
+ get_value_string(oc2gbts_sysprim_names, sysp->id),
+ get_value_string(oc2gbts_l1status_names, sac->status));
+
+ msgb_free(resp);
+
+ 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(oc2gbts_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 oc2gl1_hdl *fl1h = trx_oc2gl1_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(oc2gbts_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 oc2gl1_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);
+}
+
+int l1if_set_txpower_backoff_8psk(struct oc2gl1_hdl *fl1h, uint8_t backoff)
+{
+ 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_Set8pskPowerReduction;
+ conf_req->cfgParams.set8pskPowerReduction.u8PowerReduction = backoff;
+
+ return l1if_gsm_req_compl(fl1h, msg, chmod_txpower_backoff_8psk_compl_cb, NULL);
+}
+
+int l1if_set_max_cell_size(struct oc2gl1_hdl *fl1h, uint8_t cell_size)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ Oc2g_Prim_t *sys_prim = msgb_sysprim(msg);
+ sys_prim->id = Oc2g_PrimId_SetMaxCellSizeReq;
+ sys_prim->u.setMaxCellSizeReq.u8MaxCellSize = cell_size;
+
+ LOGP(DL1C, LOGL_INFO, "%s Set max cell size = %d qbits\n",
+ gsm_trx_name(fl1h->phy_inst->trx),
+ cell_size);
+
+ return l1if_req_compl(fl1h, msg, chmod_max_cell_size_compl_cb, NULL);
+
+}
+
+int l1if_set_txpower_c0_idle_pwr_red(struct oc2gl1_hdl *fl1h, uint8_t red)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ Oc2g_Prim_t *sys_prim = msgb_sysprim(msg);
+ sys_prim->id = Oc2g_PrimId_SetC0IdleSlotPowerReductionReq;
+ sys_prim->u.setC0IdleSlotPowerReductionReq.u8PowerReduction = red;
+
+ LOGP(DL1C, LOGL_INFO, "%s Set C0 idle slot power reduction = %d dB\n",
+ gsm_trx_name(fl1h->phy_inst->trx),
+ red);
+
+ return l1if_req_compl(fl1h, msg, chmod_c0_idle_pwr_red_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 oc2gl1_hdl *fl1h = trx_oc2gl1_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(oc2gbts_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 oc2gl1_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(oc2gbts_l1sapi_names, ic->sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(oc2gbts_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(oc2gbts_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(oc2gbts_l1sapi_names, ic->sapi), ic->u8Tn,
+ get_value_string(oc2gbts_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 oc2gl1_hdl *fl1h = trx_oc2gl1_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(oc2gbts_l1sapi_names, deact_req->sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(oc2gbts_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 oc2gl1_hdl *fl1h = trx_oc2gl1_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_PRESENT(new_attr, NM_ATT_TSC) &&
+ TLVP_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 oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
+ /* convert max TA to max cell size in qbits */
+ uint8_t cell_size = bts->max_ta << 2;
+
+ /* We do not need to check for L1 handle
+ * because the max cell size parameter can receive before MphInit */
+ if (fl1h->phy_inst->u.oc2g.max_cell_size != cell_size) {
+ /* instruct L1 to apply max cell size */
+ l1if_set_max_cell_size(fl1h, cell_size);
+ /* update current max cell size */
+ fl1h->phy_inst->u.oc2g.max_cell_size = cell_size;
+ }
+
+ /* 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);
+
+ if (fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk != trx->max_power_backoff_8psk) {
+ /* update current Tx power backoff for 8-PSK */
+ fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk = trx->max_power_backoff_8psk;
+ /* instruct L1 to apply Tx power backoff for 8 PSK */
+ l1if_set_txpower_backoff_8psk(fl1h, fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk);
+ }
+
+ if (fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red != trx->c0_idle_power_red) {
+ /* update current C0 idle slot Tx power reduction */
+ fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red = trx->c0_idle_power_red;
+ /* instruct L1 to apply C0 idle slot power reduction */
+ l1if_set_txpower_c0_idle_pwr_red(fl1h, fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red);
+ }
+ }
+
+ }
+
+ /* 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 oc2gl1_hdl *fl1 = trx_oc2gl1_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_oc2gl1_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);
+
+ msgb_free(l1_msg);
+
+ return 0;
+}
+
+int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts)
+{
+ struct msgb *msg = l1p_msgb_alloc();
+ struct oc2gl1_hdl *fl1h = trx_oc2gl1_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);
+
+ msgb_free(l1_msg);
+
+ 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-oc2g/oml_router.c b/src/osmo-bts-oc2g/oml_router.c
new file mode 100644
index 00000000..198d5e30
--- /dev/null
+++ b/src/osmo-bts-oc2g/oml_router.c
@@ -0,0 +1,132 @@
+/* Beginnings of an OML router */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "oml_router.h"
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/oml.h>
+#include <osmo-bts/msg_utils.h>
+
+#include <osmocom/core/socket.h>
+#include <osmocom/core/select.h>
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+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-oc2g/oml_router.h b/src/osmo-bts-oc2g/oml_router.h
new file mode 100644
index 00000000..4b22e9c5
--- /dev/null
+++ b/src/osmo-bts-oc2g/oml_router.h
@@ -0,0 +1,13 @@
+#pragma once
+
+struct gsm_bts;
+struct osmo_fd;
+
+/**
+ * The default path oc2gbts will listen for incoming
+ * registrations for OML routing and sending.
+ */
+#define OML_ROUTER_PATH "/var/run/oc2gbts_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-oc2g/tch.c b/src/osmo-bts-oc2g/tch.c
new file mode 100644
index 00000000..1bd93e4b
--- /dev/null
+++ b/src/osmo-bts-oc2g/tch.c
@@ -0,0 +1,545 @@
+/* Traffic channel support for NuRAN Wireless OC-2G BTS L1 */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * (C) 2011-2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/gsm/gsm_utils.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/msg_utils.h>
+#include <osmo-bts/measurement.h>
+#include <osmo-bts/amr.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/dtx_dl_amr_fsm.h>
+
+#include <nrw/oc2g/oc2g.h>
+#include <nrw/oc2g/gsml1prim.h>
+#include <nrw/oc2g/gsml1const.h>
+#include <nrw/oc2g/gsml1types.h>
+
+#include "oc2gbts.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;
+
+ /* clear RTP marker if the marker has previously sent */
+ if (!lchan->tch.dtx.is_speech_resume)
+ lchan->rtp_tx_marker = false;
+
+ 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->tch.dtx.is_speech_resume = true;
+ 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->tch.dtx.is_speech_resume = true;
+ 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->tch.dtx.is_speech_resume = true;
+ 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(oc2gbts_tch_pl_names, payload_type));
+ break;
+ }
+
+ LOGP(DL1P, LOGL_DEBUG, "%s %s lchan->rtp_tx_marker = %s, len=%u\n",
+ gsm_lchan_name(lchan),
+ get_value_string(oc2gbts_tch_pl_names, payload_type),
+ lchan->rtp_tx_marker ? "true" : "false",
+ payload_len);
+
+ 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(oc2gbts_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-oc2g/utils.c b/src/osmo-bts-oc2g/utils.c
new file mode 100644
index 00000000..28944390
--- /dev/null
+++ b/src/osmo-bts-oc2g/utils.c
@@ -0,0 +1,114 @@
+/* Helper utilities that are used in OMLs */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * (C) 2011-2013 by Harald Welte <laforge@gnumonks.org>
+ * (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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "utils.h"
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+
+#include "oc2gbts.h"
+#include "l1_if.h"
+
+int band_oc2g2osmo(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_osmo2oc2g(struct gsm_bts_trx *trx, enum gsm_band osmo_band)
+{
+ struct oc2gl1_hdl *fl1h = trx_oc2gl1_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 oc2gbts_select_oc2g_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_osmo2oc2g(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_osmo2oc2g(trx, bts->band);
+
+ /* Check if it is GSM1800/GSM1900 */
+ if (band == GSM_BAND_1800 && bts->band == GSM_BAND_1900)
+ return band_osmo2oc2g(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_osmo2oc2g(trx, band);
+ if (band == GSM_BAND_1800 && bts->band == GSM_BAND_850)
+ return band_osmo2oc2g(trx, GSM_BAND_1900);
+
+ /* give up */
+ return -1;
+}
diff --git a/src/osmo-bts-oc2g/utils.h b/src/osmo-bts-oc2g/utils.h
new file mode 100644
index 00000000..f2f13e71
--- /dev/null
+++ b/src/osmo-bts-oc2g/utils.h
@@ -0,0 +1,13 @@
+#ifndef _UTILS_H
+#define _UTILS_H
+
+#include <stdint.h>
+#include "oc2gbts.h"
+
+struct gsm_bts_trx;
+
+int band_oc2g2osmo(GsmL1_FreqBand_t band);
+
+int oc2gbts_select_oc2g_band(struct gsm_bts_trx *trx, uint16_t arfcn);
+
+#endif