aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--configure.ac9
-rw-r--r--src/Makefile.am4
-rw-r--r--src/osmo-bts-litecell15/Makefile.am33
-rw-r--r--src/osmo-bts-litecell15/calib_file.c256
-rw-r--r--src/osmo-bts-litecell15/hw_misc.c86
-rw-r--r--src/osmo-bts-litecell15/hw_misc.h13
-rw-r--r--src/osmo-bts-litecell15/l1_if.c1429
-rw-r--r--src/osmo-bts-litecell15/l1_if.h121
-rw-r--r--src/osmo-bts-litecell15/l1_transp.h14
-rw-r--r--src/osmo-bts-litecell15/l1_transp_hw.c320
-rw-r--r--src/osmo-bts-litecell15/lc15bts.c332
-rw-r--r--src/osmo-bts-litecell15/lc15bts.h64
-rw-r--r--src/osmo-bts-litecell15/lc15bts_vty.c400
-rw-r--r--src/osmo-bts-litecell15/main.c431
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_bid.c139
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_bid.h51
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_clock.c283
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_clock.h16
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_mgr.c297
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_mgr.h111
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_mgr_calib.c246
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_mgr_nl.c210
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_mgr_temp.c353
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_mgr_vty.c602
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_misc.c249
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_misc.h16
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_nl.c123
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_nl.h27
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_par.c181
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_par.h33
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_power.c167
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_power.h29
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_temp.c117
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_temp.h27
-rw-r--r--src/osmo-bts-litecell15/misc/lc15bts_util.c158
-rw-r--r--src/osmo-bts-litecell15/oml.c1765
-rw-r--r--src/osmo-bts-litecell15/oml_router.c132
-rw-r--r--src/osmo-bts-litecell15/oml_router.h13
-rw-r--r--src/osmo-bts-litecell15/tch.c534
-rw-r--r--src/osmo-bts-litecell15/utils.c127
-rw-r--r--src/osmo-bts-litecell15/utils.h17
41 files changed, 9535 insertions, 0 deletions
diff --git a/configure.ac b/configure.ac
index 8239c122..d16a5ee7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -68,6 +68,14 @@ if test "$enable_octphy" = "yes" ; then
CPPFLAGS=$oldCPPFLAGS
fi
+AC_MSG_CHECKING([whether to enable NuRAN Wireless Litecell 1.5 hardware support])
+AC_ARG_ENABLE(litecell15-bts,
+ AC_HELP_STRING([--enable-litecell15-bts],
+ [enable code for NuRAN Wireless Litecell15 bts [default=no]]),
+ [enable_litecell15_bts="yes"],[enable_litecell15_bts="no"])
+AC_MSG_RESULT([$enable_litecell15_bts])
+AM_CONDITIONAL(ENABLE_LC15BTS, test "x$enable_litecell15_bts" = "xyes")
+
# We share gsm_data.h with OpenBSC and need to be pointed to the source
# directory of OpenBSC for now.
AC_ARG_WITH([openbsc],
@@ -101,6 +109,7 @@ AC_OUTPUT(
src/Makefile
src/common/Makefile
src/osmo-bts-sysmo/Makefile
+ src/osmo-bts-litecell15/Makefile
src/osmo-bts-trx/Makefile
src/osmo-bts-octphy/Makefile
include/Makefile
diff --git a/src/Makefile.am b/src/Makefile.am
index a29a9230..e7610fe7 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -11,3 +11,7 @@ endif
if ENABLE_OCTPHY
SUBDIRS += osmo-bts-octphy
endif
+
+if ENABLE_LC15BTS
+SUBDIRS += osmo-bts-litecell15
+endif
diff --git a/src/osmo-bts-litecell15/Makefile.am b/src/osmo-bts-litecell15/Makefile.am
new file mode 100644
index 00000000..1d244eb1
--- /dev/null
+++ b/src/osmo-bts-litecell15/Makefile.am
@@ -0,0 +1,33 @@
+AUTOMAKE_OPTIONS = subdir-objects
+
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(OPENBSC_INCDIR)
+AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS)
+COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) -lortp
+
+EXTRA_DIST = misc/lc15bts_mgr.h misc/lc15bts_misc.h misc/lc15bts_par.h \
+ misc/lc15bts_temp.h misc/lc15bts_power.h misc/lc15bts_clock.h \
+ misc/lc15bts_bid.h misc/lc15bts_nl.h femtobts.h hw_misc.h \
+ l1_if.h l1_transp.h utils.h oml_router.h
+
+bin_PROGRAMS = lc15bts lc15bts-mgr lc15bts-util
+
+COMMON_SOURCES = main.c lc15bts.c l1_if.c oml.c lc15bts_vty.c tch.c hw_misc.c calib_file.c \
+ utils.c misc/lc15bts_par.c misc/lc15bts_bid.c oml_router.c
+
+lc15bts_SOURCES = $(COMMON_SOURCES) l1_transp_hw.c
+lc15bts_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD)
+
+lc15bts_mgr_SOURCES = \
+ misc/lc15bts_mgr.c misc/lc15bts_misc.c \
+ misc/lc15bts_par.c misc/lc15bts_nl.c \
+ misc/lc15bts_temp.c misc/lc15bts_power.c \
+ misc/lc15bts_clock.c misc/lc15bts_bid.c \
+ misc/lc15bts_mgr_vty.c \
+ misc/lc15bts_mgr_nl.c \
+ misc/lc15bts_mgr_temp.c \
+ misc/lc15bts_mgr_calib.c
+
+lc15bts_mgr_LDADD = $(LIBGPS_LIBS) $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCTRL_LIBS) $(top_builddir)/src/common/libbts.a
+
+lc15bts_util_SOURCES = misc/lc15bts_util.c misc/lc15bts_par.c
+lc15bts_util_LDADD = $(LIBOSMOCORE_LIBS)
diff --git a/src/osmo-bts-litecell15/calib_file.c b/src/osmo-bts-litecell15/calib_file.c
new file mode 100644
index 00000000..c6e2bc85
--- /dev/null
+++ b/src/osmo-bts-litecell15/calib_file.c
@@ -0,0 +1,256 @@
+/* NuRAN Wireless Litecell 1.5 BTS L1 calibration file routines*/
+
+/* 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 <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/litecell15/litecell15.h>
+#include <nrw/litecell15/gsml1const.h>
+
+#include "l1_if.h"
+#include "lc15bts.h"
+#include "utils.h"
+
+
+
+struct calib_file_desc {
+ const char *fname;
+ int rx;
+ int trx;
+ int rxpath;
+};
+
+static const struct calib_file_desc calib_files[] = {
+ {
+ .fname = "calib_rx1a.conf",
+ .rx = 1,
+ .trx = 1,
+ .rxpath = 0,
+ }, {
+ .fname = "calib_rx1b.conf",
+ .rx = 1,
+ .trx = 1,
+ .rxpath = 1,
+ }, {
+ .fname = "calib_rx2a.conf",
+ .rx = 1,
+ .trx = 2,
+ .rxpath = 0,
+ }, {
+ .fname = "calib_rx2b.conf",
+ .rx = 1,
+ .trx = 2,
+ .rxpath = 1,
+ }, {
+ .fname = "calib_tx1.conf",
+ .rx = 0,
+ .trx = 1,
+ }, {
+ .fname = "calib_tx2.conf",
+ .rx = 0,
+ .trx = 2,
+ },
+};
+
+
+static int calib_file_send(struct lc15l1_hdl *fl1h,
+ const struct calib_file_desc *desc);
+
+/* determine next calibration file index based on supported bands */
+static int get_next_calib_file_idx(struct lc15l1_hdl *fl1h, int last_idx)
+{
+ int i;
+
+ for (i = last_idx+1; i < ARRAY_SIZE(calib_files); i++) {
+ if (calib_files[i].trx == fl1h->hw_info.trx_nr)
+ return i;
+ }
+ return -1;
+}
+
+static int calib_file_open(struct lc15l1_hdl *fl1h,
+ const struct calib_file_desc *desc)
+{
+ struct calib_send_state *st = &fl1h->st;
+ char 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", fl1h->calib_path, desc->fname);
+ fname[sizeof(fname)-1] = '\0';
+
+ st->fp = fopen(fname, "rb");
+ if (!st->fp) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Failed to open '%s' for calibration data.\n", fname);
+ return -1;
+ }
+ return 0;
+}
+
+static int calib_file_close(struct lc15l1_hdl *fl1h)
+{
+ struct calib_send_state *st = &fl1h->st;
+
+ if (st->fp) {
+ fclose(st->fp);
+ st->fp = NULL;
+ }
+ return 0;
+}
+
+/* iteratively download the calibration data into the L1 */
+
+static int calib_send_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data);
+
+/* send a chunk of calibration tabledata for a single specified file */
+static int calib_file_send_next_chunk(struct lc15l1_hdl *fl1h)
+{
+ struct calib_send_state *st = &fl1h->st;
+ Litecell15_Prim_t *prim;
+ struct msgb *msg;
+ size_t n;
+
+ msg = sysp_msgb_alloc();
+ prim = msgb_sysprim(msg);
+
+ prim->id = Litecell15_PrimId_SetCalibTblReq;
+ prim->u.setCalibTblReq.offset = (uint32_t)ftell(st->fp);
+ n = fread(prim->u.setCalibTblReq.u8Data, 1,
+ sizeof(prim->u.setCalibTblReq.u8Data), st->fp);
+ prim->u.setCalibTblReq.length = n;
+
+
+ if (n == 0) {
+ /* The table data has been completely sent and acknowledged */
+ LOGP(DL1C, LOGL_NOTICE, "L1 calibration table %s loaded\n",
+ calib_files[st->last_file_idx].fname);
+
+ calib_file_close(fl1h);
+
+ msgb_free(msg);
+
+ /* Send the next one if any */
+ st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx);
+ if (st->last_file_idx >= 0) {
+ return calib_file_send(fl1h,
+ &calib_files[st->last_file_idx]);
+ }
+
+ LOGP(DL1C, LOGL_INFO, "L1 calibration table loading complete!\n");
+ return 0;
+ }
+
+ return l1if_req_compl(fl1h, msg, calib_send_compl_cb, NULL);
+}
+
+/* send the calibration table for a single specified file */
+static int calib_file_send(struct lc15l1_hdl *fl1h,
+ const struct calib_file_desc *desc)
+{
+ struct calib_send_state *st = &fl1h->st;
+ int rc;
+
+ rc = calib_file_open(fl1h, desc);
+ if (rc < 0) {
+ /* still, we'd like to continue trying to load
+ * calibration for all other bands */
+ st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx);
+ if (st->last_file_idx >= 0)
+ return calib_file_send(fl1h,
+ &calib_files[st->last_file_idx]);
+
+ LOGP(DL1C, LOGL_INFO, "L1 calibration table loading complete!\n");
+ return 0;
+ }
+
+ return calib_file_send_next_chunk(fl1h);
+}
+
+/* completion callback after every SetCalibTbl is confirmed */
+static int calib_send_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+ struct calib_send_state *st = &fl1h->st;
+ Litecell15_Prim_t *prim = msgb_sysprim(l1_msg);
+
+ if (prim->u.setCalibTblCnf.status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_ERROR, "L1 rejected calibration table\n");
+
+ msgb_free(l1_msg);
+
+ calib_file_close(fl1h);
+
+ /* Skip this one and try the next one */
+ st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx);
+ if (st->last_file_idx >= 0) {
+ return calib_file_send(fl1h,
+ &calib_files[st->last_file_idx]);
+ }
+
+ LOGP(DL1C, LOGL_INFO, "L1 calibration table loading complete!\n");
+ return 0;
+ }
+
+ msgb_free(l1_msg);
+
+ /* Keep sending the calibration file data */
+ return calib_file_send_next_chunk(fl1h);
+}
+
+int calib_load(struct lc15l1_hdl *fl1h)
+{
+ int rc;
+ struct calib_send_state *st = &fl1h->st;
+
+ if (!fl1h->calib_path) {
+ LOGP(DL1C, LOGL_ERROR, "Calibration file path not specified\n");
+ return -1;
+ }
+
+ rc = get_next_calib_file_idx(fl1h, -1);
+ if (rc < 0) {
+ return -1;
+ }
+ st->last_file_idx = rc;
+
+ return calib_file_send(fl1h, &calib_files[st->last_file_idx]);
+}
+
diff --git a/src/osmo-bts-litecell15/hw_misc.c b/src/osmo-bts-litecell15/hw_misc.c
new file mode 100644
index 00000000..007c90ec
--- /dev/null
+++ b/src/osmo-bts-litecell15/hw_misc.c
@@ -0,0 +1,86 @@
+/* Misc HW routines for NuRAN Wireless Litecell 1.5 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 lc15bts_led_set(enum lc15bts_led_color c)
+{
+ int fd, rc;
+ uint8_t cmd[2];
+
+ switch (c) {
+ case LED_OFF:
+ cmd[0] = 0;
+ cmd[1] = 0;
+ break;
+ case LED_RED:
+ cmd[0] = 1;
+ cmd[1] = 0;
+ break;
+ case LED_GREEN:
+ cmd[0] = 0;
+ cmd[1] = 1;
+ break;
+ case LED_ORANGE:
+ cmd[0] = 1;
+ cmd[1] = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ fd = open("/sys/class/leds/usr0/brightness", O_WRONLY);
+ if (fd < 0)
+ return -ENODEV;
+
+ rc = write(fd, cmd[0] ? "1" : "0", 2);
+ if (rc != 2) {
+ return -1;
+ }
+ close(fd);
+
+ fd = open("/sys/class/leds/usr1/brightness", O_WRONLY);
+ if (fd < 0)
+ return -ENODEV;
+
+ rc = write(fd, cmd[1] ? "1" : "0", 2);
+ if (rc != 2) {
+ return -1;
+ }
+ close(fd);
+ return 0;
+}
diff --git a/src/osmo-bts-litecell15/hw_misc.h b/src/osmo-bts-litecell15/hw_misc.h
new file mode 100644
index 00000000..59ed04b7
--- /dev/null
+++ b/src/osmo-bts-litecell15/hw_misc.h
@@ -0,0 +1,13 @@
+#ifndef _HW_MISC_H
+#define _HW_MISC_H
+
+enum lc15bts_led_color {
+ LED_OFF,
+ LED_RED,
+ LED_GREEN,
+ LED_ORANGE,
+};
+
+int lc15bts_led_set(enum lc15bts_led_color c);
+
+#endif
diff --git a/src/osmo-bts-litecell15/l1_if.c b/src/osmo-bts-litecell15/l1_if.c
new file mode 100644
index 00000000..12092a33
--- /dev/null
+++ b/src/osmo-bts-litecell15/l1_if.c
@@ -0,0 +1,1429 @@
+/* Interface handler for NuRAN Wireless Litecell 1.5 L1 */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * 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/paging.h>
+#include <osmo-bts/measurement.h>
+#include <osmo-bts/pcu_if.h>
+#include <osmo-bts/handover.h>
+#include <osmo-bts/cbch.h>
+#include <osmo-bts/bts_model.h>
+#include <osmo-bts/l1sap.h>
+
+#include <nrw/litecell15/litecell15.h>
+#include <nrw/litecell15/gsml1prim.h>
+#include <nrw/litecell15/gsml1const.h>
+#include <nrw/litecell15/gsml1types.h>
+
+#include "lc15bts.h"
+#include "l1_if.h"
+#include "l1_transp.h"
+#include "hw_misc.h"
+#include "misc/lc15bts_par.h"
+#include "misc/lc15bts_bid.h"
+#include "utils.h"
+
+extern int pcu_direct;
+
+#define MIN_QUAL_RACH 5.0f /* at least 5 dB C/I */
+#define MIN_QUAL_NORM -0.5f /* at least -1 dB C/I */
+
+
+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 */
+ unsigned int is_sys_prim; /* is this a system (1) or L1 (0) primitive */
+ l1if_compl_cb *cb;
+ void *cb_data;
+};
+
+static void release_wlc(struct wait_l1_conf *wlc)
+{
+ osmo_timer_del(&wlc->timer);
+ talloc_free(wlc);
+}
+
+static void l1if_req_timeout(void *data)
+{
+ struct wait_l1_conf *wlc = data;
+
+ if (wlc->is_sys_prim)
+ LOGP(DL1C, LOGL_FATAL, "Timeout waiting for SYS primitive %s\n",
+ get_value_string(lc15bts_sysprim_names, wlc->conf_prim_id));
+ else
+ LOGP(DL1C, LOGL_FATAL, "Timeout waiting for L1 primitive %s\n",
+ get_value_string(lc15bts_l1prim_names, wlc->conf_prim_id));
+ exit(23);
+}
+
+static int _l1if_req_compl(struct lc15l1_hdl *fl1h, struct msgb *msg,
+ int is_system_prim, l1if_compl_cb *cb, void *data)
+{
+ struct wait_l1_conf *wlc;
+ struct osmo_wqueue *wqueue;
+ unsigned int timeout_secs;
+
+ /* allocate new wsc and store reference to mutex and conf_id */
+ wlc = talloc_zero(fl1h, struct wait_l1_conf);
+ wlc->cb = cb;
+ wlc->cb_data = data;
+
+ /* Make sure we actually have received a REQUEST type primitive */
+ if (is_system_prim == 0) {
+ GsmL1_Prim_t *l1p = msgb_l1prim(msg);
+
+ LOGP(DL1P, LOGL_INFO, "Tx L1 prim %s\n",
+ get_value_string(lc15bts_l1prim_names, l1p->id));
+
+ if (lc15bts_get_l1prim_type(l1p->id) != L1P_T_REQ) {
+ LOGP(DL1C, LOGL_ERROR, "L1 Prim %s is not a Request!\n",
+ get_value_string(lc15bts_l1prim_names, l1p->id));
+ talloc_free(wlc);
+ return -EINVAL;
+ }
+ wlc->is_sys_prim = 0;
+ wlc->conf_prim_id = lc15bts_get_l1prim_conf(l1p->id);
+ wqueue = &fl1h->write_q[MQ_L1_WRITE];
+ timeout_secs = 30;
+ } else {
+ Litecell15_Prim_t *sysp = msgb_sysprim(msg);
+
+ LOGP(DL1C, LOGL_INFO, "Tx SYS prim %s\n",
+ get_value_string(lc15bts_sysprim_names, sysp->id));
+
+ if (lc15bts_get_sysprim_type(sysp->id) != L1P_T_REQ) {
+ LOGP(DL1C, LOGL_ERROR, "SYS Prim %s is not a Request!\n",
+ get_value_string(lc15bts_sysprim_names, sysp->id));
+ talloc_free(wlc);
+ return -EINVAL;
+ }
+ wlc->is_sys_prim = 1;
+ wlc->conf_prim_id = lc15bts_get_sysprim_conf(sysp->id);
+ wqueue = &fl1h->write_q[MQ_SYS_WRITE];
+ timeout_secs = 30;
+ }
+
+ /* enqueue the message in the queue and add wsc to list */
+ if (osmo_wqueue_enqueue(wqueue, msg) != 0) {
+ /* So we will get a timeout but the log message might help */
+ LOGP(DL1C, LOGL_ERROR, "Write queue for %s full. dropping msg.\n",
+ is_system_prim ? "system primitive" : "gsm");
+ msgb_free(msg);
+ }
+ llist_add(&wlc->list, &fl1h->wlc_list);
+
+ /* schedule a timer for timeout_secs seconds. If DSP fails to respond, we terminate */
+ wlc->timer.data = wlc;
+ wlc->timer.cb = l1if_req_timeout;
+ osmo_timer_schedule(&wlc->timer, timeout_secs, 0);
+
+ return 0;
+}
+
+/* send a request primitive to the L1 and schedule completion call-back */
+int l1if_req_compl(struct lc15l1_hdl *fl1h, struct msgb *msg,
+ l1if_compl_cb *cb, void *data)
+{
+ return _l1if_req_compl(fl1h, msg, 1, cb, data);
+}
+
+int l1if_gsm_req_compl(struct lc15l1_hdl *fl1h, struct msgb *msg,
+ l1if_compl_cb *cb, void *data)
+{
+ return _l1if_req_compl(fl1h, msg, 0, cb, data);
+}
+
+/* allocate a msgb containing a GsmL1_Prim_t */
+struct msgb *l1p_msgb_alloc(void)
+{
+ struct msgb *msg = msgb_alloc(sizeof(GsmL1_Prim_t), "l1_prim");
+
+ if (msg)
+ msg->l1h = msgb_put(msg, sizeof(GsmL1_Prim_t));
+
+ return msg;
+}
+
+/* allocate a msgb containing a Litecell15_Prim_t */
+struct msgb *sysp_msgb_alloc(void)
+{
+ struct msgb *msg = msgb_alloc(sizeof(Litecell15_Prim_t), "sys_prim");
+
+ if (msg)
+ msg->l1h = msgb_put(msg, sizeof(Litecell15_Prim_t));
+
+ return msg;
+}
+
+static GsmL1_PhDataReq_t *
+data_req_from_rts_ind(GsmL1_Prim_t *l1p,
+ const GsmL1_PhReadyToSendInd_t *rts_ind)
+{
+ GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq;
+
+ l1p->id = GsmL1_PrimId_PhDataReq;
+
+ /* copy fields from PH-RSS.ind */
+ data_req->hLayer1 = rts_ind->hLayer1;
+ data_req->u8Tn = rts_ind->u8Tn;
+ data_req->u32Fn = rts_ind->u32Fn;
+ data_req->sapi = rts_ind->sapi;
+ data_req->subCh = rts_ind->subCh;
+ data_req->u8BlockNbr = rts_ind->u8BlockNbr;
+
+ return data_req;
+}
+
+static GsmL1_PhEmptyFrameReq_t *
+empty_req_from_rts_ind(GsmL1_Prim_t *l1p,
+ const GsmL1_PhReadyToSendInd_t *rts_ind)
+{
+ GsmL1_PhEmptyFrameReq_t *empty_req = &l1p->u.phEmptyFrameReq;
+
+ l1p->id = GsmL1_PrimId_PhEmptyFrameReq;
+
+ empty_req->hLayer1 = rts_ind->hLayer1;
+ empty_req->u8Tn = rts_ind->u8Tn;
+ empty_req->u32Fn = rts_ind->u32Fn;
+ empty_req->sapi = rts_ind->sapi;
+ empty_req->subCh = rts_ind->subCh;
+ empty_req->u8BlockNbr = rts_ind->u8BlockNbr;
+
+ return empty_req;
+}
+
+/* check if the message is a GSM48_MT_RR_CIPH_M_CMD, and if yes, enable
+ * uni-directional de-cryption on the uplink. We need this ugly layering
+ * violation as we have no way of passing down L3 metadata (RSL CIPHERING CMD)
+ * to this point in L1 */
+static int check_for_ciph_cmd(struct lc15l1_hdl *fl1h,
+ struct msgb *msg, struct gsm_lchan *lchan)
+{
+ uint8_t n_s;
+
+ /* only do this if we are in the right state */
+ switch (lchan->ciph_state) {
+ case LCHAN_CIPH_NONE:
+ case LCHAN_CIPH_RX_REQ:
+ break;
+ default:
+ return 0;
+ }
+
+ /* First byte (Address Field) of LAPDm header) */
+ if (msg->data[0] != 0x03)
+ return 0;
+ /* First byte (protocol discriminator) of RR */
+ if ((msg->data[3] & 0xF) != GSM48_PDISC_RR)
+ return 0;
+ /* 2nd byte (msg type) of RR */
+ if ((msg->data[4] & 0x3F) != GSM48_MT_RR_CIPH_M_CMD)
+ return 0;
+
+ /* Remember N(S) + 1 to find the first ciphered frame */
+ n_s = (msg->data[1] >> 1) & 0x7;
+ lchan->ciph_ns = (n_s + 1) % 8;
+
+ lchan->ciph_state = LCHAN_CIPH_RX_REQ;
+ l1if_set_ciphering(fl1h, lchan, 0);
+
+ return 1;
+}
+
+/* public helpers for the test */
+int bts_check_for_ciph_cmd(struct lc15l1_hdl *fl1h,
+ struct msgb *msg, struct gsm_lchan *lchan)
+{
+ return check_for_ciph_cmd(fl1h, msg, lchan);
+}
+
+static const uint8_t fill_frame[GSM_MACBLOCK_LEN] = {
+ 0x03, 0x03, 0x01, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B,
+ 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B, 0x2B,
+ 0x2B, 0x2B, 0x2B
+};
+
+/* fill PH-DATA.req from l1sap primitive */
+static GsmL1_PhDataReq_t *
+data_req_from_l1sap(GsmL1_Prim_t *l1p, struct lc15l1_hdl *fl1,
+ uint8_t tn, uint32_t fn, uint8_t sapi, uint8_t sub_ch,
+ uint8_t block_nr, uint8_t len)
+{
+ GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq;
+
+ l1p->id = GsmL1_PrimId_PhDataReq;
+
+ /* copy fields from PH-RSS.ind */
+ data_req->hLayer1 = (HANDLE)fl1->hLayer1;
+ data_req->u8Tn = tn;
+ data_req->u32Fn = fn;
+ data_req->sapi = sapi;
+ data_req->subCh = sub_ch;
+ data_req->u8BlockNbr = block_nr;
+
+ data_req->msgUnitParam.u8Size = len;
+
+ return data_req;
+}
+
+/* fill PH-EMPTY_FRAME.req from l1sap primitive */
+static GsmL1_PhEmptyFrameReq_t *
+empty_req_from_l1sap(GsmL1_Prim_t *l1p, struct lc15l1_hdl *fl1,
+ uint8_t tn, uint32_t fn, uint8_t sapi,
+ uint8_t subch, uint8_t block_nr)
+{
+ GsmL1_PhEmptyFrameReq_t *empty_req = &l1p->u.phEmptyFrameReq;
+
+ l1p->id = GsmL1_PrimId_PhEmptyFrameReq;
+
+ empty_req->hLayer1 = (HANDLE)fl1->hLayer1;
+ empty_req->u8Tn = tn;
+ empty_req->u32Fn = fn;
+ empty_req->sapi = sapi;
+ empty_req->subCh = subch;
+ empty_req->u8BlockNbr = block_nr;
+
+ return empty_req;
+}
+
+static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg,
+ struct osmo_phsap_prim *l1sap)
+{
+ struct lc15l1_hdl *fl1 = trx_lc15l1_hdl(trx);
+ struct msgb *l1msg = l1p_msgb_alloc();
+ uint32_t u32Fn;
+ uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi = 0;
+ uint8_t chan_nr, link_id;
+ int len;
+
+ if (!msg) {
+ LOGP(DL1C, LOGL_FATAL, "PH-DATA.req without msg. "
+ "Please fix!\n");
+ abort();
+ }
+
+ len = msgb_l2len(msg);
+
+ chan_nr = l1sap->u.data.chan_nr;
+ link_id = l1sap->u.data.link_id;
+ u32Fn = l1sap->u.data.fn;
+ u8Tn = L1SAP_CHAN2TS(chan_nr);
+ subCh = 0x1f;
+ if (L1SAP_IS_LINK_SACCH(link_id)) {
+ sapi = GsmL1_Sapi_Sacch;
+ if (!L1SAP_IS_CHAN_TCHF(chan_nr))
+ subCh = l1sap_chan2ss(chan_nr);
+ } else if (L1SAP_IS_CHAN_TCHF(chan_nr)) {
+ if (trx->ts[u8Tn].pchan == GSM_PCHAN_PDCH) {
+ if (L1SAP_IS_PTCCH(u32Fn)) {
+ sapi = GsmL1_Sapi_Ptcch;
+ u8BlockNbr = L1SAP_FN2PTCCHBLOCK(u32Fn);
+ } else {
+ sapi = GsmL1_Sapi_Pdtch;
+ u8BlockNbr = L1SAP_FN2MACBLOCK(u32Fn);
+ }
+ } else {
+ sapi = GsmL1_Sapi_FacchF;
+ u8BlockNbr = (u32Fn % 13) >> 2;
+ }
+ } else if (L1SAP_IS_CHAN_TCHH(chan_nr)) {
+ subCh = L1SAP_CHAN2SS_TCHH(chan_nr);
+ sapi = GsmL1_Sapi_FacchH;
+ u8BlockNbr = (u32Fn % 26) >> 3;
+ } else if (L1SAP_IS_CHAN_SDCCH4(chan_nr)) {
+ subCh = L1SAP_CHAN2SS_SDCCH4(chan_nr);
+ sapi = GsmL1_Sapi_Sdcch;
+ } else if (L1SAP_IS_CHAN_SDCCH8(chan_nr)) {
+ subCh = L1SAP_CHAN2SS_SDCCH8(chan_nr);
+ sapi = GsmL1_Sapi_Sdcch;
+ } else if (L1SAP_IS_CHAN_BCCH(chan_nr)) {
+ sapi = GsmL1_Sapi_Bcch;
+ } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) {
+ /* The sapi depends on DSP configuration, not
+ * on the actual SYSTEM INFORMATION 3. */
+ u8BlockNbr = L1SAP_FN2CCCHBLOCK(u32Fn);
+ if (u8BlockNbr >= 1)
+ sapi = GsmL1_Sapi_Pch;
+ else
+ sapi = GsmL1_Sapi_Agch;
+ } else {
+ LOGP(DL1C, LOGL_NOTICE, "unknown prim %d op %d "
+ "chan_nr %d link_id %d\n", l1sap->oph.primitive,
+ l1sap->oph.operation, chan_nr, link_id);
+ 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);
+
+ OSMO_ASSERT(msgb_l2len(msg) <= sizeof(l1p->u.phDataReq.msgUnitParam.u8Buffer));
+ memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer, msg->l2h, msgb_l2len(msg));
+ LOGP(DL1P, LOGL_DEBUG, "PH-DATA.req(%s)\n",
+ osmo_hexdump(l1p->u.phDataReq.msgUnitParam.u8Buffer,
+ l1p->u.phDataReq.msgUnitParam.u8Size));
+ } else {
+ /* empty frame */
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1msg);
+
+ empty_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr);
+ }
+
+ /* free the msgb holding the L1SAP primitive */
+ msgb_free(msg);
+
+ /* send message to DSP's queue */
+ if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], l1msg) != 0) {
+ LOGP(DL1P, LOGL_ERROR, "MQ_L1_WRITE queue full. Dropping msg.\n");
+ msgb_free(msg);
+ }
+
+ return 0;
+}
+
+static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg,
+ struct osmo_phsap_prim *l1sap)
+{
+ struct lc15l1_hdl *fl1 = trx_lc15l1_hdl(trx);
+ struct gsm_lchan *lchan;
+ uint32_t u32Fn;
+ uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi, ss;
+ uint8_t chan_nr;
+ GsmL1_Prim_t *l1p;
+ struct msgb *nmsg = NULL;
+
+ 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)) {
+ ss = subCh = L1SAP_CHAN2SS_TCHH(chan_nr);
+ sapi = GsmL1_Sapi_TchH;
+ } else {
+ subCh = 0x1f;
+ ss = 0;
+ sapi = GsmL1_Sapi_TchF;
+ }
+
+ lchan = &trx->ts[u8Tn].lchan[ss];
+
+ /* 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);
+ l1if_tch_encode(lchan,
+ l1p->u.phDataReq.msgUnitParam.u8Buffer,
+ &l1p->u.phDataReq.msgUnitParam.u8Size,
+ msg->data, msg->len);
+ }
+
+ /* no message/data, we generate an empty traffic msg */
+ if (!nmsg)
+ nmsg = gen_empty_tch_msg(lchan);
+
+ /* 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 */
+ 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);
+
+ msgb_free(msg);
+ return 0;
+}
+
+static int mph_info_req(struct gsm_bts_trx *trx, struct msgb *msg,
+ struct osmo_phsap_prim *l1sap)
+{
+ struct lc15l1_hdl *fl1 = trx_lc15l1_hdl(trx);
+ uint8_t u8Tn, ss;
+ 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;
+ u8Tn = L1SAP_CHAN2TS(chan_nr);
+ ss = l1sap_chan2ss(chan_nr);
+ lchan = &trx->ts[u8Tn].lchan[ss];
+ 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;
+ u8Tn = L1SAP_CHAN2TS(chan_nr);
+ ss = l1sap_chan2ss(chan_nr);
+ lchan = &trx->ts[u8Tn].lchan[ss];
+ 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);
+ msgb_free(msg);
+ 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;
+
+ switch (OSMO_PRIM_HDR(&l1sap->oph)) {
+ case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST):
+ rc = ph_data_req(trx, msg, l1sap);
+ break;
+ case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST):
+ rc = ph_tch_req(trx, msg, l1sap);
+ break;
+ case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST):
+ rc = mph_info_req(trx, msg, l1sap);
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "unknown prim %d op %d\n",
+ l1sap->oph.primitive, l1sap->oph.operation);
+ rc = -EINVAL;
+ }
+
+ if (rc)
+ msgb_free(msg);
+ return rc;
+}
+
+static int handle_mph_time_ind(struct lc15l1_hdl *fl1,
+ GsmL1_MphTimeInd_t *time_ind)
+{
+ struct gsm_bts_trx *trx = fl1->priv;
+ 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) {
+ 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;
+
+ return l1sap_up(trx, &l1sap);
+}
+
+static uint8_t chan_nr_by_sapi(enum gsm_phys_chan_config pchan,
+ GsmL1_Sapi_t sapi, GsmL1_SubCh_t subCh,
+ uint8_t u8Tn, uint32_t u32Fn)
+{
+ uint8_t cbits = 0;
+ switch (sapi) {
+ case GsmL1_Sapi_Bcch:
+ cbits = 0x10;
+ break;
+ case GsmL1_Sapi_Sacch:
+ switch(pchan) {
+ case GSM_PCHAN_TCH_F:
+ cbits = 0x01;
+ break;
+ case GSM_PCHAN_TCH_H:
+ cbits = 0x02 + subCh;
+ break;
+ case GSM_PCHAN_CCCH_SDCCH4:
+ cbits = 0x04 + subCh;
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ cbits = 0x08 + subCh;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "SACCH for pchan %d?\n",
+ pchan);
+ return 0;
+ }
+ break;
+ case GsmL1_Sapi_Sdcch:
+ switch(pchan) {
+ case GSM_PCHAN_CCCH_SDCCH4:
+ cbits = 0x04 + subCh;
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ cbits = 0x08 + subCh;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "SDCCH for pchan %d?\n",
+ pchan);
+ return 0;
+ }
+ break;
+ case GsmL1_Sapi_Agch:
+ case GsmL1_Sapi_Pch:
+ cbits = 0x12;
+ break;
+ case GsmL1_Sapi_Pdtch:
+ case GsmL1_Sapi_Pacch:
+ switch(pchan) {
+ case GSM_PCHAN_PDCH:
+ cbits = 0x01;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "PDTCH for pchan %d?\n",
+ pchan);
+ return 0;
+ }
+ break;
+ case GsmL1_Sapi_TchF:
+ cbits = 0x01;
+ break;
+ case GsmL1_Sapi_TchH:
+ cbits = 0x02 + subCh;
+ break;
+ case GsmL1_Sapi_FacchF:
+ cbits = 0x01;
+ break;
+ case GsmL1_Sapi_FacchH:
+ cbits = 0x02 + subCh;
+ break;
+ case GsmL1_Sapi_Ptcch:
+ if (!L1SAP_IS_PTCCH(u32Fn)) {
+ LOGP(DL1C, LOGL_FATAL, "Not expecting PTCCH at frame "
+ "number other than 12, got it at %u (%u). "
+ "Please fix!\n", u32Fn % 52, u32Fn);
+ abort();
+ }
+ switch(pchan) {
+ case GSM_PCHAN_PDCH:
+ cbits = 0x01;
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "PTCCH for pchan %d?\n",
+ pchan);
+ return 0;
+ }
+ break;
+ default:
+ return 0;
+ }
+
+ /* not reached due to default case above */
+ return (cbits << 3) | u8Tn;
+}
+
+static int handle_ph_readytosend_ind(struct lc15l1_hdl *fl1,
+ GsmL1_PhReadyToSendInd_t *rts_ind,
+ struct msgb *l1p_msg)
+{
+ struct gsm_bts_trx *trx = fl1->priv;
+ 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].pchan, 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 = 0x40;
+ else
+ link_id = 0;
+ 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);
+
+ DEBUGP(DL1P, "Rx PH-RTS.ind %02u/%02u/%02u SAPI=%s\n",
+ g_time.t1, g_time.t2, g_time.t3,
+ get_value_string(lc15bts_l1sapi_names, rts_ind->sapi));
+
+ /* in all other cases, we need to allocate a new PH-DATA.ind
+ * primitive msgb and start to fill it */
+ resp_msg = l1p_msgb_alloc();
+ data_req = data_req_from_rts_ind(msgb_l1prim(resp_msg), rts_ind);
+ msu_param = &data_req->msgUnitParam;
+
+ /* set default size */
+ msu_param->u8Size = GSM_MACBLOCK_LEN;
+
+ switch (rts_ind->sapi) {
+ case GsmL1_Sapi_Sch:
+ /* compute T3prime */
+ t3p = (g_time.t3 - 1) / 10;
+ /* fill SCH burst with data */
+ msu_param->u8Size = 4;
+ msu_param->u8Buffer[0] = (bts->bsic << 2) | (g_time.t1 >> 9);
+ msu_param->u8Buffer[1] = (g_time.t1 >> 1);
+ msu_param->u8Buffer[2] = (g_time.t1 << 7) | (g_time.t2 << 2) | (t3p >> 1);
+ msu_param->u8Buffer[3] = (t3p & 1);
+ break;
+ case GsmL1_Sapi_Prach:
+ goto empty_frame;
+ break;
+ case GsmL1_Sapi_Cbch:
+ /* get them from bts->si_buf[] */
+ bts_cbch_get(bts, msu_param->u8Buffer, &g_time);
+ break;
+ default:
+ memcpy(msu_param->u8Buffer, fill_frame, GSM_MACBLOCK_LEN);
+ break;
+ }
+tx:
+
+ /* transmit */
+ if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], resp_msg) != 0) {
+ LOGP(DL1C, LOGL_ERROR, "MQ_L1_WRITE queue full. Dropping msg.\n");
+ msgb_free(resp_msg);
+ }
+
+ 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)
+{
+ 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_qbits = m->i16BurstTiming;
+ l1sap.u.info.u.meas_ind.ber10k = (unsigned int) (m->fBer * 100);
+ l1sap.u.info.u.meas_ind.inv_rssi = (uint8_t) (m->fRssi * -1);
+
+ return l1sap_up(trx, &l1sap);
+}
+
+static int handle_ph_data_ind(struct lc15l1_hdl *fl1, GsmL1_PhDataInd_t *data_ind,
+ struct msgb *l1p_msg)
+{
+ struct gsm_bts_trx *trx = fl1->priv;
+ uint8_t chan_nr, link_id;
+ struct osmo_phsap_prim *l1sap;
+ uint32_t fn;
+ uint8_t *data, len;
+ int rc = 0;
+ int8_t rssi;
+
+ chan_nr = chan_nr_by_sapi(trx->ts[data_ind->u8Tn].pchan, data_ind->sapi,
+ data_ind->subCh, data_ind->u8Tn, data_ind->u32Fn);
+ if (!chan_nr) {
+ LOGP(DL1C, LOGL_ERROR, "PH-DATA-INDICATION for unknown sapi "
+ "%d\n", data_ind->sapi);
+ msgb_free(l1p_msg);
+ return ENOTSUP;
+ }
+ fn = data_ind->u32Fn;
+ link_id = (data_ind->sapi == GsmL1_Sapi_Sacch) ? 0x40 : 0x00;
+
+ process_meas_res(trx, chan_nr, &data_ind->measParam);
+
+ if (data_ind->measParam.fLinkQuality < fl1->min_qual_norm
+ && data_ind->msgUnitParam.u8Size != 0) {
+ msgb_free(l1p_msg);
+ return 0;
+ }
+
+ DEBUGP(DL1C, "Rx PH-DATA.ind %s (hL2 %08x): %s",
+ get_value_string(lc15bts_l1sapi_names, data_ind->sapi),
+ (uint32_t)data_ind->hLayer2,
+ osmo_hexdump(data_ind->msgUnitParam.u8Buffer,
+ data_ind->msgUnitParam.u8Size));
+ dump_meas_res(LOGL_DEBUG, &data_ind->measParam);
+
+ /* check for TCH */
+ if (data_ind->sapi == GsmL1_Sapi_TchF
+ || data_ind->sapi == GsmL1_Sapi_TchH) {
+ /* TCH speech frame handling */
+ rc = l1if_tch_rx(trx, chan_nr, l1p_msg);
+ msgb_free(l1p_msg);
+ return rc;
+ }
+
+ /* get rssi */
+ rssi = (int8_t) (data_ind->measParam.fRssi);
+ /* get data pointer and length */
+ data = data_ind->msgUnitParam.u8Buffer;
+ len = data_ind->msgUnitParam.u8Size;
+ /* pull lower header part before data */
+ msgb_pull(l1p_msg, data - l1p_msg->data);
+ /* trim remaining data to it's size, to get rid of upper header part */
+ rc = msgb_trim(l1p_msg, len);
+ if (rc < 0)
+ MSGB_ABORT(l1p_msg, "No room for primitive data\n");
+ l1p_msg->l2h = l1p_msg->data;
+ /* push new l1 header */
+ l1p_msg->l1h = msgb_push(l1p_msg, sizeof(*l1sap));
+ /* fill header */
+ l1sap = msgb_l1sap_prim(l1p_msg);
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA,
+ PRIM_OP_INDICATION, l1p_msg);
+ l1sap->u.data.link_id = link_id;
+ l1sap->u.data.chan_nr = chan_nr;
+ l1sap->u.data.fn = fn;
+ l1sap->u.data.rssi = rssi;
+
+ return l1sap_up(trx, l1sap);
+}
+
+static int handle_ph_ra_ind(struct lc15l1_hdl *fl1, GsmL1_PhRaInd_t *ra_ind,
+ struct msgb *l1p_msg)
+{
+ struct gsm_bts_trx *trx = fl1->priv;
+ struct gsm_bts *bts = trx->bts;
+ struct gsm_bts_role_bts *btsb = bts->role;
+ struct gsm_lchan *lchan;
+ struct osmo_phsap_prim *l1sap;
+ uint32_t fn;
+ uint8_t ra, acc_delay = 0;
+ int rc;
+
+ /* increment number of busy RACH slots, if required */
+ if (trx == bts->c0 &&
+ ra_ind->measParam.fRssi >= btsb->load.rach.busy_thresh)
+ btsb->load.rach.busy++;
+
+ if (ra_ind->measParam.fLinkQuality < fl1->min_qual_rach) {
+ msgb_free(l1p_msg);
+ return 0;
+ }
+
+ if (ra_ind->measParam.i16BurstTiming > 0)
+ acc_delay = ra_ind->measParam.i16BurstTiming >> 2;
+
+ /* increment number of RACH slots with valid non-handover RACH burst */
+ lchan = l1if_hLayer_to_lchan(trx, (uint32_t)ra_ind->hLayer2);
+ if (trx == bts->c0 && !(lchan && lchan->ho.active == HANDOVER_ENABLED))
+ btsb->load.rach.access++;
+
+ dump_meas_res(LOGL_DEBUG, &ra_ind->measParam);
+
+ if (ra_ind->msgUnitParam.u8Size != 1) {
+ LOGP(DL1C, LOGL_ERROR, "PH-RACH-INDICATION has %d bits\n",
+ ra_ind->sapi);
+ msgb_free(l1p_msg);
+ return 0;
+ }
+
+ fn = ra_ind->u32Fn;
+ ra = ra_ind->msgUnitParam.u8Buffer[0];
+ 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.ra = ra;
+ l1sap->u.rach_ind.acc_delay = acc_delay;
+ l1sap->u.rach_ind.fn = fn;
+ if (!lchan || lchan->ts->pchan == GSM_PCHAN_CCCH ||
+ lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4)
+ l1sap->u.rach_ind.chan_nr = 0x88;
+ else
+ l1sap->u.rach_ind.chan_nr = gsm_lchan2chan_nr(lchan);
+
+ return l1sap_up(trx, l1sap);
+}
+
+/* handle any random indication from the L1 */
+static int l1if_handle_ind(struct lc15l1_hdl *fl1, struct msgb *msg)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(msg);
+ int rc = 0;
+
+ switch (l1p->id) {
+ case GsmL1_PrimId_MphTimeInd:
+ rc = handle_mph_time_ind(fl1, &l1p->u.mphTimeInd);
+ break;
+ case GsmL1_PrimId_MphSyncInd:
+ break;
+ case GsmL1_PrimId_PhConnectInd:
+ break;
+ case GsmL1_PrimId_PhReadyToSendInd:
+ return handle_ph_readytosend_ind(fl1, &l1p->u.phReadyToSendInd,
+ msg);
+ case GsmL1_PrimId_PhDataInd:
+ return handle_ph_data_ind(fl1, &l1p->u.phDataInd, msg);
+ case GsmL1_PrimId_PhRaInd:
+ return handle_ph_ra_ind(fl1, &l1p->u.phRaInd, msg);
+ break;
+ default:
+ break;
+ }
+
+ /* Special return value '1' means: do not free */
+ if (rc != 1)
+ msgb_free(msg);
+
+ return rc;
+}
+
+static inline int is_prim_compat(GsmL1_Prim_t *l1p, struct wait_l1_conf *wlc)
+{
+ /* the limitation here is that we cannot have multiple callers
+ * sending the same primitive */
+ if (wlc->is_sys_prim != 0)
+ return 0;
+ if (l1p->id != wlc->conf_prim_id)
+ return 0;
+ return 1;
+}
+
+int l1if_handle_l1prim(int wq, struct lc15l1_hdl *fl1h, struct msgb *msg)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(msg);
+ struct wait_l1_conf *wlc;
+ int rc;
+
+ switch (l1p->id) {
+ case GsmL1_PrimId_MphTimeInd:
+ /* silent, don't clog the log file */
+ break;
+ default:
+ LOGP(DL1P, LOGL_DEBUG, "Rx L1 prim %s on queue %d\n",
+ get_value_string(lc15bts_l1prim_names, l1p->id), wq);
+ }
+
+ /* check if this is a resposne to a sync-waiting request */
+ llist_for_each_entry(wlc, &fl1h->wlc_list, list) {
+ if (is_prim_compat(l1p, wlc)) {
+ llist_del(&wlc->list);
+ if (wlc->cb)
+ rc = wlc->cb(fl1h->priv, msg, wlc->cb_data);
+ else {
+ rc = 0;
+ msgb_free(msg);
+ }
+ release_wlc(wlc);
+ return rc;
+ }
+ }
+
+ /* if we reach here, it is not a Conf for a pending Req */
+ return l1if_handle_ind(fl1h, msg);
+}
+
+int l1if_handle_sysprim(struct lc15l1_hdl *fl1h, struct msgb *msg)
+{
+ Litecell15_Prim_t *sysp = msgb_sysprim(msg);
+ struct wait_l1_conf *wlc;
+ int rc;
+
+ LOGP(DL1P, LOGL_DEBUG, "Rx SYS prim %s\n",
+ get_value_string(lc15bts_sysprim_names, sysp->id));
+
+ /* check if this is a resposne to a sync-waiting request */
+ llist_for_each_entry(wlc, &fl1h->wlc_list, list) {
+ /* the limitation here is that we cannot have multiple callers
+ * sending the same primitive */
+ if (wlc->is_sys_prim && sysp->id == wlc->conf_prim_id) {
+ llist_del(&wlc->list);
+ if (wlc->cb)
+ rc = wlc->cb(fl1h->priv, 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);
+}
+
+#if 0
+/* called by RSL if the BCCH SI has been modified */
+int sysinfo_has_changed(struct gsm_bts *bts, int si)
+{
+ /* FIXME: Determine BS_AG_BLKS_RES and
+ * * set cfgParams.u.agch.u8NbrOfAgch
+ * * determine implications on paging
+ */
+ /* FIXME: Check for Extended BCCH presence */
+ /* FIXME: Check for CCCH_CONF */
+ /* FIXME: Check for BS_PA_MFRMS: update paging */
+
+ return 0;
+}
+#endif
+
+static int activate_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ Litecell15_Prim_t *sysp = msgb_sysprim(resp);
+ GsmL1_Status_t status;
+ int on = 0;
+ unsigned int i;
+
+ if (sysp->id == Litecell15_PrimId_ActivateRfCnf)
+ on = 1;
+
+ if (on)
+ status = sysp->u.activateRfCnf.status;
+ else
+ status = sysp->u.deactivateRfCnf.status;
+
+ LOGP(DL1C, LOGL_INFO, "Rx RF-%sACT.conf (status=%s)\n", on ? "" : "DE",
+ get_value_string(lc15bts_l1status_names, status));
+
+
+ if (on) {
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_FATAL, "RF-ACT.conf with status %s\n",
+ get_value_string(lc15bts_l1status_names, status));
+ bts_shutdown(trx->bts, "RF-ACT failure");
+ } else
+ bts_update_status(BTS_STATUS_RF_ACTIVE, 1);
+
+ /* signal availability */
+ oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK);
+ oml_mo_tx_sw_act_rep(&trx->mo);
+ oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK);
+ oml_mo_tx_sw_act_rep(&trx->bb_transc.mo);
+
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++)
+ oml_mo_state_chg(&trx->ts[i].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY);
+ } else {
+ bts_update_status(BTS_STATUS_RF_ACTIVE, 0);
+ oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE);
+ oml_mo_state_chg(&trx->bb_transc.mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE);
+ }
+
+ msgb_free(resp);
+
+ return 0;
+}
+
+/* activate or de-activate the entire RF-Frontend */
+int l1if_activate_rf(struct lc15l1_hdl *hdl, int on)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ Litecell15_Prim_t *sysp = msgb_sysprim(msg);
+
+ if (on) {
+ sysp->id = Litecell15_PrimId_ActivateRfReq;
+ sysp->u.activateRfReq.msgq.u8UseTchMsgq = 0;
+ sysp->u.activateRfReq.msgq.u8UsePdtchMsgq = pcu_direct;
+
+ sysp->u.activateRfReq.u8UnusedTsMode = 0;
+ sysp->u.activateRfReq.u8McCorrMode = 0;
+
+ /* maximum cell size in quarter-bits, 90 == 12.456 km */
+ sysp->u.activateRfReq.u8MaxCellSize = 90;
+ } else {
+ sysp->id = Litecell15_PrimId_DeactivateRfReq;
+ }
+
+ return l1if_req_compl(hdl, msg, activate_rf_compl_cb, NULL);
+}
+
+static void mute_handle_ts(struct gsm_bts_trx_ts *ts, int is_muted)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ts->lchan); i++) {
+ struct gsm_lchan *lchan = &ts->lchan[i];
+
+ if (!is_muted)
+ continue;
+
+ if (lchan->state != LCHAN_S_ACTIVE)
+ continue;
+
+ /* skip channels that might be active for another reason */
+ if (lchan->type == GSM_LCHAN_CCCH)
+ continue;
+ if (lchan->type == GSM_LCHAN_PDTCH)
+ continue;
+
+ if (lchan->s <= 0)
+ continue;
+
+ lchan->s = 0;
+ rsl_tx_conn_fail(lchan, RSL_ERR_RADIO_LINK_FAIL);
+ }
+}
+
+static int mute_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+ Litecell15_Prim_t *sysp = msgb_sysprim(resp);
+ GsmL1_Status_t status;
+
+ status = sysp->u.muteRfCnf.status;
+
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_ERROR, "Rx RF-MUTE.conf with status %s\n",
+ get_value_string(lc15bts_l1status_names, status));
+ oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 0);
+ } else {
+ int i;
+
+ LOGP(DL1C, LOGL_INFO, "Rx RF-MUTE.conf with status=%s\n",
+ get_value_string(lc15bts_l1status_names, status));
+ bts_update_status(BTS_STATUS_RF_MUTE, fl1h->last_rf_mute[0]);
+ oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 1);
+
+ osmo_static_assert(
+ ARRAY_SIZE(trx->ts) >= ARRAY_SIZE(fl1h->last_rf_mute),
+ ts_array_size);
+
+ for (i = 0; i < ARRAY_SIZE(fl1h->last_rf_mute); ++i)
+ mute_handle_ts(&trx->ts[i], fl1h->last_rf_mute[i]);
+ }
+
+ msgb_free(resp);
+
+ return 0;
+}
+
+/* mute/unmute RF time slots */
+int l1if_mute_rf(struct lc15l1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ Litecell15_Prim_t *sysp = msgb_sysprim(msg);
+
+ LOGP(DL1C, LOGL_INFO, "Tx RF-MUTE.req (%d, %d, %d, %d, %d, %d, %d, %d)\n",
+ mute[0], mute[1], mute[2], mute[3],
+ mute[4], mute[5], mute[6], mute[7]
+ );
+
+ sysp->id = Litecell15_PrimId_MuteRfReq;
+ memcpy(sysp->u.muteRfReq.u8Mute, mute, sizeof(sysp->u.muteRfReq.u8Mute));
+ /* save for later use */
+ memcpy(hdl->last_rf_mute, mute, sizeof(hdl->last_rf_mute));
+
+ return l1if_req_compl(hdl, msg, cb ? cb : mute_rf_compl_cb, NULL);
+}
+
+/* call-back on arrival of DSP+FPGA version + band capability */
+static int info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ Litecell15_Prim_t *sysp = msgb_sysprim(resp);
+ Litecell15_SystemInfoCnf_t *sic = &sysp->u.systemInfoCnf;
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+ int rc;
+
+ fl1h->hw_info.dsp_version[0] = sic->dspVersion.major;
+ fl1h->hw_info.dsp_version[1] = sic->dspVersion.minor;
+ fl1h->hw_info.dsp_version[2] = sic->dspVersion.build;
+
+ fl1h->hw_info.fpga_version[0] = sic->fpgaVersion.major;
+ fl1h->hw_info.fpga_version[1] = sic->fpgaVersion.minor;
+ fl1h->hw_info.fpga_version[2] = sic->fpgaVersion.build;
+
+ LOGP(DL1C, LOGL_INFO, "DSP v%u.%u.%u, FPGA v%u.%u.%u\nn",
+ sic->dspVersion.major, sic->dspVersion.minor,
+ sic->dspVersion.build, sic->fpgaVersion.major,
+ sic->fpgaVersion.minor, sic->fpgaVersion.build);
+
+ if (!(fl1h->hw_info.band_support & trx->bts->band))
+ LOGP(DL1C, LOGL_FATAL, "BTS band %s not supported by hw\n",
+ gsm_band_name(trx->bts->band));
+
+ /* Request the activation */
+ l1if_activate_rf(fl1h, 1);
+
+ /* load calibration tables */
+ rc = calib_load(fl1h);
+ if (rc < 0)
+ LOGP(DL1C, LOGL_ERROR, "Operating without calibration; "
+ "unable to load tables!\n");
+
+ msgb_free(resp);
+ return 0;
+}
+
+/* request DSP+FPGA code versions */
+static int l1if_get_info(struct lc15l1_hdl *hdl)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ Litecell15_Prim_t *sysp = msgb_sysprim(msg);
+
+ sysp->id = Litecell15_PrimId_SystemInfoReq;
+
+ return l1if_req_compl(hdl, msg, info_compl_cb, NULL);
+}
+
+static int reset_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+ Litecell15_Prim_t *sysp = msgb_sysprim(resp);
+ GsmL1_Status_t status = sysp->u.layer1ResetCnf.status;
+
+ LOGP(DL1C, LOGL_NOTICE, "Rx L1-RESET.conf (status=%s)\n",
+ get_value_string(lc15bts_l1status_names, status));
+
+ msgb_free(resp);
+
+ /* If we're coming out of reset .. */
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_FATAL, "L1-RESET.conf with status %s\n",
+ get_value_string(lc15bts_l1status_names, status));
+ bts_shutdown(trx->bts, "L1-RESET failure");
+ }
+
+ /* as we cannot get the current DSP trace flags, we simply
+ * set them to zero (or whatever dsp_trace_f has been initialized to */
+ l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f);
+
+ /* obtain version information on DSP/FPGA and band capabilities */
+ l1if_get_info(fl1h);
+
+ return 0;
+}
+
+int l1if_reset(struct lc15l1_hdl *hdl)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ Litecell15_Prim_t *sysp = msgb_sysprim(msg);
+ sysp->id = Litecell15_PrimId_Layer1ResetReq;
+
+ return l1if_req_compl(hdl, msg, reset_compl_cb, NULL);
+}
+
+/* set the trace flags within the DSP */
+int l1if_set_trace_flags(struct lc15l1_hdl *hdl, uint32_t flags)
+{
+ struct msgb *msg = sysp_msgb_alloc();
+ Litecell15_Prim_t *sysp = msgb_sysprim(msg);
+
+ LOGP(DL1C, LOGL_INFO, "Tx SET-TRACE-FLAGS.req (0x%08x)\n",
+ flags);
+
+ sysp->id = Litecell15_PrimId_SetTraceFlagsReq;
+ sysp->u.setTraceFlagsReq.u32Tf = flags;
+
+ hdl->dsp_trace_f = flags;
+
+ /* There is no confirmation we could wait for */
+ if (osmo_wqueue_enqueue(&hdl->write_q[MQ_SYS_WRITE], msg) != 0) {
+ LOGP(DL1C, LOGL_ERROR, "MQ_SYS_WRITE queue full. Dropping msg\n");
+ msgb_free(msg);
+ return -EAGAIN;
+ }
+ return 0;
+}
+
+static int get_hwinfo(struct lc15l1_hdl *fl1h)
+{
+ int rc;
+
+ rc = lc15bts_rev_get();
+ if (rc < 0)
+ return rc;
+ fl1h->hw_info.ver_major = rc;
+
+ rc = lc15bts_model_get();
+ if (rc < 0)
+ return rc;
+ fl1h->hw_info.ver_minor = rc;
+
+ rc = lc15bts_option_get(LC15BTS_OPTION_BAND);
+ if (rc < 0)
+ return rc;
+
+ switch (rc) {
+ case LC15BTS_BAND_850:
+ fl1h->hw_info.band_support = GSM_BAND_850;
+ break;
+ case LC15BTS_BAND_900:
+ fl1h->hw_info.band_support = GSM_BAND_900;
+ break;
+ case LC15BTS_BAND_1800:
+ fl1h->hw_info.band_support = GSM_BAND_1800;
+ break;
+ case LC15BTS_BAND_1900:
+ fl1h->hw_info.band_support = GSM_BAND_1900;
+ break;
+ default:
+ return -1;
+ }
+ return 0;
+}
+
+struct lc15l1_hdl *l1if_open(void *priv, int trx_nr)
+{
+ struct lc15l1_hdl *fl1h;
+ int rc;
+
+ LOGP(DL1C, LOGL_INFO, "Litecell 1.5 BTS L1IF compiled against API headers "
+ "v%u.%u.%u\n", LITECELL15_API_VERSION >> 16,
+ (LITECELL15_API_VERSION >> 8) & 0xff,
+ LITECELL15_API_VERSION & 0xff);
+
+ fl1h = talloc_zero(priv, struct lc15l1_hdl);
+ if (!fl1h)
+ return NULL;
+ INIT_LLIST_HEAD(&fl1h->wlc_list);
+
+ fl1h->priv = priv;
+ fl1h->clk_cal = 0;
+ fl1h->clk_use_eeprom = 1;
+ fl1h->min_qual_rach = MIN_QUAL_RACH;
+ fl1h->min_qual_norm = MIN_QUAL_NORM;
+
+ get_hwinfo(fl1h);
+
+ /* NTQD: Change how rx_nr is handle in multi-trx */
+ fl1h->hw_info.trx_nr = trx_nr;
+
+ rc = l1if_transport_open(MQ_SYS_WRITE, fl1h);
+ if (rc < 0) {
+ talloc_free(fl1h);
+ return NULL;
+ }
+
+ rc = l1if_transport_open(MQ_L1_WRITE, fl1h);
+ if (rc < 0) {
+ l1if_transport_close(MQ_SYS_WRITE, fl1h);
+ talloc_free(fl1h);
+ return NULL;
+ }
+
+ return fl1h;
+}
+
+int l1if_close(struct lc15l1_hdl *fl1h)
+{
+ l1if_transport_close(MQ_L1_WRITE, fl1h);
+ l1if_transport_close(MQ_SYS_WRITE, fl1h);
+ return 0;
+}
diff --git a/src/osmo-bts-litecell15/l1_if.h b/src/osmo-bts-litecell15/l1_if.h
new file mode 100644
index 00000000..a382c562
--- /dev/null
+++ b/src/osmo-bts-litecell15/l1_if.h
@@ -0,0 +1,121 @@
+#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 <nrw/litecell15/gsml1prim.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 lc15l1_hdl {
+ struct gsm_time gsm_time;
+ uint32_t hLayer1; /* handle to the L1 instance in the DSP */
+ uint32_t dsp_trace_f;
+ uint8_t clk_use_eeprom;
+ int clk_cal;
+ uint8_t clk_src;
+ float min_qual_rach;
+ float min_qual_norm;
+ char *calib_path;
+ struct llist_head wlc_list;
+
+ void *priv; /* user reference */
+
+ 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;
+ uint8_t trx_nr; // 1 or 2
+ } hw_info;
+
+ struct calib_send_state st;
+
+ uint8_t last_rf_mute[8];
+};
+
+#define msgb_l1prim(msg) ((GsmL1_Prim_t *)(msg)->l1h)
+#define msgb_sysprim(msg) ((Litecell15_Prim_t *)(msg)->l1h)
+
+typedef int l1if_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, void *data);
+
+/* send a request primitive to the L1 and schedule completion call-back */
+int l1if_req_compl(struct lc15l1_hdl *fl1h, struct msgb *msg,
+ l1if_compl_cb *cb, void *cb_data);
+int l1if_gsm_req_compl(struct lc15l1_hdl *fl1h, struct msgb *msg,
+ l1if_compl_cb *cb, void *cb_data);
+
+struct lc15l1_hdl *l1if_open(void *priv, int trx_nr);
+int l1if_close(struct lc15l1_hdl *hdl);
+int l1if_reset(struct lc15l1_hdl *hdl);
+int l1if_activate_rf(struct lc15l1_hdl *hdl, int on);
+int l1if_set_trace_flags(struct lc15l1_hdl *hdl, uint32_t flags);
+int l1if_set_txpower(struct lc15l1_hdl *fl1h, float tx_power);
+int l1if_mute_rf(struct lc15l1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb);
+
+struct msgb *l1p_msgb_alloc(void);
+struct msgb *sysp_msgb_alloc(void);
+
+uint32_t l1if_lchan_to_hLayer(struct gsm_lchan *lchan);
+struct gsm_lchan *l1if_hLayer_to_lchan(struct gsm_bts_trx *trx, uint32_t hLayer);
+
+/* tch.c */
+void l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len,
+ const uint8_t *rtp_pl, unsigned int rtp_pl_len);
+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);
+
+/* ciphering */
+int l1if_set_ciphering(struct lc15l1_hdl *fl1h,
+ struct gsm_lchan *lchan,
+ int dir_downlink);
+
+/* channel control */
+int l1if_rsl_chan_act(struct gsm_lchan *lchan);
+int l1if_rsl_chan_rel(struct gsm_lchan *lchan);
+int l1if_rsl_chan_mod(struct gsm_lchan *lchan);
+int l1if_rsl_deact_sacch(struct gsm_lchan *lchan);
+int l1if_rsl_mode_modify(struct gsm_lchan *lchan);
+
+/* calibration loading */
+int calib_load(struct lc15l1_hdl *fl1h);
+
+/* public helpers for test */
+int bts_check_for_ciph_cmd(struct lc15l1_hdl *fl1h,
+ struct msgb *msg, struct gsm_lchan *lchan);
+inline int l1if_ms_pwr_ctrl(struct gsm_lchan *lchan, const int uplink_target,
+ const uint8_t ms_power, const float rxLevel);
+#endif /* _L1_IF_H */
diff --git a/src/osmo-bts-litecell15/l1_transp.h b/src/osmo-bts-litecell15/l1_transp.h
new file mode 100644
index 00000000..7d6772e8
--- /dev/null
+++ b/src/osmo-bts-litecell15/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 lc15l1_hdl *fl1h, struct msgb *msg);
+int l1if_handle_sysprim(struct lc15l1_hdl *fl1h, struct msgb *msg);
+
+/* functions exported by a transport */
+int l1if_transport_open(int q, struct lc15l1_hdl *fl1h);
+int l1if_transport_close(int q, struct lc15l1_hdl *fl1h);
+
+#endif /* _L1_TRANSP_H */
diff --git a/src/osmo-bts-litecell15/l1_transp_hw.c b/src/osmo-bts-litecell15/l1_transp_hw.c
new file mode 100644
index 00000000..00f1e0a3
--- /dev/null
+++ b/src/osmo-bts-litecell15/l1_transp_hw.c
@@ -0,0 +1,320 @@
+/* Interface handler for Nuran Wireless Litecell 1.5 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 <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/litecell15/litecell15.h>
+#include <nrw/litecell15/gsml1prim.h>
+#include <nrw/litecell15/gsml1const.h>
+#include <nrw/litecell15/gsml1types.h>
+
+#include "lc15bts.h"
+#include "l1_if.h"
+#include "l1_transp.h"
+
+
+#define DEV_SYS_DSP2ARM_NAME "/dev/msgq/litecell15_dsp2arm_trx"
+#define DEV_SYS_ARM2DSP_NAME "/dev/msgq/litecell15_arm2dsp_trx"
+#define DEV_L1_DSP2ARM_NAME "/dev/msgq/gsml1_sig_dsp2arm_trx"
+#define DEV_L1_ARM2DSP_NAME "/dev/msgq/gsml1_sig_arm2dsp_trx"
+
+#define DEV_TCH_DSP2ARM_NAME "/dev/msgq/gsml1_tch_dsp2arm_trx"
+#define DEV_TCH_ARM2DSP_NAME "/dev/msgq/gsml1_tch_arm2dsp_trx"
+#define DEV_PDTCH_DSP2ARM_NAME "/dev/msgq/gsml1_pdtch_dsp2arm_trx"
+#define DEV_PDTCH_ARM2DSP_NAME "/dev/msgq/gsml1_pdtch_arm2dsp_trx"
+
+static const char *rd_devnames[] = {
+ [MQ_SYS_READ] = DEV_SYS_DSP2ARM_NAME,
+ [MQ_L1_READ] = DEV_L1_DSP2ARM_NAME,
+ [MQ_TCH_READ] = DEV_TCH_DSP2ARM_NAME,
+ [MQ_PDTCH_READ] = DEV_PDTCH_DSP2ARM_NAME,
+};
+
+static const char *wr_devnames[] = {
+ [MQ_SYS_WRITE] = DEV_SYS_ARM2DSP_NAME,
+ [MQ_L1_WRITE] = DEV_L1_ARM2DSP_NAME,
+ [MQ_TCH_WRITE] = DEV_TCH_ARM2DSP_NAME,
+ [MQ_PDTCH_WRITE]= DEV_PDTCH_ARM2DSP_NAME,
+};
+
+/*
+ * Make sure that all structs we read fit into the LC15BTS_PRIM_SIZE
+ */
+osmo_static_assert(sizeof(GsmL1_Prim_t) + 128 <= LC15BTS_PRIM_SIZE, l1_prim)
+osmo_static_assert(sizeof(Litecell15_Prim_t) + 128 <= LC15BTS_PRIM_SIZE, super_prim)
+
+static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what)
+{
+ struct osmo_wqueue *queue;
+
+ queue = container_of(fd, struct osmo_wqueue, bfd);
+
+ if (what & BSC_FD_READ)
+ queue->read_cb(fd);
+
+ if (what & BSC_FD_EXCEPT)
+ queue->except_cb(fd);
+
+ if (what & BSC_FD_WRITE) {
+ struct iovec iov[5];
+ struct msgb *msg, *tmp;
+ int written, count = 0;
+
+ fd->when &= ~BSC_FD_WRITE;
+
+ llist_for_each_entry(msg, &queue->msg_queue, list) {
+ /* more writes than we have */
+ if (count >= ARRAY_SIZE(iov))
+ break;
+
+ iov[count].iov_base = msg->l1h;
+ iov[count].iov_len = msgb_l1len(msg);
+ count += 1;
+ }
+
+ /* TODO: check if all lengths are the same. */
+
+
+ /* Nothing scheduled? This should not happen. */
+ if (count == 0) {
+ if (!llist_empty(&queue->msg_queue))
+ fd->when |= BSC_FD_WRITE;
+ return 0;
+ }
+
+ written = writev(fd->fd, iov, count);
+ if (written < 0) {
+ /* nothing written?! */
+ if (!llist_empty(&queue->msg_queue))
+ fd->when |= BSC_FD_WRITE;
+ return 0;
+ }
+
+ /* now delete the written entries */
+ written = written / iov[0].iov_len;
+ count = 0;
+ llist_for_each_entry_safe(msg, tmp, &queue->msg_queue, list) {
+ queue->current_length -= 1;
+
+ llist_del(&msg->list);
+ msgb_free(msg);
+
+ count += 1;
+ if (count >= written)
+ break;
+ }
+
+ if (!llist_empty(&queue->msg_queue))
+ fd->when |= BSC_FD_WRITE;
+ }
+
+ return 0;
+}
+
+static int prim_size_for_queue(int queue)
+{
+ switch (queue) {
+ case MQ_SYS_WRITE:
+ return sizeof(Litecell15_Prim_t);
+ case MQ_L1_WRITE:
+ case MQ_TCH_WRITE:
+ case MQ_PDTCH_WRITE:
+ return sizeof(GsmL1_Prim_t);
+ default:
+ /* The compiler can't know that priv_nr is an enum. Assist. */
+ LOGP(DL1C, LOGL_FATAL, "writing on a wrong queue: %d\n",
+ queue);
+ assert(false);
+ break;
+ }
+}
+
+/* callback when there's something to read from the l1 msg_queue */
+static int read_dispatch_one(struct lc15l1_hdl *fl1h, struct msgb *msg, int queue)
+{
+ switch (queue) {
+ case MQ_SYS_WRITE:
+ return l1if_handle_sysprim(fl1h, msg);
+ case MQ_L1_WRITE:
+ case MQ_TCH_WRITE:
+ case MQ_PDTCH_WRITE:
+ return l1if_handle_l1prim(queue, fl1h, msg);
+ default:
+ /* The compiler can't know that priv_nr is an enum. Assist. */
+ LOGP(DL1C, LOGL_FATAL, "writing on a wrong queue: %d\n",
+ queue);
+ assert(false);
+ break;
+ }
+};
+
+static int l1if_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ int i, rc;
+
+ const uint32_t prim_size = prim_size_for_queue(ofd->priv_nr);
+ uint32_t count;
+
+ struct iovec iov[3];
+ struct msgb *msg[ARRAY_SIZE(iov)];
+
+ for (i = 0; i < ARRAY_SIZE(iov); ++i) {
+ msg[i] = msgb_alloc_headroom(prim_size + 128, 128, "1l_fd");
+ msg[i]->l1h = msg[i]->data;
+
+ iov[i].iov_base = msg[i]->l1h;
+ iov[i].iov_len = msgb_tailroom(msg[i]);
+ }
+
+
+ rc = readv(ofd->fd, iov, ARRAY_SIZE(iov));
+ count = rc / prim_size;
+
+ for (i = 0; i < count; ++i) {
+ msgb_put(msg[i], prim_size);
+ read_dispatch_one(ofd->data, msg[i], ofd->priv_nr);
+ }
+
+ for (i = count; i < ARRAY_SIZE(iov); ++i)
+ msgb_free(msg[i]);
+
+ return 1;
+}
+
+/* callback when we can write to one of the l1 msg_queue devices */
+static int l1fd_write_cb(struct osmo_fd *ofd, struct msgb *msg)
+{
+ int rc;
+
+ rc = write(ofd->fd, msg->l1h, msgb_l1len(msg));
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_ERROR, "error writing to L1 msg_queue: %s\n",
+ strerror(errno));
+ return rc;
+ } else if (rc < msg->len) {
+ LOGP(DL1C, LOGL_ERROR, "short write to L1 msg_queue: "
+ "%u < %u\n", rc, msg->len);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+int l1if_transport_open(int q, struct lc15l1_hdl *hdl)
+{
+ 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], hdl->hw_info.trx_nr);
+ 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",
+ rd_devnames[q],
+ strerror(errno));
+ return rc;
+ }
+ read_ofd->fd = rc;
+ read_ofd->priv_nr = q;
+ read_ofd->data = hdl;
+ read_ofd->cb = l1if_fd_cb;
+ read_ofd->when = BSC_FD_READ;
+ rc = osmo_fd_register(read_ofd);
+ if (rc < 0) {
+ close(read_ofd->fd);
+ read_ofd->fd = -1;
+ return rc;
+ }
+
+ snprintf(buf, sizeof(buf)-1, "%s%d", wr_devnames[q], hdl->hw_info.trx_nr);
+ 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",
+ wr_devnames[q],
+ strerror(errno));
+ goto out_read;
+ }
+ osmo_wqueue_init(wq, 10);
+ wq->write_cb = l1fd_write_cb;
+ write_ofd->cb = wqueue_vector_cb;
+ write_ofd->fd = rc;
+ write_ofd->priv_nr = q;
+ write_ofd->data = hdl;
+ write_ofd->when = BSC_FD_WRITE;
+ rc = osmo_fd_register(write_ofd);
+ if (rc < 0) {
+ close(write_ofd->fd);
+ write_ofd->fd = -1;
+ goto out_read;
+ }
+
+ return 0;
+
+out_read:
+ close(hdl->read_ofd[q].fd);
+ osmo_fd_unregister(&hdl->read_ofd[q]);
+
+ return rc;
+}
+
+int l1if_transport_close(int q, struct lc15l1_hdl *hdl)
+{
+ struct osmo_fd *read_ofd = &hdl->read_ofd[q];
+ struct osmo_fd *write_ofd = &hdl->write_q[q].bfd;
+
+ osmo_fd_unregister(read_ofd);
+ close(read_ofd->fd);
+ read_ofd->fd = -1;
+
+ osmo_fd_unregister(write_ofd);
+ close(write_ofd->fd);
+ write_ofd->fd = -1;
+
+ return 0;
+}
diff --git a/src/osmo-bts-litecell15/lc15bts.c b/src/osmo-bts-litecell15/lc15bts.c
new file mode 100644
index 00000000..172a7e45
--- /dev/null
+++ b/src/osmo-bts-litecell15/lc15bts.c
@@ -0,0 +1,332 @@
+/* NuRAN Wireless Litecell 1.5 L1 API related definitions */
+
+/* Copyright (C) 2015 by Yves Godin <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/litecell15/litecell15.h>
+#include <nrw/litecell15/gsml1const.h>
+#include <nrw/litecell15/gsml1dbg.h>
+
+#include "lc15bts.h"
+
+enum l1prim_type lc15bts_get_l1prim_type(GsmL1_PrimId_t id)
+{
+ switch (id) {
+ case GsmL1_PrimId_MphInitReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphCloseReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphConnectReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphDisconnectReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphActivateReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphDeactivateReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphConfigReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphMeasureReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphInitCnf: return L1P_T_CONF;
+ case GsmL1_PrimId_MphCloseCnf: return L1P_T_CONF;
+ case GsmL1_PrimId_MphConnectCnf: return L1P_T_CONF;
+ case GsmL1_PrimId_MphDisconnectCnf: return L1P_T_CONF;
+ case GsmL1_PrimId_MphActivateCnf: return L1P_T_CONF;
+ case GsmL1_PrimId_MphDeactivateCnf: return L1P_T_CONF;
+ case GsmL1_PrimId_MphConfigCnf: return L1P_T_CONF;
+ case GsmL1_PrimId_MphMeasureCnf: return L1P_T_CONF;
+ case GsmL1_PrimId_PhEmptyFrameReq: return L1P_T_REQ;
+ case GsmL1_PrimId_PhDataReq: return L1P_T_REQ;
+ case GsmL1_PrimId_MphTimeInd: return L1P_T_IND;
+ case GsmL1_PrimId_MphSyncInd: return L1P_T_IND;
+ case GsmL1_PrimId_PhConnectInd: return L1P_T_IND;
+ case GsmL1_PrimId_PhReadyToSendInd: return L1P_T_IND;
+ case GsmL1_PrimId_PhDataInd: return L1P_T_IND;
+ case GsmL1_PrimId_PhRaInd: return L1P_T_IND;
+ default: return L1P_T_INVALID;
+ }
+}
+
+const struct value_string lc15bts_l1prim_names[GsmL1_PrimId_NUM+1] = {
+ { GsmL1_PrimId_MphInitReq, "MPH-INIT.req" },
+ { GsmL1_PrimId_MphCloseReq, "MPH-CLOSE.req" },
+ { GsmL1_PrimId_MphConnectReq, "MPH-CONNECT.req" },
+ { GsmL1_PrimId_MphDisconnectReq,"MPH-DISCONNECT.req" },
+ { GsmL1_PrimId_MphActivateReq, "MPH-ACTIVATE.req" },
+ { GsmL1_PrimId_MphDeactivateReq,"MPH-DEACTIVATE.req" },
+ { GsmL1_PrimId_MphConfigReq, "MPH-CONFIG.req" },
+ { GsmL1_PrimId_MphMeasureReq, "MPH-MEASURE.req" },
+ { GsmL1_PrimId_MphInitCnf, "MPH-INIT.conf" },
+ { GsmL1_PrimId_MphCloseCnf, "MPH-CLOSE.conf" },
+ { GsmL1_PrimId_MphConnectCnf, "MPH-CONNECT.conf" },
+ { GsmL1_PrimId_MphDisconnectCnf,"MPH-DISCONNECT.conf" },
+ { GsmL1_PrimId_MphActivateCnf, "MPH-ACTIVATE.conf" },
+ { GsmL1_PrimId_MphDeactivateCnf,"MPH-DEACTIVATE.conf" },
+ { GsmL1_PrimId_MphConfigCnf, "MPH-CONFIG.conf" },
+ { GsmL1_PrimId_MphMeasureCnf, "MPH-MEASURE.conf" },
+ { GsmL1_PrimId_MphTimeInd, "MPH-TIME.ind" },
+ { GsmL1_PrimId_MphSyncInd, "MPH-SYNC.ind" },
+ { GsmL1_PrimId_PhEmptyFrameReq, "PH-EMPTY_FRAME.req" },
+ { GsmL1_PrimId_PhDataReq, "PH-DATA.req" },
+ { GsmL1_PrimId_PhConnectInd, "PH-CONNECT.ind" },
+ { GsmL1_PrimId_PhReadyToSendInd,"PH-READY_TO_SEND.ind" },
+ { GsmL1_PrimId_PhDataInd, "PH-DATA.ind" },
+ { GsmL1_PrimId_PhRaInd, "PH-RA.ind" },
+ { 0, NULL }
+};
+
+GsmL1_PrimId_t lc15bts_get_l1prim_conf(GsmL1_PrimId_t id)
+{
+ switch (id) {
+ case GsmL1_PrimId_MphInitReq: return GsmL1_PrimId_MphInitCnf;
+ case GsmL1_PrimId_MphCloseReq: return GsmL1_PrimId_MphCloseCnf;
+ case GsmL1_PrimId_MphConnectReq: return GsmL1_PrimId_MphConnectCnf;
+ case GsmL1_PrimId_MphDisconnectReq: return GsmL1_PrimId_MphDisconnectCnf;
+ case GsmL1_PrimId_MphActivateReq: return GsmL1_PrimId_MphActivateCnf;
+ case GsmL1_PrimId_MphDeactivateReq: return GsmL1_PrimId_MphDeactivateCnf;
+ case GsmL1_PrimId_MphConfigReq: return GsmL1_PrimId_MphConfigCnf;
+ case GsmL1_PrimId_MphMeasureReq: return GsmL1_PrimId_MphMeasureCnf;
+ default: return -1; // Weak
+ }
+}
+
+enum l1prim_type lc15bts_get_sysprim_type(Litecell15_PrimId_t id)
+{
+ switch (id) {
+ case Litecell15_PrimId_SystemInfoReq: return L1P_T_REQ;
+ case Litecell15_PrimId_SystemInfoCnf: return L1P_T_CONF;
+ case Litecell15_PrimId_SystemFailureInd: return L1P_T_IND;
+ case Litecell15_PrimId_ActivateRfReq: return L1P_T_REQ;
+ case Litecell15_PrimId_ActivateRfCnf: return L1P_T_CONF;
+ case Litecell15_PrimId_DeactivateRfReq: return L1P_T_REQ;
+ case Litecell15_PrimId_DeactivateRfCnf: return L1P_T_CONF;
+ case Litecell15_PrimId_SetTraceFlagsReq: return L1P_T_REQ;
+ case Litecell15_PrimId_Layer1ResetReq: return L1P_T_REQ;
+ case Litecell15_PrimId_Layer1ResetCnf: return L1P_T_CONF;
+ case Litecell15_PrimId_SetCalibTblReq: return L1P_T_REQ;
+ case Litecell15_PrimId_SetCalibTblCnf: return L1P_T_CONF;
+ case Litecell15_PrimId_MuteRfReq: return L1P_T_REQ;
+ case Litecell15_PrimId_MuteRfCnf: return L1P_T_CONF;
+ case Litecell15_PrimId_SetRxAttenReq: return L1P_T_REQ;
+ case Litecell15_PrimId_SetRxAttenCnf: return L1P_T_CONF;
+ default: return L1P_T_INVALID;
+ }
+}
+
+const struct value_string lc15bts_sysprim_names[Litecell15_PrimId_NUM+1] = {
+ { Litecell15_PrimId_SystemInfoReq, "SYSTEM-INFO.req" },
+ { Litecell15_PrimId_SystemInfoCnf, "SYSTEM-INFO.conf" },
+ { Litecell15_PrimId_SystemFailureInd, "SYSTEM-FAILURE.ind" },
+ { Litecell15_PrimId_ActivateRfReq, "ACTIVATE-RF.req" },
+ { Litecell15_PrimId_ActivateRfCnf, "ACTIVATE-RF.conf" },
+ { Litecell15_PrimId_DeactivateRfReq, "DEACTIVATE-RF.req" },
+ { Litecell15_PrimId_DeactivateRfCnf, "DEACTIVATE-RF.conf" },
+ { Litecell15_PrimId_SetTraceFlagsReq, "SET-TRACE-FLAGS.req" },
+ { Litecell15_PrimId_Layer1ResetReq, "LAYER1-RESET.req" },
+ { Litecell15_PrimId_Layer1ResetCnf, "LAYER1-RESET.conf" },
+ { Litecell15_PrimId_SetCalibTblReq, "SET-CALIB.req" },
+ { Litecell15_PrimId_SetCalibTblCnf, "SET-CALIB.cnf" },
+ { Litecell15_PrimId_MuteRfReq, "MUTE-RF.req" },
+ { Litecell15_PrimId_MuteRfCnf, "MUTE-RF.cnf" },
+ { Litecell15_PrimId_SetRxAttenReq, "SET-RX-ATTEN.req" },
+ { Litecell15_PrimId_SetRxAttenCnf, "SET-RX-ATTEN-CNF.cnf" },
+ { 0, NULL }
+};
+
+Litecell15_PrimId_t lc15bts_get_sysprim_conf(Litecell15_PrimId_t id)
+{
+ switch (id) {
+ case Litecell15_PrimId_SystemInfoReq: return Litecell15_PrimId_SystemInfoCnf;
+ case Litecell15_PrimId_ActivateRfReq: return Litecell15_PrimId_ActivateRfCnf;
+ case Litecell15_PrimId_DeactivateRfReq: return Litecell15_PrimId_DeactivateRfCnf;
+ case Litecell15_PrimId_Layer1ResetReq: return Litecell15_PrimId_Layer1ResetCnf;
+ case Litecell15_PrimId_SetCalibTblReq: return Litecell15_PrimId_SetCalibTblCnf;
+ case Litecell15_PrimId_MuteRfReq: return Litecell15_PrimId_MuteRfCnf;
+ case Litecell15_PrimId_SetRxAttenReq: return Litecell15_PrimId_SetRxAttenCnf;
+ default: return -1; // Weak
+ }
+}
+
+const struct value_string lc15bts_l1sapi_names[GsmL1_Sapi_NUM+1] = {
+ { GsmL1_Sapi_Idle, "IDLE" },
+ { GsmL1_Sapi_Fcch, "FCCH" },
+ { GsmL1_Sapi_Sch, "SCH" },
+ { GsmL1_Sapi_Sacch, "SACCH" },
+ { GsmL1_Sapi_Sdcch, "SDCCH" },
+ { GsmL1_Sapi_Bcch, "BCCH" },
+ { GsmL1_Sapi_Pch, "PCH" },
+ { GsmL1_Sapi_Agch, "AGCH" },
+ { GsmL1_Sapi_Cbch, "CBCH" },
+ { GsmL1_Sapi_Rach, "RACH" },
+ { GsmL1_Sapi_TchF, "TCH/F" },
+ { GsmL1_Sapi_FacchF, "FACCH/F" },
+ { GsmL1_Sapi_TchH, "TCH/H" },
+ { GsmL1_Sapi_FacchH, "FACCH/H" },
+ { GsmL1_Sapi_Nch, "NCH" },
+ { GsmL1_Sapi_Pdtch, "PDTCH" },
+ { GsmL1_Sapi_Pacch, "PACCH" },
+ { GsmL1_Sapi_Pbcch, "PBCCH" },
+ { GsmL1_Sapi_Pagch, "PAGCH" },
+ { GsmL1_Sapi_Ppch, "PPCH" },
+ { GsmL1_Sapi_Pnch, "PNCH" },
+ { GsmL1_Sapi_Ptcch, "PTCCH" },
+ { GsmL1_Sapi_Prach, "PRACH" },
+ { 0, NULL }
+};
+
+const struct value_string lc15bts_l1status_names[GSML1_STATUS_NUM+1] = {
+ { GsmL1_Status_Success, "Success" },
+ { GsmL1_Status_Generic, "Generic error" },
+ { GsmL1_Status_NoMemory, "Not enough memory" },
+ { GsmL1_Status_Timeout, "Timeout" },
+ { GsmL1_Status_InvalidParam, "Invalid parameter" },
+ { GsmL1_Status_Busy, "Resource busy" },
+ { GsmL1_Status_NoRessource, "No more resources" },
+ { GsmL1_Status_Uninitialized, "Trying to use uninitialized resource" },
+ { GsmL1_Status_NullInterface, "Trying to call a NULL interface" },
+ { GsmL1_Status_NullFctnPtr, "Trying to call a NULL function ptr" },
+ { GsmL1_Status_BadCrc, "Bad CRC" },
+ { GsmL1_Status_BadUsf, "Bad USF" },
+ { GsmL1_Status_InvalidCPS, "Invalid CPS field" },
+ { GsmL1_Status_UnexpectedBurst, "Unexpected burst" },
+ { GsmL1_Status_UnavailCodec, "AMR codec is unavailable" },
+ { GsmL1_Status_CriticalError, "Critical error" },
+ { GsmL1_Status_OverheatError, "Overheat error" },
+ { GsmL1_Status_DeviceError, "Device error" },
+ { GsmL1_Status_FacchError, "FACCH / TCH order error" },
+ { GsmL1_Status_AlreadyDeactivated, "Lchan already deactivated" },
+ { GsmL1_Status_TxBurstFifoOvrn, "FIFO overrun" },
+ { GsmL1_Status_TxBurstFifoUndr, "FIFO underrun" },
+ { GsmL1_Status_NotSynchronized, "Not synchronized" },
+ { GsmL1_Status_Unsupported, "Unsupported feature" },
+ { GsmL1_Status_ClockError, "System clock error" },
+ { 0, NULL }
+};
+
+const struct value_string lc15bts_tracef_names[29] = {
+ { DBG_DEBUG, "DEBUG" },
+ { DBG_L1WARNING, "L1_WARNING" },
+ { DBG_ERROR, "ERROR" },
+ { DBG_L1RXMSG, "L1_RX_MSG" },
+ { DBG_L1RXMSGBYTE, "L1_RX_MSG_BYTE" },
+ { DBG_L1TXMSG, "L1_TX_MSG" },
+ { DBG_L1TXMSGBYTE, "L1_TX_MSG_BYTE" },
+ { DBG_MPHCNF, "MPH_CNF" },
+ { DBG_MPHIND, "MPH_IND" },
+ { DBG_MPHREQ, "MPH_REQ" },
+ { DBG_PHIND, "PH_IND" },
+ { DBG_PHREQ, "PH_REQ" },
+ { DBG_PHYRF, "PHY_RF" },
+ { DBG_PHYRFMSGBYTE, "PHY_MSG_BYTE" },
+ { DBG_MODE, "MODE" },
+ { DBG_TDMAINFO, "TDMA_INFO" },
+ { DBG_BADCRC, "BAD_CRC" },
+ { DBG_PHINDBYTE, "PH_IND_BYTE" },
+ { DBG_PHREQBYTE, "PH_REQ_BYTE" },
+ { DBG_DEVICEMSG, "DEVICE_MSG" },
+ { DBG_RACHINFO, "RACH_INFO" },
+ { DBG_LOGCHINFO, "LOG_CH_INFO" },
+ { DBG_MEMORY, "MEMORY" },
+ { DBG_PROFILING, "PROFILING" },
+ { DBG_TESTCOMMENT, "TEST_COMMENT" },
+ { DBG_TEST, "TEST" },
+ { DBG_STATUS, "STATUS" },
+ { 0, NULL }
+};
+
+const struct value_string lc15bts_tracef_docs[29] = {
+ { DBG_DEBUG, "Debug Region" },
+ { DBG_L1WARNING, "L1 Warning Region" },
+ { DBG_ERROR, "Error Region" },
+ { DBG_L1RXMSG, "L1_RX_MSG Region" },
+ { DBG_L1RXMSGBYTE, "L1_RX_MSG_BYTE Region" },
+ { DBG_L1TXMSG, "L1_TX_MSG Region" },
+ { DBG_L1TXMSGBYTE, "L1_TX_MSG_BYTE Region" },
+ { DBG_MPHCNF, "MphConfirmation Region" },
+ { DBG_MPHIND, "MphIndication Region" },
+ { DBG_MPHREQ, "MphRequest Region" },
+ { DBG_PHIND, "PhIndication Region" },
+ { DBG_PHREQ, "PhRequest Region" },
+ { DBG_PHYRF, "PhyRF Region" },
+ { DBG_PHYRFMSGBYTE, "PhyRF Message Region" },
+ { DBG_MODE, "Mode Region" },
+ { DBG_TDMAINFO, "TDMA Info Region" },
+ { DBG_BADCRC, "Bad CRC Region" },
+ { DBG_PHINDBYTE, "PH_IND_BYTE" },
+ { DBG_PHREQBYTE, "PH_REQ_BYTE" },
+ { DBG_DEVICEMSG, "Device Message Region" },
+ { DBG_RACHINFO, "RACH Info" },
+ { DBG_LOGCHINFO, "LOG_CH_INFO" },
+ { DBG_MEMORY, "Memory Region" },
+ { DBG_PROFILING, "Profiling Region" },
+ { DBG_TESTCOMMENT, "Test Comments" },
+ { DBG_TEST, "Test Region" },
+ { DBG_STATUS, "Status Region" },
+ { 0, NULL }
+};
+
+const struct value_string lc15bts_tch_pl_names[] = {
+ { GsmL1_TchPlType_NA, "N/A" },
+ { GsmL1_TchPlType_Fr, "FR" },
+ { GsmL1_TchPlType_Hr, "HR" },
+ { GsmL1_TchPlType_Efr, "EFR" },
+ { GsmL1_TchPlType_Amr, "AMR(IF2)" },
+ { GsmL1_TchPlType_Amr_SidBad, "AMR(SID BAD)" },
+ { GsmL1_TchPlType_Amr_Onset, "AMR(ONSET)" },
+ { GsmL1_TchPlType_Amr_Ratscch, "AMR(RATSCCH)" },
+ { GsmL1_TchPlType_Amr_SidUpdateInH, "AMR(SID_UPDATE INH)" },
+ { GsmL1_TchPlType_Amr_SidFirstP1, "AMR(SID_FIRST P1)" },
+ { GsmL1_TchPlType_Amr_SidFirstP2, "AMR(SID_FIRST P2)" },
+ { GsmL1_TchPlType_Amr_SidFirstInH, "AMR(SID_FIRST INH)" },
+ { GsmL1_TchPlType_Amr_RatscchMarker, "AMR(RATSCCH MARK)" },
+ { GsmL1_TchPlType_Amr_RatscchData, "AMR(RATSCCH DATA)" },
+ { 0, NULL }
+};
+
+const struct value_string lc15bts_dir_names[] = {
+ { GsmL1_Dir_TxDownlink, "TxDL" },
+ { GsmL1_Dir_TxUplink, "TxUL" },
+ { GsmL1_Dir_RxUplink, "RxUL" },
+ { GsmL1_Dir_RxDownlink, "RxDL" },
+ { GsmL1_Dir_TxDownlink|GsmL1_Dir_RxUplink, "BOTH" },
+ { 0, NULL }
+};
+
+const struct value_string lc15bts_chcomb_names[] = {
+ { GsmL1_LogChComb_0, "dummy" },
+ { GsmL1_LogChComb_I, "tch_f" },
+ { GsmL1_LogChComb_II, "tch_h" },
+ { GsmL1_LogChComb_IV, "ccch" },
+ { GsmL1_LogChComb_V, "ccch_sdcch4" },
+ { GsmL1_LogChComb_VII, "sdcch8" },
+ { GsmL1_LogChComb_XIII, "pdtch" },
+ { 0, NULL }
+};
+
+const uint8_t pdch_msu_size[_NUM_PDCH_CS] = {
+ [PDCH_CS_1] = 23,
+ [PDCH_CS_2] = 34,
+ [PDCH_CS_3] = 40,
+ [PDCH_CS_4] = 54,
+ [PDCH_MCS_1] = 27,
+ [PDCH_MCS_2] = 33,
+ [PDCH_MCS_3] = 42,
+ [PDCH_MCS_4] = 49,
+ [PDCH_MCS_5] = 60,
+ [PDCH_MCS_6] = 78,
+ [PDCH_MCS_7] = 118,
+ [PDCH_MCS_8] = 142,
+ [PDCH_MCS_9] = 154
+};
diff --git a/src/osmo-bts-litecell15/lc15bts.h b/src/osmo-bts-litecell15/lc15bts.h
new file mode 100644
index 00000000..4c40db0f
--- /dev/null
+++ b/src/osmo-bts-litecell15/lc15bts.h
@@ -0,0 +1,64 @@
+#ifndef LC15BTS_H
+#define LC15BTS_H
+
+#include <stdlib.h>
+#include <osmocom/core/utils.h>
+
+#include <nrw/litecell15/litecell15.h>
+#include <nrw/litecell15/gsml1const.h>
+
+/*
+ * Depending on the firmware version either GsmL1_Prim_t or Litecell15_Prim_t
+ * is the bigger struct. For earlier firmware versions the GsmL1_Prim_t was the
+ * bigger struct.
+ */
+#define LC15BTS_PRIM_SIZE \
+ (OSMO_MAX(sizeof(Litecell15_Prim_t), sizeof(GsmL1_Prim_t)) + 128)
+
+enum l1prim_type {
+ L1P_T_INVALID, /* this must be 0 to detect uninitialized elements */
+ L1P_T_REQ,
+ L1P_T_CONF,
+ L1P_T_IND,
+};
+
+enum l1prim_type lc15bts_get_l1prim_type(GsmL1_PrimId_t id);
+const struct value_string lc15bts_l1prim_names[GsmL1_PrimId_NUM+1];
+GsmL1_PrimId_t lc15bts_get_l1prim_conf(GsmL1_PrimId_t id);
+
+enum l1prim_type lc15bts_get_sysprim_type(Litecell15_PrimId_t id);
+const struct value_string lc15bts_sysprim_names[Litecell15_PrimId_NUM+1];
+Litecell15_PrimId_t lc15bts_get_sysprim_conf(Litecell15_PrimId_t id);
+
+const struct value_string lc15bts_l1sapi_names[GsmL1_Sapi_NUM+1];
+const struct value_string lc15bts_l1status_names[GSML1_STATUS_NUM+1];
+
+const struct value_string lc15bts_tracef_names[29];
+const struct value_string lc15bts_tracef_docs[29];
+
+const struct value_string lc15bts_tch_pl_names[15];
+
+const struct value_string lc15bts_clksrc_names[10];
+
+const struct value_string lc15bts_dir_names[6];
+
+enum pdch_cs {
+ PDCH_CS_1,
+ PDCH_CS_2,
+ PDCH_CS_3,
+ PDCH_CS_4,
+ PDCH_MCS_1,
+ PDCH_MCS_2,
+ PDCH_MCS_3,
+ PDCH_MCS_4,
+ PDCH_MCS_5,
+ PDCH_MCS_6,
+ PDCH_MCS_7,
+ PDCH_MCS_8,
+ PDCH_MCS_9,
+ _NUM_PDCH_CS
+};
+
+const uint8_t pdch_msu_size[_NUM_PDCH_CS];
+
+#endif /* LC15BTS_H */
diff --git a/src/osmo-bts-litecell15/lc15bts_vty.c b/src/osmo-bts-litecell15/lc15bts_vty.c
new file mode 100644
index 00000000..dddcbc86
--- /dev/null
+++ b/src/osmo-bts-litecell15/lc15bts_vty.c
@@ -0,0 +1,400 @@
+/* VTY interface for NuRAN Wireless Litecell 1.5 */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * 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 <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/vty.h>
+
+#include "lc15bts.h"
+#include "l1_if.h"
+#include "utils.h"
+
+
+extern int lchan_activate(struct gsm_lchan *lchan);
+extern int lchan_deactivate(struct gsm_lchan *lchan);
+
+#define TRX_STR "Transceiver related commands\n" "TRX number\n"
+
+#define SHOW_TRX_STR \
+ SHOW_STR \
+ TRX_STR
+#define DSP_TRACE_F_STR "DSP Trace Flag\n"
+
+static struct gsm_bts *vty_bts;
+
+/* configuration */
+
+DEFUN(cfg_bts_auto_band, cfg_bts_auto_band_cmd,
+ "auto-band",
+ "Automatically select band for ARFCN based on configured band\n")
+{
+ struct gsm_bts *bts = vty->index;
+ struct gsm_bts_role_bts *btsb = bts_role_bts(bts);
+
+ btsb->auto_band = 1;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_bts_no_auto_band, cfg_bts_no_auto_band_cmd,
+ "no auto-band",
+ NO_STR "Automatically select band for ARFCN based on configured band\n")
+{
+ struct gsm_bts *bts = vty->index;
+ struct gsm_bts_role_bts *btsb = bts_role_bts(bts);
+
+ btsb->auto_band = 0;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_cal_path, cfg_trx_cal_path_cmd,
+ "trx-calibration-path PATH",
+ "Set the path name to TRX calibration data\n" "Path name\n")
+{
+ struct gsm_bts_trx *trx = vty->index;
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+
+ if (fl1h->calib_path)
+ talloc_free(fl1h->calib_path);
+
+ fl1h->calib_path = talloc_strdup(fl1h, argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_min_qual_rach, cfg_trx_min_qual_rach_cmd,
+ "min-qual-rach <-100-100>",
+ "Set the minimum quality level of RACH burst to be accpeted\n"
+ "C/I level in tenth of dB\n")
+{
+ struct gsm_bts_trx *trx = vty->index;
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+
+ fl1h->min_qual_rach = strtof(argv[0], NULL) / 10.0f;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_min_qual_norm, cfg_trx_min_qual_norm_cmd,
+ "min-qual-norm <-100-100>",
+ "Set the minimum quality level of normal burst to be accpeted\n"
+ "C/I level in tenth of dB\n")
+{
+ struct gsm_bts_trx *trx = vty->index;
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+
+ fl1h->min_qual_norm = strtof(argv[0], NULL) / 10.0f;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_trx_nominal_power, cfg_trx_nominal_power_cmd,
+ "nominal-tx-power <0-100>",
+ "Set the nominal transmit output power in dBm\n"
+ "Nominal transmit output power level in dBm\n")
+{
+ struct gsm_bts_trx *trx = vty->index;
+
+ trx->nominal_power = atoi(argv[0]);
+
+ return CMD_SUCCESS;
+}
+
+/* runtime */
+
+DEFUN(show_dsp_trace_f, show_dsp_trace_f_cmd,
+ "show trx <0-0> dsp-trace-flags",
+ SHOW_TRX_STR "Display the current setting of the DSP trace flags")
+{
+ int trx_nr = atoi(argv[0]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct lc15l1_hdl *fl1h;
+ int i;
+
+ if (!trx)
+ return CMD_WARNING;
+
+ fl1h = trx_lc15l1_hdl(trx);
+
+ vty_out(vty, "Litecell15 L1 DSP trace flags:%s", VTY_NEWLINE);
+ for (i = 0; i < ARRAY_SIZE(lc15bts_tracef_names); i++) {
+ const char *endis;
+
+ if (lc15bts_tracef_names[i].value == 0 &&
+ lc15bts_tracef_names[i].str == NULL)
+ break;
+
+ if (fl1h->dsp_trace_f & lc15bts_tracef_names[i].value)
+ endis = "enabled";
+ else
+ endis = "disabled";
+
+ vty_out(vty, "DSP Trace %-15s %s%s",
+ lc15bts_tracef_names[i].str, endis,
+ VTY_NEWLINE);
+ }
+
+ return CMD_SUCCESS;
+
+}
+
+DEFUN(dsp_trace_f, dsp_trace_f_cmd, "HIDDEN", TRX_STR)
+{
+ int trx_nr = atoi(argv[0]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct lc15l1_hdl *fl1h;
+ unsigned int flag ;
+
+ if (!trx) {
+ vty_out(vty, "Cannot find TRX number %u%s",
+ trx_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ fl1h = trx_lc15l1_hdl(trx);
+ flag = get_string_value(lc15bts_tracef_names, argv[1]);
+ l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f | flag);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_dsp_trace_f, no_dsp_trace_f_cmd, "HIDDEN", NO_STR TRX_STR)
+{
+ int trx_nr = atoi(argv[0]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct lc15l1_hdl *fl1h;
+ unsigned int flag ;
+
+ if (!trx) {
+ vty_out(vty, "Cannot find TRX number %u%s",
+ trx_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ fl1h = trx_lc15l1_hdl(trx);
+ flag = get_string_value(lc15bts_tracef_names, argv[1]);
+ l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f & ~flag);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_sys_info, show_sys_info_cmd,
+ "show trx <0-0> system-information",
+ SHOW_TRX_STR "Display information about system\n")
+{
+ int trx_nr = atoi(argv[0]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct lc15l1_hdl *fl1h;
+ int i;
+
+ if (!trx) {
+ vty_out(vty, "Cannot find TRX number %u%s",
+ trx_nr, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ fl1h = trx_lc15l1_hdl(trx);
+
+ vty_out(vty, "DSP Version: %u.%u.%u, FPGA Version: %u.%u.%u%s",
+ fl1h->hw_info.dsp_version[0],
+ fl1h->hw_info.dsp_version[1],
+ fl1h->hw_info.dsp_version[2],
+ fl1h->hw_info.fpga_version[0],
+ fl1h->hw_info.fpga_version[1],
+ fl1h->hw_info.fpga_version[2], VTY_NEWLINE);
+
+ vty_out(vty, "GSM Band Support: ");
+ for (i = 0; i < sizeof(fl1h->hw_info.band_support); i++) {
+ if (fl1h->hw_info.band_support & (1 << i))
+ vty_out(vty, "%s ", gsm_band_name(1 << i));
+ }
+ vty_out(vty, "%s", VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(activate_lchan, activate_lchan_cmd,
+ "trx <0-0> <0-7> (activate|deactivate) <0-7>",
+ TRX_STR
+ "Timeslot number\n"
+ "Activate Logical Channel\n"
+ "Deactivate Logical Channel\n"
+ "Logical Channel Number\n" )
+{
+ int trx_nr = atoi(argv[0]);
+ int ts_nr = atoi(argv[1]);
+ int lchan_nr = atoi(argv[3]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+ struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
+
+ if (!strcmp(argv[2], "activate"))
+ lchan_activate(lchan);
+ else
+ lchan_deactivate(lchan);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(set_tx_power, set_tx_power_cmd,
+ "trx <0-0> tx-power <-110-100>",
+ TRX_STR
+ "Set transmit power (override BSC)\n"
+ "Transmit power in dBm\n")
+{
+ int trx_nr = atoi(argv[0]);
+ int power = atoi(argv[1]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+
+ power_ramp_start(trx, to_mdB(power), 1);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(loopback, loopback_cmd,
+ "trx <0-0> <0-7> loopback <0-1>",
+ TRX_STR
+ "Timeslot number\n"
+ "Set TCH loopback\n"
+ "Logical Channel Number\n")
+{
+ int trx_nr = atoi(argv[0]);
+ int ts_nr = atoi(argv[1]);
+ int lchan_nr = atoi(argv[2]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+ struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
+
+ lchan->loopback = 1;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(no_loopback, no_loopback_cmd,
+ "no trx <0-0> <0-7> loopback <0-1>",
+ NO_STR TRX_STR
+ "Timeslot number\n"
+ "Set TCH loopback\n"
+ "Logical Channel Number\n")
+{
+ int trx_nr = atoi(argv[0]);
+ int ts_nr = atoi(argv[1]);
+ int lchan_nr = atoi(argv[2]);
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+ struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
+
+ lchan->loopback = 0;
+
+ return CMD_SUCCESS;
+}
+
+
+void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts)
+{
+ struct gsm_bts_role_bts *btsb = bts_role_bts(bts);
+
+ if (btsb->auto_band)
+ vty_out(vty, " auto-band%s", VTY_NEWLINE);
+}
+
+void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+
+ if (fl1h->clk_use_eeprom)
+ vty_out(vty, " clock-calibration eeprom%s", VTY_NEWLINE);
+ else
+ vty_out(vty, " clock-calibration %d%s", fl1h->clk_cal,
+ VTY_NEWLINE);
+ if (fl1h->calib_path)
+ vty_out(vty, " trx-calibration-path %s%s",
+ fl1h->calib_path, VTY_NEWLINE);
+ vty_out(vty, " min-qual-rach %.0f%s", fl1h->min_qual_rach * 10.0f,
+ VTY_NEWLINE);
+ vty_out(vty, " min-qual-norm %.0f%s", fl1h->min_qual_norm * 10.0f,
+ VTY_NEWLINE);
+ if (trx->nominal_power != lc15bts_get_nominal_power(trx))
+ vty_out(vty, " nominal-tx-power %d%s", trx->nominal_power,
+ VTY_NEWLINE);
+}
+
+int bts_model_vty_init(struct gsm_bts *bts)
+{
+ vty_bts = bts;
+
+ /* runtime-patch the command strings with debug levels */
+ dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, lc15bts_tracef_names,
+ "trx <0-0> dsp-trace-flag (",
+ "|",")", VTY_DO_LOWER);
+ dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, lc15bts_tracef_docs,
+ TRX_STR DSP_TRACE_F_STR,
+ "\n", "", 0);
+
+ no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, lc15bts_tracef_names,
+ "no trx <0-0> dsp-trace-flag (",
+ "|",")", VTY_DO_LOWER);
+ no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, lc15bts_tracef_docs,
+ NO_STR TRX_STR DSP_TRACE_F_STR,
+ "\n", "", 0);
+
+ install_element_ve(&show_dsp_trace_f_cmd);
+ install_element_ve(&show_sys_info_cmd);
+ install_element_ve(&dsp_trace_f_cmd);
+ install_element_ve(&no_dsp_trace_f_cmd);
+
+ install_element(ENABLE_NODE, &activate_lchan_cmd);
+ install_element(ENABLE_NODE, &set_tx_power_cmd);
+
+ install_element(ENABLE_NODE, &loopback_cmd);
+ install_element(ENABLE_NODE, &no_loopback_cmd);
+
+ install_element(BTS_NODE, &cfg_bts_auto_band_cmd);
+ install_element(BTS_NODE, &cfg_bts_no_auto_band_cmd);
+
+ install_element(TRX_NODE, &cfg_trx_cal_path_cmd);
+ install_element(TRX_NODE, &cfg_trx_min_qual_rach_cmd);
+ install_element(TRX_NODE, &cfg_trx_min_qual_norm_cmd);
+ install_element(TRX_NODE, &cfg_trx_nominal_power_cmd);
+
+ return 0;
+}
diff --git a/src/osmo-bts-litecell15/main.c b/src/osmo-bts-litecell15/main.c
new file mode 100644
index 00000000..f3b1ea4c
--- /dev/null
+++ b/src/osmo-bts-litecell15/main.c
@@ -0,0 +1,431 @@
+/* Main program for NuRAN Wireless Litecell 1.5 BTS */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * 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 <osmocom/core/gsmtap_util.h>
+#include <osmocom/core/gsmtap.h>
+
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/abis.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/control_if.h>
+#include <osmo-bts/l1sap.h>
+
+/*NTQD: Change how rx_nr is handle in multi-trx*/
+#define LC15BTS_RF_LOCK_PATH(NR) ((((NR))==1) ? "/var/lock/bts_rf_lock1" : "/var/lock/bts_rf_lock2")
+#define LC15BTS_PID_FILE(NR) ((((NR))==1) ? "osmo-bts1" : "osmo-bts2")
+
+#include "utils.h"
+#include "l1_if.h"
+#include "hw_misc.h"
+#include "oml_router.h"
+#include "misc/lc15bts_bid.h"
+
+int pcu_direct = 0;
+
+static const char *config_file = "lc15bts.cfg";
+static int daemonize = 0;
+static unsigned int dsp_trace = 0x71c00020;
+static int rt_prio = -1;
+static char *gsmtap_ip = 0;
+static int trx_nr = 1;
+
+int bts_model_init(struct gsm_bts *bts)
+{
+ struct lc15l1_hdl *fl1h;
+ int rc;
+
+ fl1h = l1if_open(bts->c0, trx_nr);
+ if (!fl1h) {
+ LOGP(DL1C, LOGL_FATAL, "Cannot open L1 Interface\n");
+ return -EIO;
+ }
+ fl1h->dsp_trace_f = dsp_trace;
+
+ bts->c0->role_bts.l1h = fl1h;
+
+ rc = lc15bts_get_nominal_power(bts->c0);
+ if (rc < 0) {
+ LOGP(DL1C, LOGL_NOTICE, "Cannot determine nominal "
+ "transmit power. Assuming 37dBm.\n");
+ rc = 37;
+ }
+ bts->c0->nominal_power = rc;
+ bts->c0->power_params.trx_p_max_out_mdBm = to_mdB(rc);
+
+ bts_model_vty_init(bts);
+
+ return 0;
+}
+
+int bts_model_oml_estab(struct gsm_bts *bts)
+{
+ struct lc15l1_hdl *fl1h = bts->c0->role_bts.l1h;
+
+ l1if_reset(fl1h);
+
+ return 0;
+}
+
+void bts_update_status(enum bts_global_status which, int on)
+{
+ static uint64_t states = 0;
+ uint64_t old_states = states;
+ int led_rf_active_on;
+
+ if (on)
+ states |= (1ULL << which);
+ else
+ states &= ~(1ULL << which);
+
+ led_rf_active_on =
+ (states & (1ULL << BTS_STATUS_RF_ACTIVE)) &&
+ !(states & (1ULL << BTS_STATUS_RF_MUTE));
+
+ LOGP(DL1C, LOGL_INFO,
+ "Set global status #%d to %d (%04llx -> %04llx), LEDs: ACT %d\n",
+ which, on,
+ (long long)old_states, (long long)states,
+ led_rf_active_on);
+
+ lc15bts_led_set(led_rf_active_on ? LED_GREEN : LED_OFF);
+}
+
+static void print_help()
+{
+ printf( "Some useful options:\n"
+ " -h --help this text\n"
+ " -d --debug MASK Enable debugging (e.g. -d DRSL:DOML:DLAPDM)\n"
+ " -D --daemonize For the process into a background daemon\n"
+ " -c --config-file Specify the filename of the config file\n"
+ " -s --disable-color Don't use colors in stderr log output\n"
+ " -T --timestamp Prefix every log line with a timestamp\n"
+ " -V --version Print version information and exit\n"
+ " -e --log-level Set a global log-level\n"
+ " -p --dsp-trace Set DSP trace flags\n"
+ " -r --realtime PRIO Use SCHED_RR with the specified priority\n"
+ " -w --hw-version Print the targeted HW Version\n"
+ " -M --pcu-direct Force PCU to access message queue for "
+ "PDCH dchannel directly\n"
+ " -i --gsmtap-ip The destination IP used for GSMTAP.\n"
+ " -n --hw-trx-nr Hardware TRX number <1-2>\n"
+ );
+}
+
+static void print_hwversion()
+{
+ int rev;
+ int model;
+ static char model_name[64] = {0, };
+
+ snprintf(model_name, sizeof(model_name), "NuRAN Litecell 1.5 BTS");
+
+ rev = lc15bts_rev_get();
+ if (rev >= 0) {
+ snprintf(model_name, sizeof(model_name), "%s Rev %c",
+ model_name, (char)rev);
+ }
+
+ model = lc15bts_model_get();
+ if (model >= 0) {
+ snprintf(model_name, sizeof(model_name), "%s (%05X)",
+ model_name, model);
+ }
+
+ printf(model_name);
+}
+
+/* FIXME: finally get some option parsing code into libosmocore */
+static void handle_options(int argc, char **argv)
+{
+ while (1) {
+ int option_idx = 0, c;
+ static const struct option long_options[] = {
+ /* FIXME: all those are generic Osmocom app options */
+ { "help", 0, 0, 'h' },
+ { "debug", 1, 0, 'd' },
+ { "daemonize", 0, 0, 'D' },
+ { "config-file", 1, 0, 'c' },
+ { "disable-color", 0, 0, 's' },
+ { "timestamp", 0, 0, 'T' },
+ { "version", 0, 0, 'V' },
+ { "log-level", 1, 0, 'e' },
+ { "dsp-trace", 1, 0, 'p' },
+ { "hw-version", 0, 0, 'w' },
+ { "pcu-direct", 0, 0, 'M' },
+ { "realtime", 1, 0, 'r' },
+ { "gsmtap-ip", 1, 0, 'i' },
+ { "hw-trx-nr", 1, 0, 'n' },
+ { 0, 0, 0, 0 }
+ };
+
+ c = getopt_long(argc, argv, "hc:d:Dc:sTVe:p:w:Mr:n:",
+ long_options, &option_idx);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ print_help();
+ exit(0);
+ break;
+ case 's':
+ log_set_use_color(osmo_stderr_target, 0);
+ break;
+ case 'd':
+ log_parse_category_mask(osmo_stderr_target, optarg);
+ break;
+ case 'D':
+ daemonize = 1;
+ break;
+ case 'c':
+ config_file = optarg;
+ break;
+ case 'T':
+ log_set_print_timestamp(osmo_stderr_target, 1);
+ break;
+ case 'M':
+ pcu_direct = 1;
+ break;
+ case 'V':
+ print_version(1);
+ exit(0);
+ break;
+ case 'e':
+ log_set_log_level(osmo_stderr_target, atoi(optarg));
+ break;
+ case 'p':
+ dsp_trace = strtoul(optarg, NULL, 16);
+ break;
+ case 'w':
+ print_hwversion();
+ exit(0);
+ break;
+ case 'r':
+ rt_prio = atoi(optarg);
+ break;
+ case 'i':
+ gsmtap_ip = optarg;
+ break;
+ case 'n':
+ trx_nr = atoi(optarg);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+static struct gsm_bts *bts;
+
+static void signal_handler(int signal)
+{
+ fprintf(stderr, "signal %u received\n", signal);
+
+ switch (signal) {
+ case SIGINT:
+ //osmo_signal_dispatch(SS_GLOBAL, S_GLOBAL_SHUTDOWN, NULL);
+ bts_shutdown(bts, "SIGINT");
+ break;
+ case SIGABRT:
+ case SIGUSR1:
+ case SIGUSR2:
+ talloc_report_full(tall_bts_ctx, stderr);
+ break;
+ default:
+ break;
+ }
+}
+
+static int write_pid_file(char *procname)
+{
+ FILE *outf;
+ char tmp[PATH_MAX+1];
+
+ snprintf(tmp, sizeof(tmp)-1, "/var/run/%s.pid", procname);
+ tmp[PATH_MAX-1] = '\0';
+
+ outf = fopen(tmp, "w");
+ if (!outf)
+ return -1;
+
+ fprintf(outf, "%d\n", getpid());
+
+ fclose(outf);
+
+ return 0;
+}
+
+extern int lc15bts_ctrlif_inst_cmds(void);
+
+int main(int argc, char **argv)
+{
+ struct stat st;
+ struct sched_param param;
+ struct gsm_bts_role_bts *btsb;
+ struct e1inp_line *line;
+ void *tall_msgb_ctx;
+ struct osmo_fd accept_fd, read_fd;
+ int vty_port;
+ int rc;
+
+
+ tall_bts_ctx = talloc_named_const(NULL, 1, "lc15BTS context");
+ tall_msgb_ctx = talloc_named_const(tall_bts_ctx, 1, "msgb");
+ msgb_set_talloc_ctx(tall_msgb_ctx);
+
+ bts_log_init(NULL);
+
+ bts = gsm_bts_alloc(tall_bts_ctx);
+ vty_init(&bts_vty_info);
+ e1inp_vty_init();
+ bts_vty_init(bts, &bts_log_info);
+
+ handle_options(argc, argv);
+
+ /* enable realtime priority for us */
+ if (rt_prio != -1) {
+ memset(&param, 0, sizeof(param));
+ param.sched_priority = rt_prio;
+ rc = sched_setscheduler(getpid(), SCHED_RR, &param);
+ if (rc != 0) {
+ fprintf(stderr, "Setting SCHED_RR priority(%d) failed: %s\n",
+ param.sched_priority, strerror(errno));
+ exit(1);
+ }
+ }
+
+ if (gsmtap_ip) {
+ gsmtap = gsmtap_source_init(gsmtap_ip, GSMTAP_UDP_PORT, 1);
+ if (!gsmtap) {
+ fprintf(stderr, "Failed during gsmtap_init()\n");
+ exit(1);
+ }
+ gsmtap_source_add_sink(gsmtap);
+ }
+
+ if (bts_init(bts) < 0) {
+ fprintf(stderr, "unable to open bts\n");
+ exit(1);
+ }
+ btsb = bts_role_bts(bts);
+ btsb->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3);
+
+ abis_init(bts);
+
+ rc = vty_read_config_file(config_file, NULL);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to parse the config file: '%s'\n",
+ config_file);
+ exit(1);
+ }
+
+ if (stat(LC15BTS_RF_LOCK_PATH(trx_nr), &st) == 0) {
+ LOGP(DL1C, LOGL_NOTICE, "Not starting BTS due to RF_LOCK file present\n");
+ exit(23);
+ }
+ write_pid_file(LC15BTS_PID_FILE(trx_nr));
+
+ bts_controlif_setup(bts);
+
+ vty_port = (trx_nr == 1) ? OSMO_VTY_PORT_BTS : (OSMO_VTY_PORT_BTS + 1000);
+ rc = telnet_init(tall_bts_ctx, NULL, vty_port);
+ if (rc < 0) {
+ fprintf(stderr, "Error initializing telnet\n");
+ exit(1);
+ }
+
+ /* NTQD: For testing, we only support PCU on the first TRX */
+ if (trx_nr == 1) {
+ if (pcu_sock_init()) {
+ fprintf(stderr, "PCU L1 socket failed\n");
+ exit(1);
+ }
+ }
+
+ signal(SIGINT, &signal_handler);
+ //signal(SIGABRT, &signal_handler);
+ signal(SIGUSR1, &signal_handler);
+ signal(SIGUSR2, &signal_handler);
+ osmo_init_ignore_signals();
+
+ rc = oml_router_init(bts, OML_ROUTER_PATH, &accept_fd, &read_fd);
+ if (rc < 0) {
+ fprintf(stderr, "Error creating the OML router: %s rc=%d\n",
+ OML_ROUTER_PATH, rc);
+ exit(1);
+ }
+
+ if (!btsb->bsc_oml_host) {
+ fprintf(stderr, "Cannot start BTS without knowing BSC OML IP\n");
+ exit(1);
+ }
+
+ line = abis_open(bts, btsb->bsc_oml_host, "lc15BTS");
+ if (!line) {
+ fprintf(stderr, "unable to connect to BSC\n");
+ exit(2);
+ }
+
+ if (daemonize) {
+ rc = osmo_daemonize();
+ if (rc < 0) {
+ perror("Error during daemonize");
+ exit(1);
+ }
+ }
+
+ while (1) {
+ log_reset_context();
+ osmo_select_main(0);
+ }
+}
+
+void bts_model_abis_close(struct gsm_bts *bts)
+{
+ /* for now, we simply terminate the program and re-spawn */
+ bts_shutdown(bts, "Abis close");
+}
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_bid.c b/src/osmo-bts-litecell15/misc/lc15bts_bid.c
new file mode 100644
index 00000000..1fb58514
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_bid.c
@@ -0,0 +1,139 @@
+/* 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 "lc15bts_bid.h"
+
+#define BOARD_REV_SYSFS "/sys/devices/0.lc15/revision"
+#define BOARD_OPT_SYSFS "/sys/devices/0.lc15/option"
+
+static const int option_type_mask[_NUM_OPTION_TYPES] = {
+ [LC15BTS_OPTION_OCXO] = 0x07,
+ [LC15BTS_OPTION_FPGA] = 0x03,
+ [LC15BTS_OPTION_PA] = 0x01,
+ [LC15BTS_OPTION_BAND] = 0x03,
+ [LC15BTS_OPTION_TX_ISO_BYP] = 0x01,
+ [LC15BTS_OPTION_RX_DUP_BYP] = 0x01,
+ [LC15BTS_OPTION_RX_PB_BYP] = 0x01,
+ [LC15BTS_OPTION_RX_DIV] = 0x01,
+ [LC15BTS_OPTION_RX1A] = 0x01,
+ [LC15BTS_OPTION_RX1B] = 0x01,
+ [LC15BTS_OPTION_RX2A] = 0x01,
+ [LC15BTS_OPTION_RX2B] = 0x01,
+ [LC15BTS_OPTION_DDR_32B] = 0x01,
+ [LC15BTS_OPTION_DDR_ECC] = 0x01,
+ [LC15BTS_OPTION_LOG_DET] = 0x01,
+ [LC15BTS_OPTION_DUAL_LOG_DET] = 0x01,
+};
+
+static const int option_type_shift[_NUM_OPTION_TYPES] = {
+ [LC15BTS_OPTION_OCXO] = 0,
+ [LC15BTS_OPTION_FPGA] = 3,
+ [LC15BTS_OPTION_PA] = 5,
+ [LC15BTS_OPTION_BAND] = 6,
+ [LC15BTS_OPTION_TX_ISO_BYP] = 8,
+ [LC15BTS_OPTION_RX_DUP_BYP] = 9,
+ [LC15BTS_OPTION_RX_PB_BYP] = 10,
+ [LC15BTS_OPTION_RX_DIV] = 11,
+ [LC15BTS_OPTION_RX1A] = 12,
+ [LC15BTS_OPTION_RX1B] = 13,
+ [LC15BTS_OPTION_RX2A] = 14,
+ [LC15BTS_OPTION_RX2B] = 15,
+ [LC15BTS_OPTION_DDR_32B] = 16,
+ [LC15BTS_OPTION_DDR_ECC] = 17,
+ [LC15BTS_OPTION_LOG_DET] = 18,
+ [LC15BTS_OPTION_DUAL_LOG_DET] = 19,
+};
+
+
+static int board_rev = -1;
+static int board_option = -1;
+
+
+int lc15bts_rev_get(void)
+{
+ FILE *fp;
+ char rev;
+
+ if (board_rev != -1) {
+ return board_rev;
+ }
+
+ fp = fopen(BOARD_REV_SYSFS, "r");
+ if (fp == NULL) return -1;
+
+ if (fscanf(fp, "%c", &rev) != 1) {
+ fclose( fp );
+ return -1;
+ }
+ fclose(fp);
+
+ board_rev = rev;
+ return board_rev;
+}
+
+int lc15bts_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 lc15bts_option_get(enum lc15bts_option_type type)
+{
+ int rc;
+ int option;
+
+ if (type >= _NUM_OPTION_TYPES) {
+ return -EINVAL;
+ }
+
+ if (board_option == -1) {
+ rc = lc15bts_model_get();
+ if (rc < 0) return rc;
+ }
+
+ option = (board_option >> option_type_shift[type])
+ & option_type_mask[type];
+
+ return option;
+}
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_bid.h b/src/osmo-bts-litecell15/misc/lc15bts_bid.h
new file mode 100644
index 00000000..b320e117
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_bid.h
@@ -0,0 +1,51 @@
+#ifndef _LC15BTS_BOARD_H
+#define _LC15BTS_BOARD_H
+
+#include <stdint.h>
+
+enum lc15bts_option_type {
+ LC15BTS_OPTION_OCXO,
+ LC15BTS_OPTION_FPGA,
+ LC15BTS_OPTION_PA,
+ LC15BTS_OPTION_BAND,
+ LC15BTS_OPTION_TX_ISO_BYP,
+ LC15BTS_OPTION_RX_DUP_BYP,
+ LC15BTS_OPTION_RX_PB_BYP,
+ LC15BTS_OPTION_RX_DIV,
+ LC15BTS_OPTION_RX1A,
+ LC15BTS_OPTION_RX1B,
+ LC15BTS_OPTION_RX2A,
+ LC15BTS_OPTION_RX2B,
+ LC15BTS_OPTION_DDR_32B,
+ LC15BTS_OPTION_DDR_ECC,
+ LC15BTS_OPTION_LOG_DET,
+ LC15BTS_OPTION_DUAL_LOG_DET,
+ _NUM_OPTION_TYPES
+};
+
+enum lc15bts_ocxo_type {
+ LC15BTS_OCXO_BILAY_NVG45AV2072,
+ LC15BTS_OCXO_TAITIEN_NJ26M003,
+ _NUM_OCXO_TYPES
+};
+
+enum lc15bts_fpga_type {
+ LC15BTS_FPGA_35T,
+ LC15BTS_FPGA_50T,
+ LC15BTS_FPGA_75T,
+ LC15BTS_FPGA_100T,
+ _NUM_FPGA_TYPES
+};
+
+enum lc15bts_gsm_band {
+ LC15BTS_BAND_850,
+ LC15BTS_BAND_900,
+ LC15BTS_BAND_1800,
+ LC15BTS_BAND_1900,
+};
+
+int lc15bts_rev_get(void);
+int lc15bts_model_get(void);
+int lc15bts_option_get(enum lc15bts_option_type type);
+
+#endif
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_clock.c b/src/osmo-bts-litecell15/misc/lc15bts_clock.c
new file mode 100644
index 00000000..90ecb7a8
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_clock.c
@@ -0,0 +1,283 @@
+/* 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 "lc15bts_clock.h"
+
+#define CLKERR_ERR_SYSFS "/sys/devices/5002000.clkerr/clkerr1_average"
+#define CLKERR_ACC_SYSFS "/sys/devices/5002000.clkerr/clkerr1_average_accuracy"
+#define CLKERR_INT_SYSFS "/sys/devices/5002000.clkerr/clkerr1_average_interval"
+#define CLKERR_FLT_SYSFS "/sys/devices/5002000.clkerr/clkerr1_fault"
+#define CLKERR_RFS_SYSFS "/sys/devices/5002000.clkerr/refresh"
+#define CLKERR_RST_SYSFS "/sys/devices/5002000.clkerr/reset"
+
+#define OCXODAC_VAL_SYSFS "/sys/bus/iio/devices/iio:device0/out_voltage0_raw"
+#define OCXODAC_ROM_SYSFS "/sys/bus/iio/devices/iio:device0/store_eeprom"
+
+/* clock error */
+static int clkerr_fd_err = -1;
+static int clkerr_fd_accuracy = -1;
+static int clkerr_fd_interval = -1;
+static int clkerr_fd_fault = -1;
+static int clkerr_fd_refresh = -1;
+static int clkerr_fd_reset = -1;
+
+/* ocxo dac */
+static int ocxodac_fd_value = -1;
+static int ocxodac_fd_save = -1;
+
+
+static int sysfs_read_val(int fd, int *val)
+{
+ int rc;
+ char szVal[32] = {0};
+
+ lseek( fd, 0, SEEK_SET );
+
+ rc = read(fd, szVal, sizeof(szVal) - 1);
+ if (rc < 0) {
+ return -errno;
+ }
+
+ rc = sscanf(szVal, "%d", val);
+ if (rc != 1) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int sysfs_write_val(int fd, int val)
+{
+ int n, rc;
+ char szVal[32] = {0};
+
+ n = sprintf(szVal, "%d", val);
+
+ lseek(fd, 0, SEEK_SET);
+ rc = write(fd, szVal, n+1);
+ if (rc < 0) {
+ return -errno;
+ }
+ return 0;
+}
+
+static int sysfs_write_str(int fd, const char *str)
+{
+ int rc;
+
+ lseek( fd, 0, SEEK_SET );
+ rc = write(fd, str, strlen(str)+1);
+ if (rc < 0) {
+ return -errno;
+ }
+ return 0;
+}
+
+
+int lc15bts_clock_err_open(void)
+{
+ int rc;
+ int fault;
+
+ if (clkerr_fd_err < 0) {
+ clkerr_fd_err = open(CLKERR_ERR_SYSFS, O_RDONLY);
+ if (clkerr_fd_err < 0) {
+ lc15bts_clock_err_close();
+ return clkerr_fd_err;
+ }
+ }
+
+ if (clkerr_fd_accuracy < 0) {
+ clkerr_fd_accuracy = open(CLKERR_ACC_SYSFS, O_RDONLY);
+ if (clkerr_fd_accuracy < 0) {
+ lc15bts_clock_err_close();
+ return clkerr_fd_accuracy;
+ }
+ }
+
+ if (clkerr_fd_interval < 0) {
+ clkerr_fd_interval = open(CLKERR_INT_SYSFS, O_RDONLY);
+ if (clkerr_fd_interval < 0) {
+ lc15bts_clock_err_close();
+ return clkerr_fd_interval;
+ }
+ }
+
+ if (clkerr_fd_fault < 0) {
+ clkerr_fd_fault = open(CLKERR_FLT_SYSFS, O_RDONLY);
+ if (clkerr_fd_fault < 0) {
+ lc15bts_clock_err_close();
+ return clkerr_fd_fault;
+ }
+ }
+
+ if (clkerr_fd_refresh < 0) {
+ clkerr_fd_refresh = open(CLKERR_RFS_SYSFS, O_WRONLY);
+ if (clkerr_fd_refresh < 0) {
+ lc15bts_clock_err_close();
+ return clkerr_fd_refresh;
+ }
+ }
+
+ if (clkerr_fd_reset < 0) {
+ clkerr_fd_reset = open(CLKERR_RST_SYSFS, O_WRONLY);
+ if (clkerr_fd_reset < 0) {
+ lc15bts_clock_err_close();
+ return clkerr_fd_reset;
+ }
+ }
+
+ rc = sysfs_write_str(clkerr_fd_refresh, "once");
+ if (rc < 0) {
+ lc15bts_clock_err_close();
+ return rc;
+ }
+
+ rc = sysfs_read_val(clkerr_fd_fault, &fault);
+ if (rc < 0) {
+ lc15bts_clock_err_close();
+ return rc;
+ }
+
+ if (fault) {
+ rc = sysfs_write_val(clkerr_fd_reset, 1);
+ if (rc < 0) {
+ lc15bts_clock_err_close();
+ return rc;
+ }
+ }
+ return 0;
+}
+
+void lc15bts_clock_err_close(void)
+{
+ if (clkerr_fd_err >= 0) {
+ close(clkerr_fd_err);
+ clkerr_fd_err = -1;
+ }
+
+ if (clkerr_fd_accuracy >= 0) {
+ close(clkerr_fd_accuracy);
+ clkerr_fd_accuracy = -1;
+ }
+
+ if (clkerr_fd_interval >= 0) {
+ close(clkerr_fd_interval);
+ clkerr_fd_interval = -1;
+ }
+
+ if (clkerr_fd_fault >= 0) {
+ close(clkerr_fd_fault);
+ clkerr_fd_fault = -1;
+ }
+
+ if (clkerr_fd_refresh >= 0) {
+ close(clkerr_fd_refresh);
+ clkerr_fd_refresh = -1;
+ }
+
+ if (clkerr_fd_reset >= 0) {
+ close(clkerr_fd_reset);
+ clkerr_fd_reset = -1;
+ }
+}
+
+int lc15bts_clock_err_reset(void)
+{
+ return sysfs_write_val(clkerr_fd_reset, 1);
+}
+
+int lc15bts_clock_err_get(int *fault, int *error_ppt,
+ int *accuracy_ppq, int *interval_sec)
+{
+ int rc;
+
+ rc = sysfs_write_str(clkerr_fd_refresh, "once");
+ if (rc < 0) {
+ return -1;
+ }
+
+ rc = sysfs_read_val(clkerr_fd_fault, fault);
+ rc |= sysfs_read_val(clkerr_fd_err, error_ppt);
+ rc |= sysfs_read_val(clkerr_fd_accuracy, accuracy_ppq);
+ rc |= sysfs_read_val(clkerr_fd_interval, interval_sec);
+ if (rc) {
+ return -1;
+ }
+ return 0;
+}
+
+
+int lc15bts_clock_dac_open(void)
+{
+ if (ocxodac_fd_value < 0) {
+ ocxodac_fd_value = open(OCXODAC_VAL_SYSFS, O_RDWR);
+ if (ocxodac_fd_value < 0) {
+ lc15bts_clock_dac_close();
+ return ocxodac_fd_value;
+ }
+ }
+
+ if (ocxodac_fd_save < 0) {
+ ocxodac_fd_save = open(OCXODAC_ROM_SYSFS, O_WRONLY);
+ if (ocxodac_fd_save < 0) {
+ lc15bts_clock_dac_close();
+ return ocxodac_fd_save;
+ }
+ }
+ return 0;
+}
+
+void lc15bts_clock_dac_close(void)
+{
+ if (ocxodac_fd_value >= 0) {
+ close(ocxodac_fd_value);
+ ocxodac_fd_value = -1;
+ }
+
+ if (ocxodac_fd_save >= 0) {
+ close(ocxodac_fd_save);
+ ocxodac_fd_save = -1;
+ }
+}
+
+int lc15bts_clock_dac_get(int *dac_value)
+{
+ return sysfs_read_val(ocxodac_fd_value, dac_value);
+}
+
+int lc15bts_clock_dac_set(int dac_value)
+{
+ return sysfs_write_val(ocxodac_fd_value, dac_value);
+}
+
+int lc15bts_clock_dac_save(void)
+{
+ return sysfs_write_val(ocxodac_fd_save, 1);
+}
+
+
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_clock.h b/src/osmo-bts-litecell15/misc/lc15bts_clock.h
new file mode 100644
index 00000000..d9673598
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_clock.h
@@ -0,0 +1,16 @@
+#ifndef _LC15BTS_CLOCK_H
+#define _LC15BTS_CLOCK_H
+
+int lc15bts_clock_err_open(void);
+void lc15bts_clock_err_close(void);
+int lc15bts_clock_err_reset(void);
+int lc15bts_clock_err_get(int *fault, int *error_ppt,
+ int *accuracy_ppq, int *interval_sec);
+
+int lc15bts_clock_dac_open(void);
+void lc15bts_clock_dac_close(void);
+int lc15bts_clock_dac_get(int *dac_value);
+int lc15bts_clock_dac_set(int dac_value);
+int lc15bts_clock_dac_save(void);
+
+#endif
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr.c b/src/osmo-bts-litecell15/misc/lc15bts_mgr.c
new file mode 100644
index 00000000..a4c5650b
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_mgr.c
@@ -0,0 +1,297 @@
+/* Main program for NuRAN Wireless Litecell 1.5 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/lc15bts_misc.h"
+#include "misc/lc15bts_mgr.h"
+#include "misc/lc15bts_par.h"
+#include "misc/lc15bts_bid.h"
+#include "misc/lc15bts_power.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 TEMP_TIMER_SECS (6 * 3600)
+
+/* every 1 hours means 365*24 = 8760 rom writes per year (max) */
+#define HOURS_TIMER_SECS (1 * 3600)
+
+
+/* the initial state */
+static struct lc15bts_mgr_instance manager = {
+ .config_file = "lc15bts-mgr.cfg",
+ .temp = {
+ .supply_limit = {
+ .thresh_warn = 60,
+ .thresh_crit = 78,
+ },
+ .soc_limit = {
+ .thresh_warn = 60,
+ .thresh_crit = 78,
+ },
+ .fpga_limit = {
+ .thresh_warn = 60,
+ .thresh_crit = 78,
+ },
+ .memory_limit = {
+ .thresh_warn = 60,
+ .thresh_crit = 78,
+ },
+ .tx1_limit = {
+ .thresh_warn = 60,
+ .thresh_crit = 78,
+ },
+ .tx2_limit = {
+ .thresh_warn = 60,
+ .thresh_crit = 78,
+ },
+ .pa1_limit = {
+ .thresh_warn = 60,
+ .thresh_crit = 78,
+ },
+ .pa2_limit = {
+ .thresh_warn = 60,
+ .thresh_crit = 78,
+ },
+ .action_warn = 0,
+ .action_crit = TEMP_ACT_PA1_OFF | TEMP_ACT_PA2_OFF,
+ .state = STATE_NORMAL,
+ }
+};
+
+static struct osmo_timer_list temp_timer;
+static void check_temp_timer_cb(void *unused)
+{
+ lc15bts_check_temp(no_rom_write);
+
+ osmo_timer_schedule(&temp_timer, TEMP_TIMER_SECS, 0);
+}
+
+static struct osmo_timer_list hours_timer;
+static void hours_timer_cb(void *unused)
+{
+ lc15bts_update_hours(no_rom_write);
+
+ osmo_timer_schedule(&hours_timer, HOURS_TIMER_SECS, 0);
+}
+
+static void print_help(void)
+{
+ printf("lc15bts-mgr [-nsD] [-d cat]\n");
+ printf(" -n Do not write to ROM\n");
+ printf(" -s Disable color\n");
+ printf(" -d CAT enable debugging\n");
+ printf(" -D daemonize\n");
+ printf(" -c Specify the filename of the config file\n");
+}
+
+static int parse_options(int argc, char **argv)
+{
+ int opt;
+
+ while ((opt = getopt(argc, argv, "nhsd:c:")) != -1) {
+ switch (opt) {
+ case 'n':
+ no_rom_write = 1;
+ break;
+ case 'h':
+ print_help();
+ return -1;
+ case 's':
+ log_set_use_color(osmo_stderr_target, 0);
+ break;
+ case 'd':
+ log_parse_category_mask(osmo_stderr_target, optarg);
+ break;
+ case 'D':
+ daemonize = 1;
+ break;
+ case 'c':
+ manager.config_file = optarg;
+ break;
+ default:
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void signal_handler(int signal)
+{
+ fprintf(stderr, "signal %u received\n", signal);
+
+ switch (signal) {
+ case SIGINT:
+ lc15bts_check_temp(no_rom_write);
+ lc15bts_update_hours(no_rom_write);
+ exit(0);
+ break;
+ case SIGABRT:
+ case SIGUSR1:
+ case SIGUSR2:
+ talloc_report_full(tall_mgr_ctx, stderr);
+ break;
+ default:
+ break;
+ }
+}
+
+static struct log_info_cat mgr_log_info_cat[] = {
+ [DTEMP] = {
+ .name = "DTEMP",
+ .description = "Temperature monitoring",
+ .color = "\033[1;35m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DFW] = {
+ .name = "DFW",
+ .description = "Firmware management",
+ .color = "\033[1;36m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DFIND] = {
+ .name = "DFIND",
+ .description = "ipaccess-find handling",
+ .color = "\033[1;37m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DCALIB] = {
+ .name = "DCALIB",
+ .description = "Calibration handling",
+ .color = "\033[1;37m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+};
+
+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;
+
+
+ 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);
+
+ lc15bts_mgr_vty_init();
+ logging_vty_add_cmds(&mgr_log_info);
+ rc = lc15bts_mgr_parse_config(&manager);
+ if (rc < 0) {
+ LOGP(DFIND, LOGL_FATAL, "Cannot parse config file\n");
+ exit(1);
+ }
+
+ rc = telnet_init(tall_msgb_ctx, NULL, OSMO_VTY_PORT_BTSMGR);
+ if (rc < 0) {
+ fprintf(stderr, "Error initializing telnet\n");
+ exit(1);
+ }
+
+ /* start temperature check timer */
+ temp_timer.cb = check_temp_timer_cb;
+ check_temp_timer_cb(NULL);
+
+ /* start operational hours timer */
+ hours_timer.cb = hours_timer_cb;
+ hours_timer_cb(NULL);
+
+ /* Enable the PAs */
+ rc = lc15bts_power_set(LC15BTS_POWER_PA1, 1);
+ if (rc < 0) {
+ exit(3);
+ }
+
+ rc = lc15bts_power_set(LC15BTS_POWER_PA2, 1);
+ if (rc < 0) {
+ exit(3);
+ }
+
+
+ /* handle broadcast messages for ipaccess-find */
+ if (lc15bts_mgr_nl_init() != 0)
+ exit(3);
+
+ /* Initialize the temperature control */
+ lc15bts_mgr_temp_init(&manager);
+
+ if (lc15bts_mgr_calib_init(&manager) != 0)
+ exit(3);
+
+ if (daemonize) {
+ rc = osmo_daemonize();
+ if (rc < 0) {
+ perror("Error during daemonize");
+ exit(1);
+ }
+ }
+
+
+ while (1) {
+ log_reset_context();
+ osmo_select_main(0);
+ }
+}
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr.h b/src/osmo-bts-litecell15/misc/lc15bts_mgr.h
new file mode 100644
index 00000000..466d0b26
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_mgr.h
@@ -0,0 +1,111 @@
+#ifndef _LC15BTS_MGR_H
+#define _LC15BTS_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>
+
+enum {
+ DTEMP,
+ DFW,
+ DFIND,
+ DCALIB,
+};
+
+// TODO NTQD: Define new actions like reducing output power, limit ARM core speed, shutdown second TRX/PA, ...
+enum {
+#if 0
+ TEMP_ACT_PWR_CONTRL = 0x1,
+#endif
+ TEMP_ACT_PA1_OFF = 0x2,
+ TEMP_ACT_PA2_OFF = 0x4,
+ TEMP_ACT_BTS_SRV_OFF = 0x10,
+};
+
+/* actions only for normal state */
+enum {
+#if 0
+ TEMP_ACT_NORM_PW_CONTRL = 0x1,
+#endif
+ TEMP_ACT_NORM_PA1_ON = 0x2,
+ TEMP_ACT_NORM_PA2_ON = 0x4,
+ TEMP_ACT_NORM_BTS_SRV_ON= 0x10,
+};
+
+enum lc15bts_temp_state {
+ STATE_NORMAL, /* Everything is fine */
+ STATE_WARNING_HYST, /* Go back to normal next? */
+ STATE_WARNING, /* We are above the warning threshold */
+ STATE_CRITICAL, /* We have an issue. Wait for below warning */
+};
+
+/**
+ * Temperature Limits. We separate from a threshold
+ * that will generate a warning and one that is so
+ * severe that an action will be taken.
+ */
+struct lc15bts_temp_limit {
+ int thresh_warn;
+ int thresh_crit;
+};
+
+enum mgr_vty_node {
+ MGR_NODE = _LAST_OSMOVTY_NODE + 1,
+
+ ACT_NORM_NODE,
+ ACT_WARN_NODE,
+ ACT_CRIT_NODE,
+ LIMIT_SUPPLY_NODE,
+ LIMIT_SOC_NODE,
+ LIMIT_FPGA_NODE,
+ LIMIT_MEMORY_NODE,
+ LIMIT_TX1_NODE,
+ LIMIT_TX2_NODE,
+ LIMIT_PA1_NODE,
+ LIMIT_PA2_NODE,
+};
+
+struct lc15bts_mgr_instance {
+ const char *config_file;
+
+ struct {
+ int action_norm;
+ int action_warn;
+ int action_crit;
+
+ enum lc15bts_temp_state state;
+
+ struct lc15bts_temp_limit supply_limit;
+ struct lc15bts_temp_limit soc_limit;
+ struct lc15bts_temp_limit fpga_limit;
+ struct lc15bts_temp_limit memory_limit;
+ struct lc15bts_temp_limit tx1_limit;
+ struct lc15bts_temp_limit tx2_limit;
+ struct lc15bts_temp_limit pa1_limit;
+ struct lc15bts_temp_limit pa2_limit;
+ } temp;
+
+ struct {
+ int state;
+ int calib_from_loop;
+ struct osmo_timer_list calib_timeout;
+ } calib;
+};
+
+int lc15bts_mgr_vty_init(void);
+int lc15bts_mgr_parse_config(struct lc15bts_mgr_instance *mgr);
+int lc15bts_mgr_nl_init(void);
+int lc15bts_mgr_temp_init(struct lc15bts_mgr_instance *mgr);
+const char *lc15bts_mgr_temp_get_state(enum lc15bts_temp_state state);
+
+
+int lc15bts_mgr_calib_init(struct lc15bts_mgr_instance *mgr);
+int lc15bts_mgr_calib_run(struct lc15bts_mgr_instance *mgr);
+
+extern void *tall_mgr_ctx;
+
+#endif
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr_calib.c b/src/osmo-bts-litecell15/misc/lc15bts_mgr_calib.c
new file mode 100644
index 00000000..fb494770
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_mgr_calib.c
@@ -0,0 +1,246 @@
+/* OCXO calibration control for Litecell 1.5 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/lc15bts_mgr.h"
+#include "misc/lc15bts_misc.h"
+#include "misc/lc15bts_clock.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/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>
+
+static void calib_adjust(struct lc15bts_mgr_instance *mgr);
+static void calib_state_reset(struct lc15bts_mgr_instance *mgr, int reason);
+static void calib_loop_run(void *_data);
+
+enum calib_state {
+ CALIB_INITIAL,
+ CALIB_IN_PROGRESS,
+};
+
+enum calib_result {
+ CALIB_FAIL_START,
+ CALIB_FAIL_GPSFIX,
+ CALIB_FAIL_CLKERR,
+ CALIB_FAIL_OCXODAC,
+ CALIB_SUCCESS,
+};
+
+static void calib_start(struct lc15bts_mgr_instance *mgr)
+{
+ int rc;
+
+ rc = lc15bts_clock_err_open();
+ if (rc != 0) {
+ LOGP(DCALIB, LOGL_ERROR, "Failed to open clock error module %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_CLKERR);
+ return;
+ }
+
+ rc = lc15bts_clock_dac_open();
+ if (rc != 0) {
+ LOGP(DCALIB, LOGL_ERROR, "Failed to open OCXO dac module %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_OCXODAC);
+ return;
+ }
+
+ calib_adjust(mgr);
+}
+
+static void calib_adjust(struct lc15bts_mgr_instance *mgr)
+{
+ int rc;
+ int fault;
+ int error_ppt;
+ int accuracy_ppq;
+ int interval_sec;
+ int dac_value;
+ int new_dac_value;
+ double dac_correction;
+
+ rc = lc15bts_clock_err_get(&fault, &error_ppt,
+ &accuracy_ppq, &interval_sec);
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to get clock error measurement %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_CLKERR);
+ return;
+ }
+
+ if (fault) {
+ LOGP(DCALIB, LOGL_NOTICE, "GPS has no fix\n");
+ calib_state_reset(mgr, CALIB_FAIL_GPSFIX);
+ return;
+ }
+
+ rc = lc15bts_clock_dac_get(&dac_value);
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to get OCXO dac value %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_OCXODAC);
+ return;
+ }
+
+ LOGP(DCALIB, LOGL_NOTICE,
+ "Calibration ERR(%f PPB) ACC(%f PPB) INT(%d) DAC(%d)\n",
+ error_ppt / 1000., accuracy_ppq / 1000000., interval_sec, dac_value);
+
+ /* 1 unit of correction equal about 0.5 - 1 PPB correction */
+ dac_correction = (int)(-error_ppt * 0.00056);
+ new_dac_value = dac_value + dac_correction + 0.5;
+
+ /* 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)) {
+
+ if (new_dac_value > 4095)
+ dac_value = 4095;
+ else if (new_dac_value < 0)
+ dac_value = 0;
+ else
+ dac_value = new_dac_value;
+
+ LOGP(DCALIB, LOGL_NOTICE,
+ "Going to apply %d as new clock setting.\n",
+ dac_value);
+
+ rc = lc15bts_clock_dac_set(dac_value);
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to set OCXO dac value %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_OCXODAC);
+ return;
+ }
+ rc = lc15bts_clock_err_reset();
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to set reset clock error module %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_CLKERR);
+ return;
+ }
+ }
+
+ /* Save the correction value in the DAC eeprom if the
+ frequency has been stable for 24 hours */
+ else if (interval_sec >= (24 * 60 * 60)) {
+ rc = lc15bts_clock_dac_save();
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to save OCXO dac value %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_OCXODAC);
+ }
+ rc = lc15bts_clock_err_reset();
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to set reste 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 lc15bts_mgr_instance *mgr)
+{
+ lc15bts_clock_err_close();
+ lc15bts_clock_dac_close();
+}
+
+static void calib_state_reset(struct lc15bts_mgr_instance *mgr, int outcome)
+{
+ if (mgr->calib.calib_from_loop) {
+ /*
+ * In case of success calibrate in two hours again
+ * and in case of a failure in some minutes.
+ *
+ * TODO NTQ: Select timeout based on last error and accuracy
+ */
+ int timeout = 60;
+ //int timeout = 2 * 60 * 60;
+ //if (outcome != CALIB_SUCESS) }
+ // timeout = 5 * 60;
+ //}
+
+ mgr->calib.calib_timeout.data = mgr;
+ mgr->calib.calib_timeout.cb = calib_loop_run;
+ osmo_timer_schedule(&mgr->calib.calib_timeout, timeout, 0);
+ }
+
+ mgr->calib.state = CALIB_INITIAL;
+ calib_close(mgr);
+}
+
+static int calib_run(struct lc15bts_mgr_instance *mgr, int from_loop)
+{
+ if (mgr->calib.state != CALIB_INITIAL) {
+ LOGP(DCALIB, LOGL_ERROR, "Calib is already in progress.\n");
+ return -1;
+ }
+
+ mgr->calib.calib_from_loop = from_loop;
+
+ /* From now on everything will be handled from the failure */
+ mgr->calib.state = CALIB_IN_PROGRESS;
+ calib_start(mgr);
+ return 0;
+}
+
+static void calib_loop_run(void *_data)
+{
+ int rc;
+ struct lc15bts_mgr_instance *mgr = _data;
+
+ LOGP(DCALIB, LOGL_NOTICE, "Going to calibrate the system.\n");
+ rc = calib_run(mgr, 1);
+ if (rc != 0) {
+ calib_state_reset(mgr, CALIB_FAIL_START);
+ }
+}
+
+int lc15bts_mgr_calib_run(struct lc15bts_mgr_instance *mgr)
+{
+ return calib_run(mgr, 0);
+}
+
+int lc15bts_mgr_calib_init(struct lc15bts_mgr_instance *mgr)
+{
+ mgr->calib.state = CALIB_INITIAL;
+ mgr->calib.calib_timeout.data = mgr;
+ mgr->calib.calib_timeout.cb = calib_loop_run;
+ osmo_timer_schedule(&mgr->calib.calib_timeout, 0, 0);
+ return 0;
+}
+
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr_nl.c b/src/osmo-bts-litecell15/misc/lc15bts_mgr_nl.c
new file mode 100644
index 00000000..d2100eb1
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_mgr_nl.c
@@ -0,0 +1,210 @@
+/* NetworkListen for NuRAN Litecell 1.5 BTS management daemon */
+
+/* Copyright (C) 2015 by Yves Godin <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/lc15bts_mgr.h"
+#include "misc/lc15bts_misc.h"
+#include "misc/lc15bts_nl.h"
+#include "misc/lc15bts_par.h"
+#include "misc/lc15bts_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 "/sys/class/net/eth0/address"
+
+static struct osmo_fd nl_fd;
+
+/*
+ * The TLV structure in IPA messages in UDP packages is a bit
+ * weird. First the header appears to have an extra NULL byte
+ * and second the L16 of the L16TV needs to include +1 for the
+ * tag. The default msgb/tlv and libosmo-abis routines do not
+ * provide this.
+ */
+
+static void ipaccess_prepend_header_quirk(struct msgb *msg, int proto)
+{
+ struct ipaccess_head *hh;
+
+ /* prepend the ip.access header */
+ hh = (struct ipaccess_head *) msgb_push(msg, sizeof(*hh) + 1);
+ hh->len = htons(msg->len - sizeof(*hh) - 1);
+ hh->proto = proto;
+}
+
+static void quirk_l16tv_put(struct msgb *msg, uint16_t len, uint8_t tag,
+ const uint8_t *val)
+{
+ uint8_t *buf = msgb_put(msg, len + 2 + 1);
+
+ *buf++ = (len + 1) >> 8;
+ *buf++ = (len + 1) & 0xff;
+ *buf++ = tag;
+ memcpy(buf, val, len);
+}
+
+/*
+ * We don't look at the content of the request yet and lie
+ * about most of the responses.
+ */
+static void respond_to(struct sockaddr_in *src, struct osmo_fd *fd,
+ uint8_t *data, size_t len)
+{
+ static int fetched_info = 0;
+ static char mac_str[20] = {0, };
+ static char model_name[64] = {0, };
+ static char ser_str[20] = {0, };
+
+ struct sockaddr_in loc_addr;
+ int rc;
+ char loc_ip[INET_ADDRSTRLEN];
+ struct msgb *msg = msgb_alloc_headroom(512, 128, "ipa get response");
+ if (!msg) {
+ LOGP(DFIND, LOGL_ERROR, "Failed to allocate msgb\n");
+ return;
+ }
+
+ if (!fetched_info) {
+ int fd_eth;
+ int serno;
+ int model;
+ int rev;
+
+ /* fetch the MAC */
+ fd_eth = open(ETH0_ADDR_SYSFS, O_RDONLY);
+ if (fd_eth >= 0) {
+ read(fd_eth, mac_str, sizeof(mac_str)-1);
+ mac_str[sizeof(mac_str)-1] = '\0';
+ close(fd_eth);
+ }
+
+ /* fetch the serial number */
+ lc15bts_par_get_int(LC15BTS_PAR_SERNR, &serno);
+ snprintf(ser_str, sizeof(ser_str), "%d", serno);
+
+ /* fetch the model and trx number */
+ snprintf(model_name, sizeof(model_name), "Litecell 1.5 BTS");
+
+ rev = lc15bts_rev_get();
+ if (rev >= 0) {
+ snprintf(model_name, sizeof(model_name), "%s Rev %c",
+ model_name, rev);
+ }
+
+ model = lc15bts_model_get();
+ if (model >= 0) {
+ snprintf(model_name, sizeof(model_name), "%s (%05X)",
+ model_name, model);
+ }
+ fetched_info = 1;
+ }
+
+ if (source_for_dest(&src->sin_addr, &loc_addr.sin_addr) != 0) {
+ LOGP(DFIND, LOGL_ERROR, "Failed to determine local source\n");
+ return;
+ }
+
+ msgb_put_u8(msg, IPAC_MSGT_ID_RESP);
+
+ /* append MAC addr */
+ quirk_l16tv_put(msg, strlen(mac_str) + 1, IPAC_IDTAG_MACADDR, (uint8_t *) mac_str);
+
+ /* append ip address */
+ inet_ntop(AF_INET, &loc_addr.sin_addr, loc_ip, sizeof(loc_ip));
+ quirk_l16tv_put(msg, strlen(loc_ip) + 1, IPAC_IDTAG_IPADDR, (uint8_t *) loc_ip);
+
+ /* append the serial number */
+ quirk_l16tv_put(msg, strlen(ser_str) + 1, IPAC_IDTAG_SERNR, (uint8_t *) ser_str);
+
+ /* abuse some flags */
+ quirk_l16tv_put(msg, strlen(model_name) + 1, IPAC_IDTAG_UNIT, (uint8_t *) model_name);
+
+ /* ip.access nanoBTS would reply to port==3006 */
+ ipaccess_prepend_header_quirk(msg, IPAC_PROTO_IPACCESS);
+ rc = sendto(fd->fd, msg->data, msg->len, 0, (struct sockaddr *)src, sizeof(*src));
+ if (rc != msg->len)
+ LOGP(DFIND, LOGL_ERROR,
+ "Failed to send with rc(%d) errno(%d)\n", rc, errno);
+}
+
+static int ipaccess_bcast(struct osmo_fd *fd, unsigned int what)
+{
+ uint8_t data[2048];
+ char src[INET_ADDRSTRLEN];
+ struct sockaddr_in addr = {};
+ socklen_t len = sizeof(addr);
+ int rc;
+
+ rc = recvfrom(fd->fd, data, sizeof(data), 0,
+ (struct sockaddr *) &addr, &len);
+ if (rc <= 0) {
+ LOGP(DFIND, LOGL_ERROR,
+ "Failed to read from socket errno(%d)\n", errno);
+ return -1;
+ }
+
+ LOGP(DFIND, LOGL_DEBUG,
+ "Received request from: %s size %d\n",
+ inet_ntop(AF_INET, &addr.sin_addr, src, sizeof(src)), rc);
+
+ if (rc < 6)
+ return 0;
+
+ if (data[2] != IPAC_PROTO_IPACCESS || data[4] != IPAC_MSGT_ID_GET)
+ return 0;
+
+ respond_to(&addr, fd, data + 6, rc - 6);
+ return 0;
+}
+
+int lc15bts_mgr_nl_init(void)
+{
+ int rc;
+
+ nl_fd.cb = ipaccess_bcast;
+ rc = osmo_sock_init_ofd(&nl_fd, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ "0.0.0.0", 3006, OSMO_SOCK_F_BIND);
+ if (rc < 0) {
+ perror("Socket creation");
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr_temp.c b/src/osmo-bts-litecell15/misc/lc15bts_mgr_temp.c
new file mode 100644
index 00000000..00b8657c
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_mgr_temp.c
@@ -0,0 +1,353 @@
+/* Temperature control for NuRAN Litecell 1.5 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 "misc/lc15bts_mgr.h"
+#include "misc/lc15bts_misc.h"
+#include "misc/lc15bts_temp.h"
+#include "misc/lc15bts_power.h"
+
+#include <osmo-bts/logging.h>
+
+#include <osmocom/core/timer.h>
+#include <osmocom/core/utils.h>
+
+static struct lc15bts_mgr_instance *s_mgr;
+static struct osmo_timer_list temp_ctrl_timer;
+
+static const struct value_string state_names[] = {
+ { STATE_NORMAL, "NORMAL" },
+ { STATE_WARNING_HYST, "WARNING (HYST)" },
+ { STATE_WARNING, "WARNING" },
+ { STATE_CRITICAL, "CRITICAL" },
+ { 0, NULL }
+};
+
+const char *lc15bts_mgr_temp_get_state(enum lc15bts_temp_state state)
+{
+ return get_value_string(state_names, state);
+}
+
+static int next_state(enum lc15bts_temp_state current_state, int critical, int warning)
+{
+ int next_state = -1;
+ switch (current_state) {
+ case STATE_NORMAL:
+ if (critical)
+ next_state = STATE_CRITICAL;
+ else if (warning)
+ next_state = STATE_WARNING;
+ break;
+ case STATE_WARNING_HYST:
+ if (critical)
+ next_state = STATE_CRITICAL;
+ else if (warning)
+ next_state = STATE_WARNING;
+ else
+ next_state = STATE_NORMAL;
+ break;
+ case STATE_WARNING:
+ if (critical)
+ next_state = STATE_CRITICAL;
+ else if (!warning)
+ next_state = STATE_WARNING_HYST;
+ break;
+ case STATE_CRITICAL:
+ if (!critical && !warning)
+ next_state = STATE_WARNING;
+ break;
+ };
+
+ return next_state;
+}
+
+static void handle_normal_actions(int actions)
+{
+ /* switch on the PA */
+ if (actions & TEMP_ACT_NORM_PA1_ON) {
+ if (lc15bts_power_set(LC15BTS_POWER_PA1, 1) != 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to switch on the PA #1\n");
+ } else {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Switched on the PA #1 as normal action.\n");
+ }
+ }
+
+ if (actions & TEMP_ACT_NORM_PA2_ON) {
+ if (lc15bts_power_set(LC15BTS_POWER_PA2, 1) != 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to switch on the PA #2\n");
+ } else {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Switched on the PA #2 as normal action.\n");
+ }
+ }
+
+ if (actions & TEMP_ACT_NORM_BTS_SRV_ON) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Going to switch on the BTS service\n");
+ /*
+ * TODO: use/create something like nspawn that serializes
+ * and used SIGCHLD/waitpid to pick up the dead processes
+ * without invoking shell.
+ */
+ system("/bin/systemctl start lc15bts.service");
+ }
+}
+
+static void handle_actions(int actions)
+{
+ /* switch off the PA */
+ if (actions & TEMP_ACT_PA2_OFF) {
+ if (lc15bts_power_set(LC15BTS_POWER_PA2, 0) != 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to switch off the PA #2. Stop BTS?\n");
+ } else {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Switched off the PA #2 due temperature.\n");
+ }
+ }
+
+ if (actions & TEMP_ACT_PA1_OFF) {
+ if (lc15bts_power_set(LC15BTS_POWER_PA1, 0) != 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to switch off the PA #1. Stop BTS?\n");
+ } else {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Switched off the PA #1 due temperature.\n");
+ }
+ }
+
+ if (actions & TEMP_ACT_BTS_SRV_OFF) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Going to switch off the BTS service\n");
+ /*
+ * TODO: use/create something like nspawn that serializes
+ * and used SIGCHLD/waitpid to pick up the dead processes
+ * without invoking shell.
+ */
+ system("/bin/systemctl stop lc15bts.service");
+ }
+}
+
+/**
+ * Go back to normal! Depending on the configuration execute the normal
+ * actions that could (start to) undo everything we did in the other
+ * states. What is still missing is the power increase/decrease depending
+ * on the state. E.g. starting from WARNING_HYST we might want to slowly
+ * ramp up the output power again.
+ */
+static void execute_normal_act(struct lc15bts_mgr_instance *manager)
+{
+ LOGP(DTEMP, LOGL_NOTICE, "System is back to normal temperature.\n");
+ handle_normal_actions(manager->temp.action_norm);
+}
+
+static void execute_warning_act(struct lc15bts_mgr_instance *manager)
+{
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached temperature warning.\n");
+ handle_actions(manager->temp.action_warn);
+}
+
+static void execute_critical_act(struct lc15bts_mgr_instance *manager)
+{
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical warning.\n");
+ handle_actions(manager->temp.action_crit);
+}
+
+static void lc15bts_mgr_temp_handle(struct lc15bts_mgr_instance *manager,
+ int critical, int warning)
+{
+ int new_state = next_state(manager->temp.state, critical, warning);
+
+ /* Nothing changed */
+ if (new_state < 0)
+ return;
+
+ LOGP(DTEMP, LOGL_NOTICE, "Moving from state %s to %s.\n",
+ get_value_string(state_names, manager->temp.state),
+ get_value_string(state_names, new_state));
+ manager->temp.state = new_state;
+ switch (manager->temp.state) {
+ case STATE_NORMAL:
+ execute_normal_act(manager);
+ break;
+ case STATE_WARNING_HYST:
+ /* do nothing? Maybe start to increase transmit power? */
+ break;
+ case STATE_WARNING:
+ execute_warning_act(manager);
+ break;
+ case STATE_CRITICAL:
+ execute_critical_act(manager);
+ break;
+ };
+}
+
+static void temp_ctrl_check()
+{
+ int rc;
+ int warn_thresh_passed = 0;
+ int crit_thresh_passed = 0;
+
+ LOGP(DTEMP, LOGL_DEBUG, "Going to check the temperature.\n");
+
+ /* Read the current supply temperature */
+ rc = lc15bts_temp_get(LC15BTS_TEMP_SUPPLY, LC15BTS_TEMP_INPUT);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to read the supply temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ int temp = rc / 1000;
+ if (temp > s_mgr->temp.supply_limit.thresh_warn)
+ warn_thresh_passed = 1;
+ if (temp > s_mgr->temp.supply_limit.thresh_crit)
+ crit_thresh_passed = 1;
+ LOGP(DTEMP, LOGL_DEBUG, "Supply temperature is: %d\n", temp);
+ }
+
+ /* Read the current SoC temperature */
+ rc = lc15bts_temp_get(LC15BTS_TEMP_SOC, LC15BTS_TEMP_INPUT);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to read the SoC temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ int temp = rc / 1000;
+ if (temp > s_mgr->temp.soc_limit.thresh_warn)
+ warn_thresh_passed = 1;
+ if (temp > s_mgr->temp.soc_limit.thresh_crit)
+ crit_thresh_passed = 1;
+ LOGP(DTEMP, LOGL_DEBUG, "SoC temperature is: %d\n", temp);
+ }
+
+ /* Read the current fpga temperature */
+ rc = lc15bts_temp_get(LC15BTS_TEMP_FPGA, LC15BTS_TEMP_INPUT);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to read the fpga temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ int temp = rc / 1000;
+ if (temp > s_mgr->temp.fpga_limit.thresh_warn)
+ warn_thresh_passed = 1;
+ if (temp > s_mgr->temp.fpga_limit.thresh_crit)
+ crit_thresh_passed = 1;
+ LOGP(DTEMP, LOGL_DEBUG, "FPGA temperature is: %d\n", temp);
+ }
+
+ /* Read the current memory temperature */
+ rc = lc15bts_temp_get(LC15BTS_TEMP_MEMORY, LC15BTS_TEMP_INPUT);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to read the memory temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ int temp = rc / 1000;
+ if (temp > s_mgr->temp.memory_limit.thresh_warn)
+ warn_thresh_passed = 1;
+ if (temp > s_mgr->temp.memory_limit.thresh_crit)
+ crit_thresh_passed = 1;
+ LOGP(DTEMP, LOGL_DEBUG, "Memory temperature is: %d\n", temp);
+ }
+
+ /* Read the current TX #1 temperature */
+ rc = lc15bts_temp_get(LC15BTS_TEMP_TX1, LC15BTS_TEMP_INPUT);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to read the TX #1 temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ int temp = rc / 1000;
+ if (temp > s_mgr->temp.tx1_limit.thresh_warn)
+ warn_thresh_passed = 1;
+ if (temp > s_mgr->temp.tx1_limit.thresh_crit)
+ crit_thresh_passed = 1;
+ LOGP(DTEMP, LOGL_DEBUG, "TX #1 temperature is: %d\n", temp);
+ }
+
+ /* Read the current TX #2 temperature */
+ rc = lc15bts_temp_get(LC15BTS_TEMP_TX2, LC15BTS_TEMP_INPUT);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to read the TX #2 temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ int temp = rc / 1000;
+ if (temp > s_mgr->temp.tx2_limit.thresh_warn)
+ warn_thresh_passed = 1;
+ if (temp > s_mgr->temp.tx2_limit.thresh_crit)
+ crit_thresh_passed = 1;
+ LOGP(DTEMP, LOGL_DEBUG, "TX #2 temperature is: %d\n", temp);
+ }
+
+ /* Read the current PA #1 temperature */
+ rc = lc15bts_temp_get(LC15BTS_TEMP_PA1, LC15BTS_TEMP_INPUT);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to read the PA #1 temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ int temp = rc / 1000;
+ if (temp > s_mgr->temp.pa1_limit.thresh_warn)
+ warn_thresh_passed = 1;
+ if (temp > s_mgr->temp.pa1_limit.thresh_crit)
+ crit_thresh_passed = 1;
+ LOGP(DTEMP, LOGL_DEBUG, "PA #1 temperature is: %d\n", temp);
+ }
+
+ /* Read the current PA #2 temperature */
+ rc = lc15bts_temp_get(LC15BTS_TEMP_PA2, LC15BTS_TEMP_INPUT);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to read the PA #2 temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ int temp = rc / 1000;
+ if (temp > s_mgr->temp.pa2_limit.thresh_warn)
+ warn_thresh_passed = 1;
+ if (temp > s_mgr->temp.pa2_limit.thresh_crit)
+ crit_thresh_passed = 1;
+ LOGP(DTEMP, LOGL_DEBUG, "PA #2 temperature is: %d\n", temp);
+ }
+
+ lc15bts_mgr_temp_handle(s_mgr, crit_thresh_passed, warn_thresh_passed);
+}
+
+static void temp_ctrl_check_cb(void *unused)
+{
+ temp_ctrl_check();
+ /* Check every two minutes? XXX make it configurable! */
+ osmo_timer_schedule(&temp_ctrl_timer, 2 * 60, 0);
+}
+
+int lc15bts_mgr_temp_init(struct lc15bts_mgr_instance *mgr)
+{
+ s_mgr = mgr;
+ temp_ctrl_timer.cb = temp_ctrl_check_cb;
+ temp_ctrl_check_cb(NULL);
+ return 0;
+}
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_mgr_vty.c b/src/osmo-bts-litecell15/misc/lc15bts_mgr_vty.c
new file mode 100644
index 00000000..cfc6e129
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_mgr_vty.c
@@ -0,0 +1,602 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_mgr_vty.c
+ * (C) 2014 by lc15com - s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * Author: Alvaro Neira Ayuso <anayuso@lc15com.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 <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/misc.h>
+
+#include <osmo-bts/logging.h>
+
+#include "lc15bts_misc.h"
+#include "lc15bts_mgr.h"
+#include "lc15bts_temp.h"
+#include "lc15bts_power.h"
+#include "btsconfig.h"
+
+static struct lc15bts_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 enum node_type 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_NODE:
+ case LIMIT_SOC_NODE:
+ case LIMIT_FPGA_NODE:
+ case LIMIT_MEMORY_NODE:
+ case LIMIT_TX1_NODE:
+ case LIMIT_TX2_NODE:
+ case LIMIT_PA1_NODE:
+ case LIMIT_PA2_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_NODE:
+ case LIMIT_SOC_NODE:
+ case LIMIT_FPGA_NODE:
+ case LIMIT_MEMORY_NODE:
+ case LIMIT_TX1_NODE:
+ case LIMIT_TX2_NODE:
+ case LIMIT_PA1_NODE:
+ case LIMIT_PA2_NODE:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static struct vty_app_info vty_info = {
+ .name = "lc15bts-mgr",
+ .version = PACKAGE_VERSION,
+ .go_parent_cb = go_to_parent,
+ .is_config_node = is_config_node,
+ .copyright = copyright,
+};
+
+
+#define MGR_STR "Configure lc15bts-mgr\n"
+
+static struct cmd_node mgr_node = {
+ MGR_NODE,
+ "%s(lc15bts-mgr)# ",
+ 1,
+};
+
+static struct cmd_node act_norm_node = {
+ ACT_NORM_NODE,
+ "%s(action-normal)# ",
+ 1,
+};
+
+static struct cmd_node act_warn_node = {
+ ACT_WARN_NODE,
+ "%s(action-warn)# ",
+ 1,
+};
+
+static struct cmd_node act_crit_node = {
+ ACT_CRIT_NODE,
+ "%s(action-critical)# ",
+ 1,
+};
+
+static struct cmd_node limit_supply_node = {
+ LIMIT_SUPPLY_NODE,
+ "%s(limit-supply)# ",
+ 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_memory_node = {
+ LIMIT_MEMORY_NODE,
+ "%s(limit-memory)# ",
+ 1,
+};
+
+static struct cmd_node limit_tx1_node = {
+ LIMIT_TX1_NODE,
+ "%s(limit-tx1)# ",
+ 1,
+};
+static struct cmd_node limit_tx2_node = {
+ LIMIT_TX2_NODE,
+ "%s(limit-tx2)# ",
+ 1,
+};
+static struct cmd_node limit_pa1_node = {
+ LIMIT_PA1_NODE,
+ "%s(limit-pa1)# ",
+ 1,
+};
+static struct cmd_node limit_pa2_node = {
+ LIMIT_PA2_NODE,
+ "%s(limit-pa2)# ",
+ 1,
+};
+
+DEFUN(cfg_mgr, cfg_mgr_cmd,
+ "lc15bts-mgr",
+ MGR_STR)
+{
+ vty->node = MGR_NODE;
+ return CMD_SUCCESS;
+}
+
+static void write_temp_limit(struct vty *vty, const char *name,
+ struct lc15bts_temp_limit *limit)
+{
+ vty_out(vty, " %s%s", name, VTY_NEWLINE);
+ vty_out(vty, " threshold warning %d%s",
+ limit->thresh_warn, VTY_NEWLINE);
+ vty_out(vty, " threshold critical %d%s",
+ limit->thresh_crit, VTY_NEWLINE);
+}
+
+static void write_norm_action(struct vty *vty, const char *name, int actions)
+{
+ vty_out(vty, " %s%s", name, VTY_NEWLINE);
+ vty_out(vty, " %spa1-on%s",
+ (actions & TEMP_ACT_NORM_PA1_ON) ? "" : "no ", VTY_NEWLINE);
+ vty_out(vty, " %spa2-on%s",
+ (actions & TEMP_ACT_NORM_PA2_ON) ? "" : "no ", VTY_NEWLINE);
+ vty_out(vty, " %sbts-service-on%s",
+ (actions & TEMP_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, " %spa1-off%s",
+ (actions & TEMP_ACT_PA1_OFF) ? "" : "no ", VTY_NEWLINE);
+ vty_out(vty, " %spa2-off%s",
+ (actions & TEMP_ACT_PA2_OFF) ? "" : "no ", VTY_NEWLINE);
+ vty_out(vty, " %sbts-service-off%s",
+ (actions & TEMP_ACT_BTS_SRV_OFF) ? "" : "no ", VTY_NEWLINE);
+}
+
+static int config_write_mgr(struct vty *vty)
+{
+ vty_out(vty, "lc15bts-mgr%s", VTY_NEWLINE);
+
+ write_temp_limit(vty, "limits supply", &s_mgr->temp.supply_limit);
+ write_temp_limit(vty, "limits soc", &s_mgr->temp.soc_limit);
+ write_temp_limit(vty, "limits fpga", &s_mgr->temp.fpga_limit);
+ write_temp_limit(vty, "limits memory", &s_mgr->temp.memory_limit);
+ write_temp_limit(vty, "limits tx1", &s_mgr->temp.tx1_limit);
+ write_temp_limit(vty, "limits tx2", &s_mgr->temp.tx2_limit);
+ write_temp_limit(vty, "limits pa1", &s_mgr->temp.pa1_limit);
+ write_temp_limit(vty, "limits pa2", &s_mgr->temp.pa2_limit);
+
+ write_norm_action(vty, "actions normal", s_mgr->temp.action_norm);
+ write_action(vty, "actions warn", s_mgr->temp.action_warn);
+ write_action(vty, "actions critical", s_mgr->temp.action_crit);
+
+ return CMD_SUCCESS;
+}
+
+static int config_write_dummy(struct vty *vty)
+{
+ return CMD_SUCCESS;
+}
+
+#define CFG_LIMIT(name, expl, switch_to, variable) \
+DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \
+ "limits " #name, \
+ "Configure Limits\n" expl) \
+{ \
+ vty->node = switch_to; \
+ vty->index = &s_mgr->temp.variable; \
+ return CMD_SUCCESS; \
+}
+
+CFG_LIMIT(supply, "SUPPLY\n", LIMIT_SUPPLY_NODE, supply_limit)
+CFG_LIMIT(soc, "SOC\n", LIMIT_SOC_NODE, soc_limit)
+CFG_LIMIT(fpga, "FPGA\n", LIMIT_FPGA_NODE, fpga_limit)
+CFG_LIMIT(memory, "MEMORY\n", LIMIT_MEMORY_NODE, memory_limit)
+CFG_LIMIT(tx1, "TX1\n", LIMIT_TX1_NODE, tx1_limit)
+CFG_LIMIT(tx2, "TX2\n", LIMIT_TX2_NODE, tx2_limit)
+CFG_LIMIT(pa1, "PA1\n", LIMIT_PA1_NODE, pa1_limit)
+CFG_LIMIT(pa2, "PA2\n", LIMIT_PA2_NODE, pa2_limit)
+#undef CFG_LIMIT
+
+DEFUN(cfg_limit_warning, cfg_thresh_warning_cmd,
+ "threshold warning <0-200>",
+ "Threshold to reach\n" "Warning level\n" "Range\n")
+{
+ struct lc15bts_temp_limit *limit = vty->index;
+ limit->thresh_warn = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_limit_crit, cfg_thresh_crit_cmd,
+ "threshold critical <0-200>",
+ "Threshold to reach\n" "Severe level\n" "Range\n")
+{
+ struct lc15bts_temp_limit *limit = vty->index;
+ limit->thresh_crit = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+#define CFG_ACTION(name, expl, switch_to, variable) \
+DEFUN(cfg_action_##name, cfg_action_##name##_cmd, \
+ "actions " #name, \
+ "Configure Actions\n" expl) \
+{ \
+ vty->node = switch_to; \
+ vty->index = &s_mgr->temp.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_pa1_on, cfg_action_pa1_on_cmd,
+ "pa1-on",
+ "Switch the Power Amplifier #1 on\n")
+{
+ int *action = vty->index;
+ *action |= TEMP_ACT_NORM_PA1_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_pa1_on, cfg_no_action_pa1_on_cmd,
+ "no pa1-on",
+ NO_STR "Switch the Power Amplifieri #1 on\n")
+{
+ int *action = vty->index;
+ *action &= ~TEMP_ACT_NORM_PA1_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_pa2_on, cfg_action_pa2_on_cmd,
+ "pa2-on",
+ "Switch the Power Amplifier #2 on\n")
+{
+ int *action = vty->index;
+ *action |= TEMP_ACT_NORM_PA2_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_pa2_on, cfg_no_action_pa2_on_cmd,
+ "no pa2-on",
+ NO_STR "Switch the Power Amplifieri #2 on\n")
+{
+ int *action = vty->index;
+ *action &= ~TEMP_ACT_NORM_PA2_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_bts_srv_on, cfg_action_bts_srv_on_cmd,
+ "bts-service-on",
+ "Start the systemd lc15bts.service\n")
+{
+ int *action = vty->index;
+ *action |= TEMP_ACT_NORM_BTS_SRV_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_bts_srv_on, cfg_no_action_bts_srv_on_cmd,
+ "no bts-service-on",
+ NO_STR "Start the systemd lc15bts.service\n")
+{
+ int *action = vty->index;
+ *action &= ~TEMP_ACT_NORM_BTS_SRV_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_pa1_off, cfg_action_pa1_off_cmd,
+ "pa1-off",
+ "Switch the Power Amplifier #1 off\n")
+{
+ int *action = vty->index;
+ *action |= TEMP_ACT_PA1_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_pa1_off, cfg_no_action_pa1_off_cmd,
+ "no pa1-off",
+ NO_STR "Do not switch off the Power Amplifier #1\n")
+{
+ int *action = vty->index;
+ *action &= ~TEMP_ACT_PA1_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_pa2_off, cfg_action_pa2_off_cmd,
+ "pa2-off",
+ "Switch the Power Amplifier #2 off\n")
+{
+ int *action = vty->index;
+ *action |= TEMP_ACT_PA2_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_pa2_off, cfg_no_action_pa2_off_cmd,
+ "no pa2-off",
+ NO_STR "Do not switch off the Power Amplifier #2\n")
+{
+ int *action = vty->index;
+ *action &= ~TEMP_ACT_PA2_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_bts_srv_off, cfg_action_bts_srv_off_cmd,
+ "bts-service-off",
+ "Stop the systemd lc15bts.service\n")
+{
+ int *action = vty->index;
+ *action |= TEMP_ACT_BTS_SRV_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_bts_srv_off, cfg_no_action_bts_srv_off_cmd,
+ "no bts-service-off",
+ NO_STR "Stop the systemd lc15bts.service\n")
+{
+ int *action = vty->index;
+ *action &= ~TEMP_ACT_BTS_SRV_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_mgr, show_mgr_cmd, "show manager",
+ SHOW_STR "Display information about the manager")
+{
+ vty_out(vty, "Temperature control state: %s%s",
+ lc15bts_mgr_temp_get_state(s_mgr->temp.state), VTY_NEWLINE);
+ vty_out(vty, "Current Temperatures%s", VTY_NEWLINE);
+ vty_out(vty, " Main Supply : %f Celcius%s",
+ lc15bts_temp_get(LC15BTS_TEMP_SUPPLY,
+ LC15BTS_TEMP_INPUT) / 1000.0f,
+ VTY_NEWLINE);
+ vty_out(vty, " SoC : %f Celcius%s",
+ lc15bts_temp_get(LC15BTS_TEMP_SOC,
+ LC15BTS_TEMP_INPUT) / 1000.0f,
+ VTY_NEWLINE);
+ vty_out(vty, " FPGA : %f Celcius%s",
+ lc15bts_temp_get(LC15BTS_TEMP_FPGA,
+ LC15BTS_TEMP_INPUT) / 1000.0f,
+ VTY_NEWLINE);
+ vty_out(vty, " Memory (DDR): %f Celcius%s",
+ lc15bts_temp_get(LC15BTS_TEMP_MEMORY,
+ LC15BTS_TEMP_INPUT) / 1000.0f,
+ VTY_NEWLINE);
+ vty_out(vty, " TX 1 : %f Celcius%s",
+ lc15bts_temp_get(LC15BTS_TEMP_TX1,
+ LC15BTS_TEMP_INPUT) / 1000.0f,
+ VTY_NEWLINE);
+ vty_out(vty, " TX 2 : %f Celcius%s",
+ lc15bts_temp_get(LC15BTS_TEMP_TX2,
+ LC15BTS_TEMP_INPUT) / 1000.0f,
+ VTY_NEWLINE);
+ vty_out(vty, " Power Amp #1: %f Celcius%s",
+ lc15bts_temp_get(LC15BTS_TEMP_PA1,
+ LC15BTS_TEMP_INPUT) / 1000.0f,
+ VTY_NEWLINE);
+ vty_out(vty, " Power Amp #2: %f Celcius%s",
+ lc15bts_temp_get(LC15BTS_TEMP_PA2,
+ LC15BTS_TEMP_INPUT) / 1000.0f,
+ VTY_NEWLINE);
+
+ vty_out(vty, "Power Status%s", VTY_NEWLINE);
+ vty_out(vty, " Main Supply : (ON) [%6.2f Vdc, %4.2f A, %6.2f W]%s",
+ lc15bts_power_sensor_get(LC15BTS_POWER_SUPPLY,
+ LC15BTS_POWER_VOLTAGE)/1000.0f,
+ lc15bts_power_sensor_get(LC15BTS_POWER_SUPPLY,
+ LC15BTS_POWER_CURRENT)/1000.0f,
+ lc15bts_power_sensor_get(LC15BTS_POWER_SUPPLY,
+ LC15BTS_POWER_POWER)/1000000.0f,
+ VTY_NEWLINE);
+ vty_out(vty, " Power Amp #1: %s [%6.2f Vdc, %4.2f A, %6.2f W]%s",
+ lc15bts_power_get(LC15BTS_POWER_PA1) ? "ON " : "OFF",
+ lc15bts_power_sensor_get(LC15BTS_POWER_PA1,
+ LC15BTS_POWER_VOLTAGE)/1000.0f,
+ lc15bts_power_sensor_get(LC15BTS_POWER_PA1,
+ LC15BTS_POWER_CURRENT)/1000.0f,
+ lc15bts_power_sensor_get(LC15BTS_POWER_PA1,
+ LC15BTS_POWER_POWER)/1000000.0f,
+ VTY_NEWLINE);
+ vty_out(vty, " Power Amp #2: %s [%6.2f Vdc, %4.2f A, %6.2f W]%s",
+ lc15bts_power_get(LC15BTS_POWER_PA2) ? "ON " : "OFF",
+ lc15bts_power_sensor_get(LC15BTS_POWER_PA2,
+ LC15BTS_POWER_VOLTAGE)/1000.0f,
+ lc15bts_power_sensor_get(LC15BTS_POWER_PA2,
+ LC15BTS_POWER_CURRENT)/1000.0f,
+ lc15bts_power_sensor_get(LC15BTS_POWER_PA2,
+ LC15BTS_POWER_POWER)/1000000.0f,
+ VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(calibrate_clock, calibrate_clock_cmd,
+ "calibrate clock",
+ "Calibration commands\n"
+ "Calibrate clock against GPS PPS\n")
+{
+ if (lc15bts_mgr_calib_run(s_mgr) < 0) {
+ vty_out(vty, "%%Failed to start calibration.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ return CMD_SUCCESS;
+}
+
+static void register_limit(int limit)
+{
+ install_element(limit, &cfg_thresh_warning_cmd);
+ install_element(limit, &cfg_thresh_crit_cmd);
+}
+
+static void register_normal_action(int act)
+{
+ install_element(act, &cfg_action_pa1_on_cmd);
+ install_element(act, &cfg_no_action_pa1_on_cmd);
+ install_element(act, &cfg_action_pa2_on_cmd);
+ install_element(act, &cfg_no_action_pa2_on_cmd);
+ install_element(act, &cfg_action_bts_srv_on_cmd);
+ install_element(act, &cfg_no_action_bts_srv_on_cmd);
+}
+
+static void register_action(int act)
+{
+ install_element(act, &cfg_action_pa1_off_cmd);
+ install_element(act, &cfg_no_action_pa1_off_cmd);
+ install_element(act, &cfg_action_pa2_off_cmd);
+ install_element(act, &cfg_no_action_pa2_off_cmd);
+ install_element(act, &cfg_action_bts_srv_off_cmd);
+ install_element(act, &cfg_no_action_bts_srv_off_cmd);
+}
+
+int lc15bts_mgr_vty_init(void)
+{
+ vty_init(&vty_info);
+
+ install_element_ve(&show_mgr_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_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_supply_cmd);
+ register_limit(LIMIT_SUPPLY_NODE);
+ vty_install_default(LIMIT_SUPPLY_NODE);
+
+ install_node(&limit_soc_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_soc_cmd);
+ register_limit(LIMIT_SOC_NODE);
+ vty_install_default(LIMIT_SOC_NODE);
+
+ install_node(&limit_fpga_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_fpga_cmd);
+ register_limit(LIMIT_FPGA_NODE);
+ vty_install_default(LIMIT_FPGA_NODE);
+
+ install_node(&limit_memory_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_memory_cmd);
+ register_limit(LIMIT_MEMORY_NODE);
+ vty_install_default(LIMIT_MEMORY_NODE);
+
+ install_node(&limit_tx1_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_tx1_cmd);
+ register_limit(LIMIT_TX1_NODE);
+ vty_install_default(LIMIT_TX1_NODE);
+
+ install_node(&limit_tx2_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_tx2_cmd);
+ register_limit(LIMIT_TX2_NODE);
+ vty_install_default(LIMIT_TX2_NODE);
+
+ install_node(&limit_pa1_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_pa1_cmd);
+ register_limit(LIMIT_PA1_NODE);
+ vty_install_default(LIMIT_PA1_NODE);
+
+ install_node(&limit_pa2_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_pa2_cmd);
+ register_limit(LIMIT_PA2_NODE);
+ vty_install_default(LIMIT_PA2_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);
+
+ return 0;
+}
+
+int lc15bts_mgr_parse_config(struct lc15bts_mgr_instance *manager)
+{
+ int rc;
+
+ s_mgr = manager;
+ rc = vty_read_config_file(s_mgr->config_file, NULL);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to parse the config file: '%s'\n",
+ s_mgr->config_file);
+ return rc;
+ }
+
+ return 0;
+}
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_misc.c b/src/osmo-bts-litecell15/misc/lc15bts_misc.c
new file mode 100644
index 00000000..e0602c82
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_misc.c
@@ -0,0 +1,249 @@
+/* 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 "btsconfig.h"
+#include "lc15bts_misc.h"
+#include "lc15bts_par.h"
+#include "lc15bts_mgr.h"
+#include "lc15bts_temp.h"
+
+/*********************************************************************
+ * Temperature handling
+ *********************************************************************/
+
+static const struct {
+ const char *name;
+ int has_max;
+ enum lc15bts_temp_sensor sensor;
+ enum lc15bts_par ee_par;
+} temp_data[] = {
+ {
+ .name = "supply",
+ .has_max = 1,
+ .sensor = LC15BTS_TEMP_SUPPLY,
+ .ee_par = LC15BTS_PAR_TEMP_SUPPLY_MAX,
+ }, {
+ .name = "soc",
+ .has_max = 0,
+ .sensor = LC15BTS_TEMP_SOC,
+ .ee_par = LC15BTS_PAR_TEMP_SOC_MAX,
+ }, {
+ .name = "fpga",
+ .has_max = 0,
+ .sensor = LC15BTS_TEMP_FPGA,
+ .ee_par = LC15BTS_PAR_TEMP_FPGA_MAX,
+
+ }, {
+ .name = "memory",
+ .has_max = 1,
+ .sensor = LC15BTS_TEMP_MEMORY,
+ .ee_par = LC15BTS_PAR_TEMP_MEMORY_MAX,
+ }, {
+ .name = "tx1",
+ .has_max = 0,
+ .sensor = LC15BTS_TEMP_TX1,
+ .ee_par = LC15BTS_PAR_TEMP_TX1_MAX,
+ }, {
+ .name = "tx2",
+ .has_max = 0,
+ .sensor = LC15BTS_TEMP_TX2,
+ .ee_par = LC15BTS_PAR_TEMP_TX2_MAX,
+ }, {
+ .name = "pa1",
+ .has_max = 1,
+ .sensor = LC15BTS_TEMP_PA1,
+ .ee_par = LC15BTS_PAR_TEMP_PA1_MAX,
+ }, {
+ .name = "pa2",
+ .has_max = 1,
+ .sensor = LC15BTS_TEMP_PA2,
+ .ee_par = LC15BTS_PAR_TEMP_PA2_MAX,
+ }
+};
+
+void lc15bts_check_temp(int no_rom_write)
+{
+ int temp_old[ARRAY_SIZE(temp_data)];
+ int temp_hi[ARRAY_SIZE(temp_data)];
+ int temp_cur[ARRAY_SIZE(temp_data)];
+ int i, rc;
+
+ for (i = 0; i < ARRAY_SIZE(temp_data); i++) {
+ int ret;
+ rc = lc15bts_par_get_int(temp_data[i].ee_par, &ret);
+ temp_old[i] = ret * 1000;
+ if (temp_data[i].has_max) {
+ temp_hi[i] = lc15bts_temp_get(temp_data[i].sensor,
+ LC15BTS_TEMP_HIGHEST);
+ temp_cur[i] = lc15bts_temp_get(temp_data[i].sensor,
+ LC15BTS_TEMP_INPUT);
+
+ if ((temp_cur[i] < 0 && temp_cur[i] > -1000) ||
+ (temp_hi[i] < 0 && temp_hi[i] > -1000)) {
+ LOGP(DTEMP, LOGL_ERROR, "Error reading temperature\n");
+ continue;
+ }
+ }
+ else {
+ temp_cur[i] = lc15bts_temp_get(temp_data[i].sensor,
+ LC15BTS_TEMP_INPUT);
+
+ if (temp_cur[i] < 0 && temp_cur[i] > -1000) {
+ LOGP(DTEMP, LOGL_ERROR, "Error reading temperature\n");
+ continue;
+ }
+ temp_hi[i] = temp_cur[i];
+ }
+
+ LOGP(DTEMP, LOGL_DEBUG, "Current %s temperature: %d.%d C\n",
+ temp_data[i].name, temp_cur[i]/1000, temp_cur[i]%1000);
+
+ if (temp_hi[i] > temp_old[i]) {
+ LOGP(DTEMP, LOGL_NOTICE, "New maximum %s "
+ "temperature: %d.%d C\n", temp_data[i].name,
+ temp_hi[i]/1000, temp_hi[i]%1000);
+
+ if (!no_rom_write) {
+ rc = lc15bts_par_set_int(temp_data[i].ee_par,
+ temp_hi[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));
+ }
+ }
+ }
+}
+
+/*********************************************************************
+ * Hours handling
+ *********************************************************************/
+static time_t last_update;
+
+int lc15bts_update_hours(int no_rom_write)
+{
+ time_t now = time(NULL);
+ int rc, op_hrs;
+
+ /* first time after start of manager program */
+ if (last_update == 0) {
+ last_update = now;
+
+ rc = lc15bts_par_get_int(LC15BTS_PAR_HOURS, &op_hrs);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR, "Unable to read "
+ "operational hours: %d (%s)\n", rc,
+ strerror(errno));
+ return rc;
+ }
+
+ LOGP(DTEMP, LOGL_INFO, "Total hours of Operation: %u\n",
+ op_hrs);
+
+ return 0;
+ }
+
+ if (now >= last_update + 3600) {
+ rc = lc15bts_par_get_int(LC15BTS_PAR_HOURS, &op_hrs);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR, "Unable to read "
+ "operational hours: %d (%s)\n", rc,
+ strerror(errno));
+ return rc;
+ }
+
+ /* number of hours to increase */
+ op_hrs += (now-last_update)/3600;
+
+ LOGP(DTEMP, LOGL_INFO, "Total hours of Operation: %u\n",
+ op_hrs);
+
+ if (!no_rom_write) {
+ rc = lc15bts_par_set_int(LC15BTS_PAR_HOURS, op_hrs);
+ if (rc < 0)
+ return rc;
+ }
+
+ last_update = now;
+ }
+
+ return 0;
+}
+
+/*********************************************************************
+ * Firmware reloading
+ *********************************************************************/
+
+static const char *fw_sysfs[_NUM_FW] = {
+ [LC15BTS_FW_DSP0] = "/sys/kernel/debug/remoteproc/remoteproc0/recovery",
+ [LC15BTS_FW_DSP1] = "/sys/kernel/debug/remoteproc/remoteproc0/recovery",
+};
+
+int lc15bts_firmware_reload(enum lc15bts_firmware_type type)
+{
+ int fd;
+ int rc;
+
+ switch (type) {
+ case LC15BTS_FW_DSP0:
+ case LC15BTS_FW_DSP1:
+ fd = open(fw_sysfs[type], O_WRONLY);
+ if (fd < 0) {
+ LOGP(DFW, LOGL_ERROR, "unable ot open firmware device %s: %s\n",
+ fw_sysfs[type], strerror(errno));
+ close(fd);
+ return fd;
+ }
+ rc = write(fd, "restart", 8);
+ if (rc < 8) {
+ LOGP(DFW, LOGL_ERROR, "short write during "
+ "fw write to %s\n", fw_sysfs[type]);
+ close(fd);
+ return -EIO;
+ }
+ close(fd);
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_misc.h b/src/osmo-bts-litecell15/misc/lc15bts_misc.h
new file mode 100644
index 00000000..4c3a862a
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_misc.h
@@ -0,0 +1,16 @@
+#ifndef _LC15BTS_MISC_H
+#define _LC15BTS_MISC_H
+
+#include <stdint.h>
+
+void lc15bts_check_temp(int no_rom_write);
+
+int lc15bts_update_hours(int no_rom_write);
+
+enum lc15bts_firmware_type {
+ LC15BTS_FW_DSP0,
+ LC15BTS_FW_DSP1,
+ _NUM_FW
+};
+
+#endif
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_nl.c b/src/osmo-bts-litecell15/misc/lc15bts_nl.c
new file mode 100644
index 00000000..39f64aae
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_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-litecell15/misc/lc15bts_nl.h b/src/osmo-bts-litecell15/misc/lc15bts_nl.h
new file mode 100644
index 00000000..340cf117
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_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-litecell15/misc/lc15bts_par.c b/src/osmo-bts-litecell15/misc/lc15bts_par.c
new file mode 100644
index 00000000..71544261
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_par.c
@@ -0,0 +1,181 @@
+/* lc15bts - 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 "lc15bts_par.h"
+
+
+#define FACTORY_ROM_PATH "/mnt/rom/factory"
+#define USER_ROM_PATH "/mnt/rom/user"
+
+const struct value_string lc15bts_par_names[_NUM_LC15BTS_PAR+1] = {
+ { LC15BTS_PAR_TEMP_SUPPLY_MAX, "temp-supply-max" },
+ { LC15BTS_PAR_TEMP_SOC_MAX, "temp-soc-max" },
+ { LC15BTS_PAR_TEMP_FPGA_MAX, "temp-fpga-max" },
+ { LC15BTS_PAR_TEMP_MEMORY_MAX, "temp-memory-max" },
+ { LC15BTS_PAR_TEMP_TX1_MAX, "temp-tx1-max" },
+ { LC15BTS_PAR_TEMP_TX2_MAX, "temp-tx2-max" },
+ { LC15BTS_PAR_TEMP_PA1_MAX, "temp-pa1-max" },
+ { LC15BTS_PAR_TEMP_PA2_MAX, "temp-pa2-max" },
+ { LC15BTS_PAR_SERNR, "serial-nr" },
+ { LC15BTS_PAR_HOURS, "hours-running" },
+ { LC15BTS_PAR_BOOTS, "boot-count" },
+ { LC15BTS_PAR_KEY, "key" },
+ { 0, NULL }
+};
+
+int lc15bts_par_is_int(enum lc15bts_par par)
+{
+ switch (par) {
+ case LC15BTS_PAR_TEMP_SUPPLY_MAX:
+ case LC15BTS_PAR_TEMP_SOC_MAX:
+ case LC15BTS_PAR_TEMP_FPGA_MAX:
+ case LC15BTS_PAR_TEMP_MEMORY_MAX:
+ case LC15BTS_PAR_TEMP_TX1_MAX:
+ case LC15BTS_PAR_TEMP_TX2_MAX:
+ case LC15BTS_PAR_TEMP_PA1_MAX:
+ case LC15BTS_PAR_TEMP_PA2_MAX:
+ case LC15BTS_PAR_SERNR:
+ case LC15BTS_PAR_HOURS:
+ case LC15BTS_PAR_BOOTS:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+int lc15bts_par_get_int(enum lc15bts_par par, int *ret)
+{
+ char fpath[PATH_MAX];
+ FILE *fp;
+ int rc;
+
+ if (par >= _NUM_LC15BTS_PAR)
+ return -ENODEV;
+
+ snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(lc15bts_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 lc15bts_par_set_int(enum lc15bts_par par, int val)
+{
+ char fpath[PATH_MAX];
+ FILE *fp;
+ int rc;
+
+ if (par >= _NUM_LC15BTS_PAR)
+ return -ENODEV;
+
+ snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(lc15bts_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;
+ }
+ fclose(fp);
+ return 0;
+}
+
+int lc15bts_par_get_buf(enum lc15bts_par par, uint8_t *buf,
+ unsigned int size)
+{
+ char fpath[PATH_MAX];
+ FILE *fp;
+ int rc;
+
+ if (par >= _NUM_LC15BTS_PAR)
+ return -ENODEV;
+
+ snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(lc15bts_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 lc15bts_par_set_buf(enum lc15bts_par par, const uint8_t *buf,
+ unsigned int size)
+{
+ char fpath[PATH_MAX];
+ FILE *fp;
+ int rc;
+
+ if (par >= _NUM_LC15BTS_PAR)
+ return -ENODEV;
+
+ snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(lc15bts_par_names, par));
+ fpath[sizeof(fpath)-1] = '\0';
+
+ fp = fopen(fpath, "wb");
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fwrite(buf, 1, size, fp);
+
+ fclose(fp);
+
+ return rc;
+}
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_par.h b/src/osmo-bts-litecell15/misc/lc15bts_par.h
new file mode 100644
index 00000000..7c182715
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_par.h
@@ -0,0 +1,33 @@
+#ifndef _LC15BTS_PAR_H
+#define _LC15BTS_PAR_H
+
+#include <osmocom/core/utils.h>
+
+enum lc15bts_par {
+ LC15BTS_PAR_TEMP_SUPPLY_MAX,
+ LC15BTS_PAR_TEMP_SOC_MAX,
+ LC15BTS_PAR_TEMP_FPGA_MAX,
+ LC15BTS_PAR_TEMP_MEMORY_MAX,
+ LC15BTS_PAR_TEMP_TX1_MAX,
+ LC15BTS_PAR_TEMP_TX2_MAX,
+ LC15BTS_PAR_TEMP_PA1_MAX,
+ LC15BTS_PAR_TEMP_PA2_MAX,
+ LC15BTS_PAR_SERNR,
+ LC15BTS_PAR_HOURS,
+ LC15BTS_PAR_BOOTS,
+ LC15BTS_PAR_KEY,
+ _NUM_LC15BTS_PAR
+};
+
+extern const struct value_string lc15bts_par_names[_NUM_LC15BTS_PAR+1];
+
+int lc15bts_par_get_int(enum lc15bts_par par, int *ret);
+int lc15bts_par_set_int(enum lc15bts_par par, int val);
+int lc15bts_par_get_buf(enum lc15bts_par par, uint8_t *buf,
+ unsigned int size);
+int lc15bts_par_set_buf(enum lc15bts_par par, const uint8_t *buf,
+ unsigned int size);
+
+int lc15bts_par_is_int(enum lc15bts_par par);
+
+#endif
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_power.c b/src/osmo-bts-litecell15/misc/lc15bts_power.c
new file mode 100644
index 00000000..a2997ee2
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_power.c
@@ -0,0 +1,167 @@
+/* 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 "lc15bts_power.h"
+
+#define LC15BTS_PA_VOLTAGE 24000000
+
+#define PA_SUPPLY_MIN_SYSFS "/sys/devices/0.pa-supply/min_microvolts"
+#define PA_SUPPLY_MAX_SYSFS "/sys/devices/0.pa-supply/max_microvolts"
+
+static const char *power_enable_devs[_NUM_POWER_SOURCES] = {
+ [LC15BTS_POWER_PA1] = "/sys/devices/0.pa1/state",
+ [LC15BTS_POWER_PA2] = "/sys/devices/0.pa2/state",
+};
+
+static const char *power_sensor_devs[_NUM_POWER_SOURCES] = {
+ [LC15BTS_POWER_SUPPLY] = "/sys/bus/i2c/devices/2-0040/hwmon/hwmon6/",
+ [LC15BTS_POWER_PA1] = "/sys/bus/i2c/devices/2-0044/hwmon/hwmon7/",
+ [LC15BTS_POWER_PA2] = "/sys/bus/i2c/devices/2-0045/hwmon/hwmon8/",
+};
+
+static const char *power_sensor_type_str[_NUM_POWER_TYPES] = {
+ [LC15BTS_POWER_POWER] = "power1_input",
+ [LC15BTS_POWER_VOLTAGE] = "in1_input",
+ [LC15BTS_POWER_CURRENT] = "curr1_input",
+};
+
+int lc15bts_power_sensor_get(
+ enum lc15bts_power_source source,
+ enum lc15bts_power_type type)
+{
+ 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);
+
+ return atoi(pwrstr);
+}
+
+
+int lc15bts_power_set(
+ enum lc15bts_power_source source,
+ int en)
+{
+ int fd;
+ int rc;
+
+ if ((source != LC15BTS_POWER_PA1)
+ && (source != LC15BTS_POWER_PA2) ) {
+ return -EINVAL;
+ }
+
+ fd = open(PA_SUPPLY_MAX_SYSFS, O_WRONLY);
+ if (fd < 0) {
+ return fd;
+ }
+ rc = write(fd, "32000000", 9);
+ close( fd );
+
+ if (rc != 9) {
+ return -1;
+ }
+
+ fd = open(PA_SUPPLY_MIN_SYSFS, O_WRONLY);
+ if (fd < 0) {
+ return fd;
+ }
+
+ /* TODO NTQ: Make the voltage configurable */
+ rc = write(fd, "24000000", 9);
+ close( fd );
+
+ if (rc != 9) {
+ return -1;
+ }
+
+ fd = open(power_enable_devs[source], O_WRONLY);
+ if (fd < 0) {
+ return fd;
+ }
+ rc = write(fd, en?"1":"0", 2);
+ close( fd );
+
+ if (rc != 2) {
+ return -1;
+ }
+
+ if (en) usleep(50*1000);
+
+ return 0;
+}
+
+int lc15bts_power_get(
+ enum lc15bts_power_source source)
+{
+ int fd;
+ int rc;
+ char enstr[10];
+
+ fd = open(power_enable_devs[source], O_RDONLY);
+ if (fd < 0) {
+ return fd;
+ }
+
+ rc = read(fd, enstr, sizeof(enstr));
+ enstr[sizeof(enstr)-1] = '\0';
+
+ close(fd);
+
+ if (rc < 0) {
+ return rc;
+ }
+ if (rc == 0) {
+ return -EIO;
+ }
+
+ return atoi(enstr);
+}
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_power.h b/src/osmo-bts-litecell15/misc/lc15bts_power.h
new file mode 100644
index 00000000..4bb27486
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_power.h
@@ -0,0 +1,29 @@
+#ifndef _LC15BTS_POWER_H
+#define _LC15BTS_POWER_H
+
+enum lc15bts_power_source {
+ LC15BTS_POWER_SUPPLY,
+ LC15BTS_POWER_PA1,
+ LC15BTS_POWER_PA2,
+ _NUM_POWER_SOURCES
+};
+
+enum lc15bts_power_type {
+ LC15BTS_POWER_POWER,
+ LC15BTS_POWER_VOLTAGE,
+ LC15BTS_POWER_CURRENT,
+ _NUM_POWER_TYPES
+};
+
+int lc15bts_power_sensor_get(
+ enum lc15bts_power_source source,
+ enum lc15bts_power_type type);
+
+int lc15bts_power_set(
+ enum lc15bts_power_source source,
+ int en);
+
+int lc15bts_power_get(
+ enum lc15bts_power_source source);
+
+#endif
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_temp.c b/src/osmo-bts-litecell15/misc/lc15bts_temp.c
new file mode 100644
index 00000000..fa6300e7
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_temp.c
@@ -0,0 +1,117 @@
+/* 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 "lc15bts_temp.h"
+
+
+static const char *temp_devs[_NUM_TEMP_SENSORS] = {
+ [LC15BTS_TEMP_SUPPLY] = "/sys/bus/i2c/devices/2-004d/hwmon/hwmon5/temp1_",
+ [LC15BTS_TEMP_SOC] = "/sys/class/hwmon/hwmon1/temp1_",
+ [LC15BTS_TEMP_FPGA] = "/sys/devices/0.iio_hwmon/temp1_",
+ [LC15BTS_TEMP_MEMORY] = "/sys/bus/i2c/devices/2-004c/hwmon/hwmon4/temp1_",
+ [LC15BTS_TEMP_TX1] = "/sys/devices/0.ncp15xh103_tx1/temp1_",
+ [LC15BTS_TEMP_TX2] = "/sys/devices/0.ncp15xh103_tx2/temp1_",
+ [LC15BTS_TEMP_PA1] = "/sys/bus/i2c/devices/2-004d/hwmon/hwmon5/temp2_",
+ [LC15BTS_TEMP_PA2] = "/sys/bus/i2c/devices/2-004c/hwmon/hwmon4/temp2_",
+};
+
+static const int temp_has_fault[_NUM_TEMP_SENSORS] = {
+ [LC15BTS_TEMP_PA1] = 1,
+ [LC15BTS_TEMP_PA2] = 1,
+};
+
+static const char *temp_type_str[_NUM_TEMP_TYPES] = {
+ [LC15BTS_TEMP_INPUT] = "input",
+ [LC15BTS_TEMP_LOWEST] = "lowest",
+ [LC15BTS_TEMP_HIGHEST] = "highest",
+ [LC15BTS_TEMP_FAULT] = "fault",
+};
+
+int lc15bts_temp_get(enum lc15bts_temp_sensor sensor,
+ enum lc15bts_temp_type type)
+{
+ char buf[PATH_MAX];
+ char tempstr[8];
+ char faultstr[8];
+ int fd, rc;
+
+ if (sensor < 0 || sensor >= _NUM_TEMP_SENSORS)
+ return -EINVAL;
+
+ if (type >= ARRAY_SIZE(temp_type_str))
+ return -EINVAL;
+
+ snprintf(buf, sizeof(buf)-1, "%s%s", temp_devs[sensor], temp_type_str[type]);
+ buf[sizeof(buf)-1] = '\0';
+
+ fd = open(buf, O_RDONLY);
+ if (fd < 0)
+ return fd;
+
+ rc = read(fd, tempstr, sizeof(tempstr));
+ tempstr[sizeof(tempstr)-1] = '\0';
+ if (rc < 0) {
+ close(fd);
+ return rc;
+ }
+ if (rc == 0) {
+ close(fd);
+ return -EIO;
+ }
+ close(fd);
+
+ // Check fault
+ if (type == LC15BTS_TEMP_FAULT || !temp_has_fault[sensor])
+ return atoi(tempstr);
+
+ snprintf(buf, sizeof(buf)-1, "%s%s", temp_devs[sensor], temp_type_str[LC15BTS_TEMP_FAULT]);
+ buf[sizeof(buf)-1] = '\0';
+
+ fd = open(buf, O_RDONLY);
+ if (fd < 0)
+ return fd;
+
+ rc = read(fd, faultstr, sizeof(faultstr));
+ tempstr[sizeof(faultstr)-1] = '\0';
+ if (rc < 0) {
+ close(fd);
+ return rc;
+ }
+ if (rc == 0) {
+ close(fd);
+ return -EIO;
+ }
+ close(fd);
+
+ if (atoi(faultstr))
+ return -EIO;
+
+ return atoi(tempstr);
+}
+
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_temp.h b/src/osmo-bts-litecell15/misc/lc15bts_temp.h
new file mode 100644
index 00000000..4b70cb8b
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_temp.h
@@ -0,0 +1,27 @@
+#ifndef _LC15BTS_TEMP_H
+#define _LC15BTS_TEMP_H
+
+enum lc15bts_temp_sensor {
+ LC15BTS_TEMP_SUPPLY,
+ LC15BTS_TEMP_SOC,
+ LC15BTS_TEMP_FPGA,
+ LC15BTS_TEMP_MEMORY,
+ LC15BTS_TEMP_TX1,
+ LC15BTS_TEMP_TX2,
+ LC15BTS_TEMP_PA1,
+ LC15BTS_TEMP_PA2,
+ _NUM_TEMP_SENSORS
+};
+
+enum lc15bts_temp_type {
+ LC15BTS_TEMP_INPUT,
+ LC15BTS_TEMP_LOWEST,
+ LC15BTS_TEMP_HIGHEST,
+ LC15BTS_TEMP_FAULT,
+ _NUM_TEMP_TYPES
+};
+
+int lc15bts_temp_get(enum lc15bts_temp_sensor sensor,
+ enum lc15bts_temp_type type);
+
+#endif
diff --git a/src/osmo-bts-litecell15/misc/lc15bts_util.c b/src/osmo-bts-litecell15/misc/lc15bts_util.c
new file mode 100644
index 00000000..33f9e4ef
--- /dev/null
+++ b/src/osmo-bts-litecell15/misc/lc15bts_util.c
@@ -0,0 +1,158 @@
+/* lc15bts-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 "lc15bts_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 = lc15bts_par_names;
+
+ printf("lc15bts-util [--void-warranty -r | -w value] param_name\n");
+ printf("Possible param names:\n");
+
+ for (; par->str != NULL; par += 1) {
+ if (!lc15bts_par_is_int(par->value))
+ continue;
+ printf(" %s\n", par->str);
+ }
+}
+
+static int parse_options(int argc, char **argv)
+{
+ while (1) {
+ int option_idx = 0, c;
+ static const struct option long_options[] = {
+ { "help", 0, 0, 'h' },
+ { "read", 0, 0, 'r' },
+ { "void-warranty", 0, 0, 1000},
+ { "write", 1, 0, 'w' },
+ { 0, 0, 0, 0 }
+ };
+
+ c = getopt_long(argc, argv, "rw:h",
+ long_options, &option_idx);
+ if (c == -1)
+ break;
+ switch (c) {
+ case 'r':
+ action = ACT_GET;
+ break;
+ case 'w':
+ action = ACT_SET;
+ write_arg = optarg;
+ break;
+ case 'h':
+ print_help();
+ return -1;
+ break;
+ case 1000:
+ printf("Will void warranty on write.\n");
+ void_warranty = 1;
+ break;
+ default:
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ const char *parname;
+ enum lc15bts_par par;
+ int rc, val;
+
+ rc = parse_options(argc, argv);
+ if (rc < 0)
+ exit(2);
+
+ if (optind >= argc) {
+ fprintf(stderr, "You must specify the parameter name\n");
+ exit(2);
+ }
+ parname = argv[optind];
+
+ rc = get_string_value(lc15bts_par_names, parname);
+ if (rc < 0) {
+ fprintf(stderr, "`%s' is not a valid parameter\n", parname);
+ exit(2);
+ } else
+ par = rc;
+
+ switch (action) {
+ case ACT_GET:
+ rc = lc15bts_par_get_int(par, &val);
+ if (rc < 0) {
+ fprintf(stderr, "Error %d\n", rc);
+ goto err;
+ }
+ printf("%d\n", val);
+ break;
+ case ACT_SET:
+ rc = lc15bts_par_get_int(par, &val);
+ if (rc < 0) {
+ fprintf(stderr, "Error %d\n", rc);
+ goto err;
+ }
+ if (val != 0xFFFF && val != 0xFF && val != 0xFFFFFFFF && !void_warranty) {
+ fprintf(stderr, "Parameter is already set!\r\n");
+ goto err;
+ }
+ rc = lc15bts_par_set_int(par, atoi(write_arg));
+ if (rc < 0) {
+ fprintf(stderr, "Error %d\n", rc);
+ goto err;
+ }
+ printf("Success setting %s=%d\n", parname,
+ atoi(write_arg));
+ break;
+ default:
+ fprintf(stderr, "Unsupported action\n");
+ goto err;
+ }
+
+ exit(0);
+
+err:
+ exit(1);
+}
+
diff --git a/src/osmo-bts-litecell15/oml.c b/src/osmo-bts-litecell15/oml.c
new file mode 100644
index 00000000..5670f6c1
--- /dev/null
+++ b/src/osmo-bts-litecell15/oml.c
@@ -0,0 +1,1765 @@
+/* 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/litecell15/gsml1prim.h>
+#include <nrw/litecell15/gsml1const.h>
+#include <nrw/litecell15/gsml1types.h>
+#include <nrw/litecell15/litecell15.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/handover.h>
+#include <osmo-bts/l1sap.h>
+
+#include "l1_if.h"
+#include "lc15bts.h"
+#include "utils.h"
+
+static int mph_info_chan_confirm(struct gsm_lchan *lchan,
+ enum osmo_mph_info_type type, uint8_t cause)
+{
+ struct osmo_phsap_prim l1sap;
+
+ memset(&l1sap, 0, sizeof(l1sap));
+ osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_CONFIRM,
+ NULL);
+ l1sap.u.info.type = type;
+ l1sap.u.info.u.act_cnf.chan_nr = gsm_lchan2chan_nr(lchan);
+ l1sap.u.info.u.act_cnf.cause = cause;
+
+ return l1sap_up(lchan->ts->trx, &l1sap);
+}
+
+enum sapi_cmd_type {
+ SAPI_CMD_ACTIVATE,
+ SAPI_CMD_CONFIG_CIPHERING,
+ SAPI_CMD_CONFIG_LOGCH_PARAM,
+ SAPI_CMD_SACCH_REL_MARKER,
+ SAPI_CMD_REL_MARKER,
+ SAPI_CMD_DEACTIVATE,
+};
+
+struct sapi_cmd {
+ struct llist_head entry;
+ GsmL1_Sapi_t sapi;
+ GsmL1_Dir_t dir;
+ enum sapi_cmd_type type;
+ int (*callback)(struct gsm_lchan *lchan, int status);
+};
+
+static const enum GsmL1_LogChComb_t pchan_to_logChComb[_GSM_PCHAN_MAX] = {
+ [GSM_PCHAN_NONE] = GsmL1_LogChComb_0,
+ [GSM_PCHAN_CCCH] = GsmL1_LogChComb_IV,
+ [GSM_PCHAN_CCCH_SDCCH4] = GsmL1_LogChComb_V,
+ [GSM_PCHAN_CCCH_SDCCH4_CBCH] = GsmL1_LogChComb_V,
+ [GSM_PCHAN_TCH_F] = GsmL1_LogChComb_I,
+ [GSM_PCHAN_TCH_H] = GsmL1_LogChComb_II,
+ [GSM_PCHAN_SDCCH8_SACCH8C] = GsmL1_LogChComb_VII,
+ [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = GsmL1_LogChComb_VII,
+ [GSM_PCHAN_PDCH] = GsmL1_LogChComb_XIII,
+ //[GSM_PCHAN_TCH_F_PDCH] = FIXME,
+ [GSM_PCHAN_UNKNOWN] = GsmL1_LogChComb_0,
+};
+
+static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb);
+
+static void *prim_init(GsmL1_Prim_t *prim, GsmL1_PrimId_t id, struct lc15l1_hdl *gl1)
+{
+ prim->id = id;
+
+ switch (id) {
+ case GsmL1_PrimId_MphInitReq:
+ //prim->u.mphInitReq.hLayer1 = (HANDLE)gl1->hLayer1;
+ break;
+ case GsmL1_PrimId_MphCloseReq:
+ prim->u.mphCloseReq.hLayer1 = (HANDLE)gl1->hLayer1;
+ break;
+ case GsmL1_PrimId_MphConnectReq:
+ prim->u.mphConnectReq.hLayer1 = (HANDLE)gl1->hLayer1;
+ break;
+ case GsmL1_PrimId_MphDisconnectReq:
+ prim->u.mphDisconnectReq.hLayer1 = (HANDLE)gl1->hLayer1;
+ break;
+ case GsmL1_PrimId_MphActivateReq:
+ prim->u.mphActivateReq.hLayer1 = (HANDLE)gl1->hLayer1;
+ break;
+ case GsmL1_PrimId_MphDeactivateReq:
+ prim->u.mphDeactivateReq.hLayer1 = (HANDLE)gl1->hLayer1;
+ break;
+ case GsmL1_PrimId_MphConfigReq:
+ prim->u.mphConfigReq.hLayer1 = (HANDLE)gl1->hLayer1;
+ break;
+ case GsmL1_PrimId_MphMeasureReq:
+ prim->u.mphMeasureReq.hLayer1 = (HANDLE)gl1->hLayer1;
+ break;
+ case GsmL1_PrimId_MphInitCnf:
+ case GsmL1_PrimId_MphCloseCnf:
+ case GsmL1_PrimId_MphConnectCnf:
+ case GsmL1_PrimId_MphDisconnectCnf:
+ case GsmL1_PrimId_MphActivateCnf:
+ case GsmL1_PrimId_MphDeactivateCnf:
+ case GsmL1_PrimId_MphConfigCnf:
+ case GsmL1_PrimId_MphMeasureCnf:
+ break;
+ case GsmL1_PrimId_MphTimeInd:
+ break;
+ case GsmL1_PrimId_MphSyncInd:
+ break;
+ case GsmL1_PrimId_PhEmptyFrameReq:
+ prim->u.phEmptyFrameReq.hLayer1 = (HANDLE)gl1->hLayer1;
+ break;
+ case GsmL1_PrimId_PhDataReq:
+ prim->u.phDataReq.hLayer1 = (HANDLE)gl1->hLayer1;
+ break;
+ case GsmL1_PrimId_PhConnectInd:
+ break;
+ case GsmL1_PrimId_PhReadyToSendInd:
+ break;
+ case GsmL1_PrimId_PhDataInd:
+ break;
+ case GsmL1_PrimId_PhRaInd:
+ break;
+ default:
+ LOGP(DL1C, LOGL_ERROR, "unknown L1 primitive %u\n", id);
+ break;
+ }
+ return &prim->u;
+}
+
+GsmL1_Status_t prim_status(GsmL1_Prim_t *prim)
+{
+ switch (prim->id) {
+ case GsmL1_PrimId_MphInitCnf:
+ return prim->u.mphInitCnf.status;
+ case GsmL1_PrimId_MphCloseCnf:
+ return prim->u.mphCloseCnf.status;
+ case GsmL1_PrimId_MphConnectCnf:
+ return prim->u.mphConnectCnf.status;
+ case GsmL1_PrimId_MphDisconnectCnf:
+ return prim->u.mphDisconnectCnf.status;
+ case GsmL1_PrimId_MphActivateCnf:
+ return prim->u.mphActivateCnf.status;
+ case GsmL1_PrimId_MphDeactivateCnf:
+ return prim->u.mphDeactivateCnf.status;
+ case GsmL1_PrimId_MphConfigCnf:
+ return prim->u.mphConfigCnf.status;
+ case GsmL1_PrimId_MphMeasureCnf:
+ return prim->u.mphMeasureCnf.status;
+ default:
+ break;
+ }
+ return GsmL1_Status_Success;
+}
+
+#if 0
+static int compl_cb_send_oml_msg(struct msgb *l1_msg, void *data)
+{
+ struct msgb *resp_msg = data;
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+
+ if (prim_status(l1p) != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_ERROR, "Rx %s, status: %s\n",
+ get_value_string(lc15bts_l1prim_names, l1p->id),
+ get_value_string(lc15bts_l1status_names, cc->status));
+ return 0;
+ }
+
+ msgb_free(l1_msg);
+
+ return abis_nm_sendmsg(msg);
+}
+#endif
+
+int lchan_activate(struct gsm_lchan *lchan);
+
+static int opstart_compl(struct gsm_abis_mo *mo, struct msgb *l1_msg)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_Status_t status = prim_status(l1p);
+
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_ERROR, "Rx %s, status: %s\n",
+ get_value_string(lc15bts_l1prim_names, l1p->id),
+ get_value_string(lc15bts_l1status_names, status));
+ msgb_free(l1_msg);
+ return oml_mo_opstart_nack(mo, NM_NACK_CANT_PERFORM);
+ }
+
+ msgb_free(l1_msg);
+
+ /* Set to Operational State: Enabled */
+ oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK);
+
+ /* ugly hack to auto-activate all SAPIs for the BCCH/CCCH on TS0 */
+ if (mo->obj_class == NM_OC_CHANNEL && mo->obj_inst.trx_nr == 0 &&
+ mo->obj_inst.ts_nr == 0) {
+ struct gsm_lchan *cbch = gsm_bts_get_cbch(mo->bts);
+ DEBUGP(DL1C, "====> trying to activate lchans of BCCH\n");
+ mo->bts->c0->ts[0].lchan[4].rel_act_kind = LCHAN_REL_ACT_OML;
+ lchan_activate(&mo->bts->c0->ts[0].lchan[4]);
+ if (cbch) {
+ cbch->rel_act_kind = LCHAN_REL_ACT_OML;
+ lchan_activate(cbch);
+ }
+ }
+
+ /* Send OPSTART ack */
+ return oml_mo_opstart_ack(mo);
+}
+
+static int opstart_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ struct gsm_abis_mo *mo;
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphConnectCnf_t *cnf = &l1p->u.mphConnectCnf;
+
+ mo = &trx->ts[cnf->u8Tn].mo;
+ return opstart_compl(mo, l1_msg);
+}
+
+static int trx_mute_on_init_cb(struct gsm_bts_trx *trx, struct msgb *resp,
+ void *data)
+{
+ Litecell15_Prim_t *sysp = msgb_sysprim(resp);
+ GsmL1_Status_t status;
+
+ status = sysp->u.muteRfCnf.status;
+
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_FATAL, "Rx RF-MUTE.conf status=%s\n",
+ get_value_string(lc15bts_l1status_names, status));
+ bts_shutdown(trx->bts, "RF-MUTE failure");
+ }
+
+ msgb_free(resp);
+
+ return 0;
+}
+
+static int trx_init_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphInitCnf_t *ic = &l1p->u.mphInitCnf;
+
+ LOGP(DL1C, LOGL_INFO, "Rx MPH-INIT.conf (status=%s)\n",
+ get_value_string(lc15bts_l1status_names, ic->status));
+
+ /* store layer1 handle */
+ if (ic->status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_FATAL, "Rx MPH-INIT.conf status=%s\n",
+ get_value_string(lc15bts_l1status_names, ic->status));
+ bts_shutdown(trx->bts, "MPH-INIT failure");
+ }
+
+ fl1h->hLayer1 = (uint32_t)ic->hLayer1;
+
+ /* If the TRX was already locked the MphInit would have undone it */
+ if (trx->mo.nm_state.administrative == NM_STATE_LOCKED)
+ trx_rf_lock(trx, 1, trx_mute_on_init_cb);
+
+ /* Begin to ramp up the power */
+ power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0);
+
+ return opstart_compl(&trx->mo, l1_msg);
+}
+
+int gsm_abis_mo_check_attr(const struct gsm_abis_mo *mo, const uint8_t *attr_ids,
+ unsigned int num_attr_ids)
+{
+ unsigned int i;
+
+ if (!mo->nm_attr)
+ return 0;
+
+ for (i = 0; i < num_attr_ids; i++) {
+ if (!TLVP_PRESENT(mo->nm_attr, attr_ids[i]))
+ return 0;
+ }
+ return 1;
+}
+
+static const uint8_t trx_rqd_attr[] = { NM_ATT_RF_MAXPOWR_R };
+
+/* initialize the layer1 */
+static int trx_init(struct gsm_bts_trx *trx)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+ struct gsm_bts_role_bts *btsb = bts_role_bts(trx->bts);
+ struct msgb *msg;
+ GsmL1_MphInitReq_t *mi_req;
+ GsmL1_DeviceParam_t *dev_par;
+ int lc15_band;
+
+ if (!gsm_abis_mo_check_attr(&trx->mo, trx_rqd_attr,
+ ARRAY_SIZE(trx_rqd_attr))) {
+ /* HACK: spec says we need to decline, but openbsc
+ * doesn't deal with this very well */
+ return oml_mo_opstart_ack(&trx->mo);
+ //return oml_mo_opstart_nack(&trx->mo, NM_NACK_CANT_PERFORM);
+ }
+
+ lc15_band = lc15bts_select_lc15_band(trx, trx->arfcn);
+ if (lc15_band < 0) {
+ LOGP(DL1C, LOGL_ERROR, "Unsupported GSM band %s\n",
+ gsm_band_name(trx->bts->band));
+ }
+
+ msg = l1p_msgb_alloc();
+ mi_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphInitReq, fl1h);
+ dev_par = &mi_req->deviceParam;
+ dev_par->devType = GsmL1_DevType_TxdRxu;
+ dev_par->freqBand = lc15_band;
+ dev_par->u16Arfcn = trx->arfcn;
+ dev_par->u16BcchArfcn = trx->bts->c0->arfcn;
+ dev_par->u8NbTsc = trx->bts->bsic & 7;
+ dev_par->fRxPowerLevel = trx_ms_pwr_ctrl_is_osmo(trx)
+ ? 0.0 : btsb->ul_power_target;
+
+ dev_par->fTxPowerLevel = 0.0;
+ LOGP(DL1C, LOGL_NOTICE, "Init TRX (Band %d, ARFCN %u, TSC %u, RxPower % 2f dBm, "
+ "TxPower % 2.2f dBm\n", dev_par->freqBand, dev_par->u16Arfcn, dev_par->u8NbTsc,
+ dev_par->fRxPowerLevel, dev_par->fTxPowerLevel);
+
+ /* send MPH-INIT-REQ, wait for MPH-INIT-CNF */
+ return l1if_gsm_req_compl(fl1h, msg, trx_init_compl_cb, NULL);
+}
+
+uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+
+ return fl1h->hLayer1;
+}
+
+static int trx_close_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ msgb_free(l1_msg);
+ return 0;
+}
+
+int bts_model_trx_close(struct gsm_bts_trx *trx)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+ struct msgb *msg;
+
+ msg = l1p_msgb_alloc();
+ prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphCloseReq, fl1h);
+ LOGP(DL1C, LOGL_NOTICE, "Close TRX %u\n", trx->nr);
+
+ return l1if_gsm_req_compl(fl1h, msg, trx_close_compl_cb, NULL);
+}
+
+static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+ uint8_t mute[8];
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mute); ++i)
+ mute[i] = locked ? 1 : 0;
+
+ return l1if_mute_rf(fl1h, mute, cb);
+}
+
+int oml_mo_rf_lock_chg(struct gsm_abis_mo *mo, uint8_t mute_state[8],
+ int success)
+{
+ if (success) {
+ int i;
+ int is_locked = 1;
+
+ for (i = 0; i < 8; ++i)
+ if (!mute_state[i])
+ is_locked = 0;
+
+ mo->nm_state.administrative =
+ is_locked ? NM_STATE_LOCKED : NM_STATE_UNLOCKED;
+ mo->procedure_pending = 0;
+ return oml_mo_statechg_ack(mo);
+ } else {
+ mo->procedure_pending = 0;
+ return oml_mo_statechg_nack(mo, NM_NACK_REQ_NOT_GRANT);
+ }
+}
+
+static int ts_connect(struct gsm_bts_trx_ts *ts)
+{
+ struct msgb *msg = l1p_msgb_alloc();
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(ts->trx);
+ GsmL1_MphConnectReq_t *cr;
+
+ cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConnectReq, fl1h);
+ cr->u8Tn = ts->nr;
+ cr->logChComb = pchan_to_logChComb[ts->pchan];
+
+ return l1if_gsm_req_compl(fl1h, msg, 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)
+{
+ switch (lchan->ts->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:
+ 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_NOTICE,
+ "%s End of queue encountered. Now empty? %d\n",
+ gsm_lchan_name(lchan), llist_empty(&lchan->sapi_cmds));
+ return;
+ }
+
+ sapi_queue_send(lchan);
+}
+
+/**
+ * Queue and possible execute a SAPI command. Return 1 in case the command was
+ * already executed and 0 in case if it was only put into the queue
+ */
+static int queue_sapi_command(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ int start = llist_empty(&lchan->sapi_cmds);
+ llist_add_tail(&cmd->entry, &lchan->sapi_cmds);
+
+ if (!start)
+ return 0;
+
+ sapi_queue_send(lchan);
+ return 1;
+}
+
+static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ enum lchan_sapi_state status;
+ struct sapi_cmd *cmd;
+ struct gsm_lchan *lchan;
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphActivateCnf_t *ic = &l1p->u.mphActivateCnf;
+
+ /* get the lchan from the information we supplied */
+ lchan = l1if_hLayer_to_lchan(trx, (uint32_t)ic->hLayer3);
+ if (!lchan) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Failed to find lchan for hLayer3=0x%x\n", (uint32_t)ic->hLayer3);
+ goto err;
+ }
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.conf (%s ",
+ gsm_lchan_name(lchan),
+ get_value_string(lc15bts_l1sapi_names, ic->sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(lc15bts_dir_names, ic->dir));
+
+ if (ic->status == GsmL1_Status_Success) {
+ DEBUGP(DL1C, "Successful activation of L1 SAPI %s on TS %u\n",
+ get_value_string(lc15bts_l1sapi_names, ic->sapi), ic->u8Tn);
+ status = LCHAN_SAPI_S_ASSIGNED;
+ } else {
+ LOGP(DL1C, LOGL_ERROR, "Error activating L1 SAPI %s on TS %u: %s\n",
+ get_value_string(lc15bts_l1sapi_names, ic->sapi), ic->u8Tn,
+ get_value_string(lc15bts_l1status_names, ic->status));
+ status = LCHAN_SAPI_S_ERROR;
+ }
+
+ if (ic->dir & GsmL1_Dir_TxDownlink)
+ lchan->sapis_dl[ic->sapi] = status;
+ if (ic->dir & GsmL1_Dir_RxUplink)
+ lchan->sapis_ul[ic->sapi] = status;
+
+ if (llist_empty(&lchan->sapi_cmds)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Got activation confirmation with empty queue\n",
+ gsm_lchan_name(lchan));
+ goto err;
+ }
+
+ cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry);
+ if (cmd->sapi != ic->sapi || cmd->dir != ic->dir ||
+ cmd->type != SAPI_CMD_ACTIVATE) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Confirmation mismatch (%d, %d) (%d, %d)\n",
+ gsm_lchan_name(lchan), cmd->sapi, cmd->dir,
+ ic->sapi, ic->dir);
+ goto err;
+ }
+
+ sapi_queue_dispatch(lchan, ic->status);
+
+err:
+ msgb_free(l1_msg);
+
+ return 0;
+}
+
+uint32_t l1if_lchan_to_hLayer(struct gsm_lchan *lchan)
+{
+ return 0xBB
+ | (lchan->nr << 8)
+ | (lchan->ts->nr << 16)
+ | (lchan->ts->trx->nr << 24);
+}
+
+/* obtain a ptr to the lapdm_channel for a given hLayer */
+struct gsm_lchan *
+l1if_hLayer_to_lchan(struct gsm_bts_trx *trx, uint32_t hLayer2)
+{
+ uint8_t magic = hLayer2 & 0xff;
+ uint8_t ts_nr = (hLayer2 >> 16) & 0xff;
+ uint8_t lchan_nr = (hLayer2 >> 8)& 0xff;
+ struct gsm_bts_trx_ts *ts;
+
+ if (magic != 0xBB)
+ return NULL;
+
+ /* FIXME: if we actually run on the BTS, the 32bit field is large
+ * enough to simply put a pointer inside. */
+ if (ts_nr >= ARRAY_SIZE(trx->ts))
+ return NULL;
+
+ ts = &trx->ts[ts_nr];
+
+ if (lchan_nr >= ARRAY_SIZE(ts->lchan))
+ return NULL;
+
+ return &ts->lchan[lchan_nr];
+}
+
+/* we regularly check if the DSP L1 is still sending us primitives.
+ * if not, we simply stop the BTS program (and be re-spawned) */
+static void alive_timer_cb(void *data)
+{
+ struct lc15l1_hdl *fl1h = data;
+
+ if (fl1h->alive_prim_cnt == 0) {
+ LOGP(DL1C, LOGL_FATAL, "DSP L1 is no longer sending primitives!\n");
+ exit(23);
+ }
+ fl1h->alive_prim_cnt = 0;
+ osmo_timer_schedule(&fl1h->alive_timer, 5, 0);
+}
+
+static void clear_amr_params(GsmL1_LogChParam_t *lch_par)
+{
+ int j;
+ /* common for the SIGN, V1 and EFR: */
+ lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_NA;
+ lch_par->tch.amrInitCodecMode = GsmL1_AmrCodecMode_Unset;
+ for (j = 0; j < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); j++)
+ lch_par->tch.amrActiveCodecSet[j] = GsmL1_AmrCodec_Unset;
+}
+
+static void set_payload_format(GsmL1_LogChParam_t *lch_par)
+{
+ lch_par->tch.tchPlFmt = GsmL1_TchPlFmt_Rtp;
+}
+
+static void lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan)
+{
+ struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr;
+ struct gsm48_multi_rate_conf *mr_conf =
+ (struct gsm48_multi_rate_conf *) amr_mrc->gsm48_ie;
+ int j;
+
+ LOGP(DL1C, LOGL_INFO, "%s: %s tch_mode=0x%02x\n",
+ gsm_lchan_name(lchan), __FUNCTION__, lchan->tch_mode);
+
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SIGN:
+ /* we have to set some TCH payload type even if we don't
+ * know yet what codec we will use later on */
+ if (lchan->type == GSM_LCHAN_TCH_F)
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Fr;
+ else
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Hr;
+ clear_amr_params(lch_par);
+ break;
+ case GSM48_CMODE_SPEECH_V1:
+ if (lchan->type == GSM_LCHAN_TCH_F)
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Fr;
+ else
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Hr;
+ set_payload_format(lch_par);
+ clear_amr_params(lch_par);
+ break;
+ case GSM48_CMODE_SPEECH_EFR:
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Efr;
+ set_payload_format(lch_par);
+ clear_amr_params(lch_par);
+ break;
+ case GSM48_CMODE_SPEECH_AMR:
+ lch_par->tch.tchPlType = GsmL1_TchPlType_Amr;
+ set_payload_format(lch_par);
+ lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_Odd; /* FIXME? */
+ lch_par->tch.amrInitCodecMode = amr_get_initial_mode(lchan);
+
+ /* initialize to clean state */
+ for (j = 0; j < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); j++)
+ lch_par->tch.amrActiveCodecSet[j] = GsmL1_AmrCodec_Unset;
+
+ j = 0;
+ if (mr_conf->m4_75)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_4_75;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m5_15)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_5_15;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m5_90)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_5_9;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m6_70)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_6_7;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m7_40)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_7_4;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m7_95)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_7_95;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+
+ if (mr_conf->m10_2)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_10_2;
+ if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet))
+ break;
+ if (mr_conf->m12_2)
+ lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_12_2;
+ break;
+ case GSM48_CMODE_DATA_14k5:
+ case GSM48_CMODE_DATA_12k0:
+ case GSM48_CMODE_DATA_6k0:
+ case GSM48_CMODE_DATA_3k6:
+ LOGP(DL1C, LOGL_ERROR, "%s: CSD not supported!\n",
+ gsm_lchan_name(lchan));
+ break;
+ }
+}
+
+static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(lchan->ts->trx);
+ struct msgb *msg = l1p_msgb_alloc();
+ int sapi = cmd->sapi;
+ int dir = cmd->dir;
+ GsmL1_MphActivateReq_t *act_req;
+ GsmL1_LogChParam_t *lch_par;
+
+ act_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphActivateReq, fl1h);
+ 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:
+#warning Set BS_AG_BLKS_RES
+ lch_par->agch.u8NbrOfAgch = 1;
+ break;
+ case GsmL1_Sapi_TchH:
+ case GsmL1_Sapi_TchF:
+ lchan2lch_par(lch_par, lchan);
+ break;
+ case GsmL1_Sapi_Ptcch:
+ lch_par->ptcch.u8Bsic = lchan->ts->trx->bts->bsic;
+ break;
+ case GsmL1_Sapi_Prach:
+ lch_par->prach.u8Bsic = lchan->ts->trx->bts->bsic;
+ break;
+ case GsmL1_Sapi_Sacch:
+ /*
+ * For the SACCH we need to set the u8MsPowerLevel when
+ * doing manual MS power control.
+ */
+ if (trx_ms_pwr_ctrl_is_osmo(lchan->ts->trx))
+ lch_par->sacch.u8MsPowerLevel = lchan->ms_power_ctrl.current;
+ /* fall through */
+ case GsmL1_Sapi_Pdtch:
+ case GsmL1_Sapi_Pacch:
+ /*
+ * Be sure that every packet is received, even if it
+ * fails. In this case the length might be lower or 0.
+ */
+ act_req->fBFILevel = -200.0f;
+ break;
+ default:
+ break;
+ }
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.req (hL2=0x%08x, %s ",
+ gsm_lchan_name(lchan), (uint32_t)act_req->hLayer2,
+ get_value_string(lc15bts_l1sapi_names, act_req->sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(lc15bts_dir_names, act_req->dir));
+
+ /* send the primitive for all GsmL1_Sapi_* that match the LCHAN */
+ return l1if_gsm_req_compl(fl1h, msg, lchan_act_compl_cb, NULL);
+}
+
+static void sapi_clear_queue(struct llist_head *queue)
+{
+ struct sapi_cmd *next, *tmp;
+
+ llist_for_each_entry_safe(next, tmp, queue, entry) {
+ llist_del(&next->entry);
+ talloc_free(next);
+ }
+}
+
+static int sapi_activate_cb(struct gsm_lchan *lchan, int status)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(lchan->ts->trx);
+
+ /* FIXME: Error handling */
+ if (status != GsmL1_Status_Success) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s act failed mark broken due status: %d\n",
+ gsm_lchan_name(lchan), status);
+ lchan_set_state(lchan, LCHAN_S_BROKEN);
+ sapi_clear_queue(&lchan->sapi_cmds);
+ mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, RSL_ERR_PROCESSOR_OVERLOAD);
+ return -1;
+ }
+
+ if (!llist_empty(&lchan->sapi_cmds))
+ return 0;
+
+ if (lchan->state != LCHAN_S_ACT_REQ)
+ return 0;
+
+ lchan_set_state(lchan, LCHAN_S_ACTIVE);
+ mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, 0);
+
+ /* set the initial ciphering parameters for both directions */
+ l1if_set_ciphering(fl1h, lchan, 1);
+ l1if_set_ciphering(fl1h, lchan, 0);
+ if (lchan->encr.alg_id)
+ lchan->ciph_state = LCHAN_CIPH_RXTX_REQ;
+ else
+ lchan->ciph_state = LCHAN_CIPH_NONE;
+
+ return 0;
+}
+
+static void enqueue_sapi_act_cmd(struct gsm_lchan *lchan, int sapi, int dir)
+{
+ struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+
+ cmd->sapi = sapi;
+ cmd->dir = dir;
+ cmd->type = SAPI_CMD_ACTIVATE;
+ cmd->callback = sapi_activate_cb;
+ queue_sapi_command(lchan, cmd);
+}
+
+int lchan_activate(struct gsm_lchan *lchan)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(lchan->ts->trx);
+ const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type];
+ unsigned int i;
+
+ lchan_set_state(lchan, LCHAN_S_ACT_REQ);
+
+ if (!llist_empty(&lchan->sapi_cmds))
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Trying to activate lchan, but commands in queue\n",
+ gsm_lchan_name(lchan));
+
+ /* override the regular SAPIs if this is the first hand-over
+ * related activation of the LCHAN */
+ if (lchan->ho.active == HANDOVER_ENABLED)
+ s4l = &sapis_for_ho;
+
+ for (i = 0; i < s4l->num_sapis; i++) {
+ int sapi = s4l->sapis[i].sapi;
+ int dir = s4l->sapis[i].dir;
+
+ if (sapi == GsmL1_Sapi_Sch) {
+ /* once we activate the SCH, we should get MPH-TIME.ind */
+ fl1h->alive_timer.cb = alive_timer_cb;
+ fl1h->alive_timer.data = fl1h;
+ fl1h->alive_prim_cnt = 0;
+ osmo_timer_schedule(&fl1h->alive_timer, 5, 0);
+ }
+ enqueue_sapi_act_cmd(lchan, sapi, dir);
+ }
+
+#warning "FIXME: Should this be in sapi_activate_cb?"
+ lchan_init_lapdm(lchan);
+
+ return 0;
+}
+
+const struct value_string lc15bts_l1cfgt_names[] = {
+ { GsmL1_ConfigParamId_SetNbTsc, "Set NB TSC" },
+ { GsmL1_ConfigParamId_SetTxPowerLevel, "Set Tx power level" },
+ { GsmL1_ConfigParamId_SetLogChParams, "Set logical channel params" },
+ { GsmL1_ConfigParamId_SetCipheringParams,"Configure ciphering params" },
+ { 0, NULL }
+};
+
+static void dump_lch_par(int logl, GsmL1_LogChParam_t *lch_par, GsmL1_Sapi_t sapi)
+{
+ int i;
+
+ switch (sapi) {
+ case GsmL1_Sapi_Rach:
+ LOGPC(DL1C, logl, "BSIC=0x%08x", lch_par->rach.u8Bsic);
+ break;
+ case GsmL1_Sapi_Agch:
+ LOGPC(DL1C, logl, "BS_AG_BLKS_RES=%u ",
+ lch_par->agch.u8NbrOfAgch);
+ break;
+ case GsmL1_Sapi_Sacch:
+ LOGPC(DL1C, logl, "MS Power Level 0x%02x",
+ lch_par->sacch.u8MsPowerLevel);
+ break;
+ case GsmL1_Sapi_TchF:
+ case GsmL1_Sapi_TchH:
+ LOGPC(DL1C, logl, "amrCmiPhase=0x%02x amrInitCodec=0x%02x (",
+ lch_par->tch.amrCmiPhase,
+ lch_par->tch.amrInitCodecMode);
+ for (i = 0; i < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); i++) {
+ LOGPC(DL1C, logl, "%x ",
+ lch_par->tch.amrActiveCodecSet[i]);
+ }
+ break;
+ /* FIXME: PRACH / PTCCH */
+ default:
+ break;
+ }
+ LOGPC(DL1C, logl, ")\n");
+}
+
+static int chmod_txpower_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf;
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ",
+ gsm_trx_name(trx),
+ get_value_string(lc15bts_l1cfgt_names, cc->cfgParamId));
+
+ LOGPC(DL1C, LOGL_INFO, "setTxPower %f dBm\n",
+ cc->cfgParams.setTxPowerLevel.fTxPowerLevel);
+
+ power_trx_change_compl(trx,
+ (int) (cc->cfgParams.setTxPowerLevel.fTxPowerLevel * 1000));
+
+ msgb_free(l1_msg);
+
+ return 0;
+}
+
+static int chmod_modif_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ struct gsm_lchan *lchan;
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf;
+
+ /* get the lchan from the information we supplied */
+ lchan = l1if_hLayer_to_lchan(trx, (uint32_t)cc->hLayer3);
+ if (!lchan) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Failed to find lchan for hLayer3=0x%x\n", (uint32_t)cc->hLayer3);
+ goto err;
+ }
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ",
+ gsm_lchan_name(lchan),
+ get_value_string(lc15bts_l1cfgt_names, cc->cfgParamId));
+
+ switch (cc->cfgParamId) {
+ case GsmL1_ConfigParamId_SetLogChParams:
+ dump_lch_par(LOGL_INFO,
+ &cc->cfgParams.setLogChParams.logChParams,
+ cc->cfgParams.setLogChParams.sapi);
+
+ sapi_queue_dispatch(lchan, cc->status);
+ break;
+ case GsmL1_ConfigParamId_SetCipheringParams:
+ switch (lchan->ciph_state) {
+ case LCHAN_CIPH_RX_REQ:
+ LOGPC(DL1C, LOGL_INFO, "RX_REQ -> RX_CONF\n");
+ lchan->ciph_state = LCHAN_CIPH_RX_CONF;
+ break;
+ case LCHAN_CIPH_RX_CONF_TX_REQ:
+ LOGPC(DL1C, LOGL_INFO, "RX_CONF_TX_REQ -> RXTX_CONF\n");
+ lchan->ciph_state = LCHAN_CIPH_RXTX_CONF;
+ break;
+ case LCHAN_CIPH_RXTX_REQ:
+ LOGPC(DL1C, LOGL_INFO, "RXTX_REQ -> RX_CONF_TX_REQ\n");
+ lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ;
+ break;
+ case LCHAN_CIPH_NONE:
+ LOGPC(DL1C, LOGL_INFO, "\n");
+ break;
+ default:
+ LOGPC(DL1C, LOGL_INFO, "unhandled state %u\n", lchan->ciph_state);
+ break;
+ }
+ if (llist_empty(&lchan->sapi_cmds)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Got ciphering conf with empty queue\n",
+ gsm_lchan_name(lchan));
+ goto err;
+ }
+
+ sapi_queue_dispatch(lchan, cc->status);
+ break;
+ case GsmL1_ConfigParamId_SetNbTsc:
+ default:
+ LOGPC(DL1C, LOGL_INFO, "\n");
+ break;
+ }
+
+err:
+ msgb_free(l1_msg);
+
+ return 0;
+}
+
+static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ struct gsm_bts_trx *trx = lchan->ts->trx;
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+ struct msgb *msg = l1p_msgb_alloc();
+ GsmL1_MphConfigReq_t *conf_req;
+ GsmL1_LogChParam_t *lch_par;
+
+ /* channel mode, encryption and/or multirate have changed */
+
+ /* update multi-rate config */
+ conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h);
+ conf_req->cfgParamId = GsmL1_ConfigParamId_SetLogChParams;
+ conf_req->cfgParams.setLogChParams.sapi = cmd->sapi;
+ conf_req->cfgParams.setLogChParams.u8Tn = lchan->ts->nr;
+ conf_req->cfgParams.setLogChParams.subCh = lchan_to_GsmL1_SubCh_t(lchan);
+ conf_req->cfgParams.setLogChParams.dir = cmd->dir;
+ conf_req->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan);
+
+ lch_par = &conf_req->cfgParams.setLogChParams.logChParams;
+ lchan2lch_par(lch_par, lchan);
+
+ /* Update the MS Power Level */
+ if (cmd->sapi == GsmL1_Sapi_Sacch && trx_ms_pwr_ctrl_is_osmo(trx))
+ lch_par->sacch.u8MsPowerLevel = lchan->ms_power_ctrl.current;
+
+ /* FIXME: update encryption */
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.req (%s) ",
+ gsm_lchan_name(lchan),
+ get_value_string(lc15bts_l1sapi_names,
+ conf_req->cfgParams.setLogChParams.sapi));
+ LOGPC(DL1C, LOGL_INFO, "cfgParams Tn=%u, subCh=%u, dir=0x%x ",
+ conf_req->cfgParams.setLogChParams.u8Tn,
+ conf_req->cfgParams.setLogChParams.subCh,
+ conf_req->cfgParams.setLogChParams.dir);
+ dump_lch_par(LOGL_INFO,
+ &conf_req->cfgParams.setLogChParams.logChParams,
+ conf_req->cfgParams.setLogChParams.sapi);
+
+ return l1if_gsm_req_compl(fl1h, msg, chmod_modif_compl_cb, NULL);
+}
+
+static void enqueue_sapi_logchpar_cmd(struct gsm_lchan *lchan, int dir, GsmL1_Sapi_t sapi)
+{
+ struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+
+ cmd->dir = dir;
+ cmd->sapi = sapi;
+ cmd->type = SAPI_CMD_CONFIG_LOGCH_PARAM;
+ queue_sapi_command(lchan, cmd);
+}
+
+static int tx_confreq_logchpar(struct gsm_lchan *lchan, uint8_t direction)
+{
+ enqueue_sapi_logchpar_cmd(lchan, direction, lchan_to_GsmL1_Sapi_t(lchan));
+ return 0;
+}
+
+int l1if_set_txpower(struct lc15l1_hdl *fl1h, float tx_power)
+{
+ struct msgb *msg = l1p_msgb_alloc();
+ GsmL1_MphConfigReq_t *conf_req;
+
+ conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h);
+ conf_req->cfgParamId = GsmL1_ConfigParamId_SetTxPowerLevel;
+ conf_req->cfgParams.setTxPowerLevel.fTxPowerLevel = tx_power;
+
+ return l1if_gsm_req_compl(fl1h, msg, chmod_txpower_compl_cb, NULL);
+}
+
+const enum GsmL1_CipherId_t rsl2l1_ciph[] = {
+ [0] = GsmL1_CipherId_A50,
+ [1] = GsmL1_CipherId_A50,
+ [2] = GsmL1_CipherId_A51,
+ [3] = GsmL1_CipherId_A52,
+ [4] = GsmL1_CipherId_A53,
+};
+
+static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(lchan->ts->trx);
+ struct msgb *msg = l1p_msgb_alloc();
+ struct GsmL1_MphConfigReq_t *cfgr;
+
+ cfgr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h);
+
+ cfgr->cfgParamId = GsmL1_ConfigParamId_SetCipheringParams;
+ cfgr->cfgParams.setCipheringParams.u8Tn = lchan->ts->nr;
+ cfgr->cfgParams.setCipheringParams.subCh = lchan_to_GsmL1_SubCh_t(lchan);
+ cfgr->cfgParams.setCipheringParams.dir = cmd->dir;
+ cfgr->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan);
+
+ if (lchan->encr.alg_id >= ARRAY_SIZE(rsl2l1_ciph))
+ return -EINVAL;
+ cfgr->cfgParams.setCipheringParams.cipherId = rsl2l1_ciph[lchan->encr.alg_id];
+
+ LOGP(DL1C, LOGL_NOTICE, "%s SET_CIPHERING (ALG=%u %s)\n",
+ gsm_lchan_name(lchan),
+ cfgr->cfgParams.setCipheringParams.cipherId,
+ get_value_string(lc15bts_dir_names,
+ cfgr->cfgParams.setCipheringParams.dir));
+
+ memcpy(cfgr->cfgParams.setCipheringParams.u8Kc,
+ lchan->encr.key, lchan->encr.key_len);
+
+ return l1if_gsm_req_compl(fl1h, msg, chmod_modif_compl_cb, NULL);
+}
+
+static void enqueue_sapi_ciphering_cmd(struct gsm_lchan *lchan, int dir)
+{
+ struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+
+ cmd->dir = dir;
+ cmd->type = SAPI_CMD_CONFIG_CIPHERING;
+ queue_sapi_command(lchan, cmd);
+}
+
+int l1if_set_ciphering(struct lc15l1_hdl *fl1h,
+ struct gsm_lchan *lchan,
+ int dir_downlink)
+{
+ int dir;
+
+ /* ignore the request when the channel is not active */
+ if (lchan->state != LCHAN_S_ACTIVE)
+ return -1;
+
+ if (dir_downlink)
+ dir = GsmL1_Dir_TxDownlink;
+ else
+ dir = GsmL1_Dir_RxUplink;
+
+ enqueue_sapi_ciphering_cmd(lchan, dir);
+
+ return 0;
+}
+
+int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan)
+{
+ if (lchan->state != LCHAN_S_ACTIVE)
+ return -1;
+
+ enqueue_sapi_logchpar_cmd(lchan, GsmL1_Dir_RxUplink, GsmL1_Sapi_Sacch);
+ return 0;
+}
+
+int l1if_rsl_mode_modify(struct gsm_lchan *lchan)
+{
+ if (lchan->state != LCHAN_S_ACTIVE)
+ return -1;
+
+ /* channel mode, encryption and/or multirate have changed */
+
+ /* update multi-rate config */
+ tx_confreq_logchpar(lchan, GsmL1_Dir_RxUplink);
+ tx_confreq_logchpar(lchan, GsmL1_Dir_TxDownlink);
+
+ /* FIXME: update encryption */
+
+ return 0;
+}
+
+static int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
+ void *data)
+{
+ enum lchan_sapi_state status;
+ struct sapi_cmd *cmd;
+ struct gsm_lchan *lchan;
+ GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg);
+ GsmL1_MphDeactivateCnf_t *ic = &l1p->u.mphDeactivateCnf;
+
+ lchan = l1if_hLayer_to_lchan(trx, (uint32_t)ic->hLayer3);
+ if (!lchan) {
+ LOGP(DL1C, LOGL_ERROR,
+ "Failed to find lchan for hLayer3=0x%x\n", (uint32_t)ic->hLayer3);
+ goto err;
+ }
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.conf (%s ",
+ gsm_lchan_name(lchan),
+ get_value_string(lc15bts_l1sapi_names, ic->sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(lc15bts_dir_names, ic->dir));
+
+ if (ic->status == GsmL1_Status_Success) {
+ DEBUGP(DL1C, "Successful deactivation of L1 SAPI %s on TS %u\n",
+ get_value_string(lc15bts_l1sapi_names, ic->sapi), ic->u8Tn);
+ status = LCHAN_SAPI_S_NONE;
+ } else {
+ LOGP(DL1C, LOGL_ERROR, "Error deactivating L1 SAPI %s on TS %u: %s\n",
+ get_value_string(lc15bts_l1sapi_names, ic->sapi), ic->u8Tn,
+ get_value_string(lc15bts_l1status_names, ic->status));
+ status = LCHAN_SAPI_S_ERROR;
+ }
+
+ if (ic->dir & GsmL1_Dir_TxDownlink)
+ lchan->sapis_dl[ic->sapi] = status;
+ if (ic->dir & GsmL1_Dir_RxUplink)
+ lchan->sapis_ul[ic->sapi] = status;
+
+
+ if (llist_empty(&lchan->sapi_cmds)) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Got de-activation confirmation with empty queue\n",
+ gsm_lchan_name(lchan));
+ goto err;
+ }
+
+ cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry);
+ if (cmd->sapi != ic->sapi || cmd->dir != ic->dir ||
+ cmd->type != SAPI_CMD_DEACTIVATE) {
+ LOGP(DL1C, LOGL_ERROR,
+ "%s Confirmation mismatch (%d, %d) (%d, %d)\n",
+ gsm_lchan_name(lchan), cmd->sapi, cmd->dir,
+ ic->sapi, ic->dir);
+ goto err;
+ }
+
+ sapi_queue_dispatch(lchan, ic->status);
+
+err:
+ msgb_free(l1_msg);
+ return 0;
+}
+
+static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(lchan->ts->trx);
+ struct msgb *msg = l1p_msgb_alloc();
+ GsmL1_MphDeactivateReq_t *deact_req;
+
+ deact_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDeactivateReq, fl1h);
+ deact_req->u8Tn = lchan->ts->nr;
+ deact_req->subCh = lchan_to_GsmL1_SubCh_t(lchan);
+ deact_req->dir = cmd->dir;
+ deact_req->sapi = cmd->sapi;
+ deact_req->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan);
+
+ LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.req (%s ",
+ gsm_lchan_name(lchan),
+ get_value_string(lc15bts_l1sapi_names, deact_req->sapi));
+ LOGPC(DL1C, LOGL_INFO, "%s)\n",
+ get_value_string(lc15bts_dir_names, deact_req->dir));
+
+ /* send the primitive for all GsmL1_Sapi_* that match the LCHAN */
+ return l1if_gsm_req_compl(fl1h, msg, lchan_deact_compl_cb, NULL);
+}
+
+static int sapi_deactivate_cb(struct gsm_lchan *lchan, int status)
+{
+ /* FIXME: Error handling. There is no NACK... */
+ if (status != GsmL1_Status_Success && lchan->state == LCHAN_S_REL_REQ) {
+ LOGP(DL1C, LOGL_ERROR, "%s is now broken. Stopping the release.\n",
+ gsm_lchan_name(lchan));
+ lchan_set_state(lchan, LCHAN_S_BROKEN);
+ sapi_clear_queue(&lchan->sapi_cmds);
+ mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
+ return -1;
+ }
+
+ if (!llist_empty(&lchan->sapi_cmds))
+ return 0;
+
+ /* Don't send an REL ACK on SACCH deactivate */
+ if (lchan->state != LCHAN_S_REL_REQ)
+ return 0;
+
+ lchan_set_state(lchan, LCHAN_S_NONE);
+ mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
+ return 0;
+}
+
+static int enqueue_sapi_deact_cmd(struct gsm_lchan *lchan, int sapi, int dir)
+{
+ struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+
+ cmd->sapi = sapi;
+ cmd->dir = dir;
+ cmd->type = SAPI_CMD_DEACTIVATE;
+ cmd->callback = sapi_deactivate_cb;
+ return queue_sapi_command(lchan, cmd);
+}
+
+/*
+ * Release the SAPI if it was allocated. E.g. the SACCH might be already
+ * deactivated or during a hand-over the TCH was not allocated yet.
+ */
+static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir)
+{
+ /* check if we should schedule a release */
+ if (dir & GsmL1_Dir_TxDownlink) {
+ if (lchan->sapis_dl[sapi] != LCHAN_SAPI_S_ASSIGNED)
+ return 0;
+ lchan->sapis_dl[sapi] = LCHAN_SAPI_S_REL;
+ } else if (dir & GsmL1_Dir_RxUplink) {
+ if (lchan->sapis_ul[sapi] != LCHAN_SAPI_S_ASSIGNED)
+ return 0;
+ lchan->sapis_ul[sapi] = LCHAN_SAPI_S_REL;
+ }
+
+ /* now schedule the command and maybe dispatch it */
+ return enqueue_sapi_deact_cmd(lchan, sapi, dir);
+}
+
+static int release_sapis_for_ho(struct gsm_lchan *lchan)
+{
+ int res = 0;
+ int i;
+
+ const struct lchan_sapis *s4l = &sapis_for_ho;
+
+ for (i = s4l->num_sapis-1; i >= 0; i--)
+ res |= check_sapi_release(lchan,
+ s4l->sapis[i].sapi, s4l->sapis[i].dir);
+ return res;
+}
+
+static int lchan_deactivate_sapis(struct gsm_lchan *lchan)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(lchan->ts->trx);
+ const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type];
+ int i, res;
+
+ res = 0;
+
+ /* The order matters.. the Facch needs to be released first */
+ for (i = s4l->num_sapis-1; i >= 0; i--) {
+ /* Stop the alive timer once we deactivate the SCH */
+ if (s4l->sapis[i].sapi == GsmL1_Sapi_Sch)
+ osmo_timer_del(&fl1h->alive_timer);
+
+ /* Release if it was allocated */
+ res |= check_sapi_release(lchan, s4l->sapis[i].sapi, s4l->sapis[i].dir);
+ }
+
+ /* always attempt to disable the RACH burst */
+ res |= release_sapis_for_ho(lchan);
+
+ /* nothing was queued */
+ if (res == 0) {
+ LOGP(DL1C, LOGL_ERROR, "%s all SAPIs already released?\n",
+ gsm_lchan_name(lchan));
+ lchan_set_state(lchan, LCHAN_S_BROKEN);
+ mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0);
+ }
+
+ return res;
+}
+
+static void enqueue_rel_marker(struct gsm_lchan *lchan)
+{
+ struct sapi_cmd *cmd;
+
+ /* remember we need to release all active SAPIs */
+ cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd);
+ cmd->type = SAPI_CMD_REL_MARKER;
+ queue_sapi_command(lchan, cmd);
+}
+
+int lchan_deactivate(struct gsm_lchan *lchan)
+{
+ lchan_set_state(lchan, LCHAN_S_REL_REQ);
+ lchan->ciph_state = 0; /* FIXME: do this in common/\*.c */
+ 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);
+}
+
+static int 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 lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+
+ /* Did we go through MphInit yet? If yes fire and forget */
+ if (fl1h->hLayer1)
+ power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0);
+ }
+
+ /* FIXME: we actaully need to send a ACK or NACK for the OML message */
+ return oml_fom_ack_nack(msg, 0);
+}
+
+/* callback from OML */
+int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo,
+ void *obj)
+{
+ int rc;
+
+ switch (mo->obj_class) {
+ case NM_OC_RADIO_CARRIER:
+ rc = trx_init(obj);
+ break;
+ case NM_OC_CHANNEL:
+ rc = ts_connect(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 lchan_deactivate_sacch(lchan);
+}
+
+int bts_model_trx_deact_rf(struct gsm_bts_trx *trx)
+{
+ struct lc15l1_hdl *fl1 = trx_lc15l1_hdl(trx);
+
+ return l1if_activate_rf(fl1, 0);
+}
+
+int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm)
+{
+ return l1if_set_txpower(trx_lc15l1_hdl(trx), ((float) p_trxout_mdBm)/1000.0);
+}
diff --git a/src/osmo-bts-litecell15/oml_router.c b/src/osmo-bts-litecell15/oml_router.c
new file mode 100644
index 00000000..198d5e30
--- /dev/null
+++ b/src/osmo-bts-litecell15/oml_router.c
@@ -0,0 +1,132 @@
+/* Beginnings of an OML router */
+
+/* Copyright (C) 2015 by Yves Godin <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-litecell15/oml_router.h b/src/osmo-bts-litecell15/oml_router.h
new file mode 100644
index 00000000..8c08baaa
--- /dev/null
+++ b/src/osmo-bts-litecell15/oml_router.h
@@ -0,0 +1,13 @@
+#pragma once
+
+struct gsm_bts;
+struct osmo_fd;
+
+/**
+ * The default path lc15bts will listen for incoming
+ * registrations for OML routing and sending.
+ */
+#define OML_ROUTER_PATH "/var/run/lc15bts_oml_router"
+
+
+int oml_router_init(struct gsm_bts *bts, const char *path, struct osmo_fd *accept, struct osmo_fd *read);
diff --git a/src/osmo-bts-litecell15/tch.c b/src/osmo-bts-litecell15/tch.c
new file mode 100644
index 00000000..a11911c8
--- /dev/null
+++ b/src/osmo-bts-litecell15/tch.c
@@ -0,0 +1,534 @@
+/* Traffic channel support for NuRAN Wireless Litecell 1.5 BTS L1 */
+
+/* Copyright (C) 2015 by Yves Godin <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 <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 <osmocom/trau/osmo_ortp.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+#include <osmo-bts/gsm_data.h>
+#include <osmo-bts/measurement.h>
+#include <osmo-bts/amr.h>
+#include <osmo-bts/l1sap.h>
+
+#include <nrw/litecell15/litecell15.h>
+#include <nrw/litecell15/gsml1prim.h>
+#include <nrw/litecell15/gsml1const.h>
+#include <nrw/litecell15/gsml1types.h>
+
+#include "lc15bts.h"
+#include "l1_if.h"
+
+/* input octet-aligned, output not octet-aligned */
+void osmo_nibble_shift_right(uint8_t *out, const uint8_t *in,
+ unsigned int num_nibbles)
+{
+ unsigned int i;
+ unsigned int num_whole_bytes = num_nibbles / 2;
+
+ /* first byte: upper nibble empty, lower nibble from src */
+ out[0] = (in[0] >> 4);
+
+ /* bytes 1.. */
+ for (i = 1; i < num_whole_bytes; i++)
+ out[i] = ((in[i-1] & 0xF) << 4) | (in[i] >> 4);
+
+ /* shift the last nibble, in case there's an odd count */
+ i = num_whole_bytes;
+ if (num_nibbles & 1)
+ out[i] = ((in[i-1] & 0xF) << 4) | (in[i] >> 4);
+ else
+ out[i] = (in[i-1] & 0xF) << 4;
+}
+
+
+/* input unaligned, output octet-aligned */
+void osmo_nibble_shift_left_unal(uint8_t *out, const uint8_t *in,
+ unsigned int num_nibbles)
+{
+ unsigned int i;
+ unsigned int num_whole_bytes = num_nibbles / 2;
+
+ for (i = 0; i < num_whole_bytes; i++)
+ out[i] = ((in[i] & 0xF) << 4) | (in[i+1] >> 4);
+
+ /* shift the last nibble, in case there's an odd count */
+ i = num_whole_bytes;
+ if (num_nibbles & 1)
+ out[i] = (in[i] & 0xF) << 4;
+}
+
+
+#define GSM_FR_BITS 260
+#define GSM_EFR_BITS 244
+
+#define GSM_FR_BYTES 33 /* TS 101318 Chapter 5.1: 260 bits + 4bit sig */
+#define GSM_HR_BYTES 14 /* TS 101318 Chapter 5.2: 112 bits, no sig */
+#define GSM_EFR_BYTES 31 /* TS 101318 Chapter 5.3: 244 bits + 4bit sig */
+
+static struct msgb *l1_to_rtppayload_fr(uint8_t *l1_payload, uint8_t payload_len)
+{
+ struct msgb *msg;
+ uint8_t *cur;
+
+ msg = msgb_alloc_headroom(1024, 128, "L1C-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);
+
+ 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 msgb *msg;
+ uint8_t *cur;
+
+ msg = msgb_alloc_headroom(1024, 128, "L1C-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);
+ 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 msgb *msg;
+ uint8_t *cur;
+
+ msg = msgb_alloc_headroom(1024, 128, "L1C-to-RTP");
+ if (!msg)
+ return NULL;
+
+ if (payload_len != GSM_HR_BYTES) {
+ LOGP(DL1C, 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);
+
+ 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(DL1C, 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, "L1C-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;
+}
+
+enum amr_frame_type {
+ AMR_FT_SID_AMR = 8,
+};
+
+int get_amr_mode_idx(const struct amr_multirate_conf *amr_mrc, uint8_t cmi)
+{
+ unsigned int i;
+ for (i = 0; i < amr_mrc->num_modes; i++) {
+ if (amr_mrc->bts_mode[i].mode == cmi)
+ return i;
+ }
+ return -EINVAL;
+}
+
+/*! \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,
+ struct gsm_lchan *lchan)
+{
+ struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr;
+ uint8_t ft = (rtp_payload[1] >> 3) & 0xf;
+ uint8_t cmr = rtp_payload[0] >> 4;
+ uint8_t cmi, sti;
+ uint8_t *l1_cmi_idx = l1_payload;
+ uint8_t *l1_cmr_idx = l1_payload+1;
+ int rc;
+
+ memcpy(l1_payload+2, rtp_payload, payload_len);
+
+ /* CMI in downlink tells the L1 encoder which encoding function
+ * it will use, so we have to use the frame type */
+ switch (ft) {
+ case 0: case 1: case 2: case 3:
+ case 4: case 5: case 6: case 7:
+ cmi = ft;
+ LOGP(DRTP, LOGL_DEBUG, "SPEECH frame with CMI %u\n", cmi);
+ break;
+ case AMR_FT_SID_AMR:
+ /* extract the mode indiciation from last bits of
+ * 39 bit SID frame (Table 6 / 26.101) */
+ cmi = (rtp_payload[2+4] >> 1) & 0x7;
+ sti = rtp_payload[2+4] & 0x10;
+ LOGP(DRTP, LOGL_DEBUG, "SID %s frame with CMI %u\n",
+ sti ? "UPDATE" : "FIRST", cmi);
+ break;
+ default:
+ LOGP(DRTP, LOGL_ERROR, "unsupported AMR FT 0x%02x\n", ft);
+ return -EINVAL;
+ break;
+ }
+
+ rc = get_amr_mode_idx(amr_mrc, cmi);
+ if (rc < 0) {
+ LOGP(DRTP, LOGL_ERROR, "AMR CMI %u not part of AMR MR set\n",
+ cmi);
+ *l1_cmi_idx = 0;
+ } else
+ *l1_cmi_idx = rc;
+
+ /* Codec Mode Request is in upper 4 bits of RTP payload header,
+ * and we simply copy the CMR into the CMC */
+ if (cmr == 0xF) {
+ /* FIXME: we need some state about the last codec mode */
+ *l1_cmr_idx = 0;
+ } else {
+ rc = get_amr_mode_idx(amr_mrc, cmr);
+ if (rc < 0) {
+ /* FIXME: we need some state about the last codec mode */
+ LOGP(DRTP, LOGL_INFO, "RTP->L1: overriding CMR %u\n", cmr);
+ *l1_cmr_idx = 0;
+ } else
+ *l1_cmr_idx = rc;
+ }
+#if 0
+ /* check for bad quality indication */
+ if (rtp_payload[1] & AMR_TOC_QBIT) {
+ /* obtain frame type from AMR FT */
+ l1_payload[2] = ft;
+ } else {
+ /* bad quality, we should indicate that... */
+ if (ft == AMR_FT_SID_AMR) {
+ /* FIXME: Should we do GsmL1_TchPlType_Amr_SidBad? */
+ l1_payload[2] = ft;
+ } else {
+ l1_payload[2] = ft;
+ }
+ }
+#endif
+
+ if (ft == AMR_FT_SID_AMR) {
+ /* store the last SID frame in lchan context */
+ unsigned int copy_len;
+ copy_len = OSMO_MIN(payload_len+1,
+ ARRAY_SIZE(lchan->tch.last_sid.buf));
+ lchan->tch.last_sid.len = copy_len;
+ memcpy(lchan->tch.last_sid.buf, l1_payload, copy_len);
+ }
+
+ return payload_len+1;
+}
+
+#define RTP_MSGB_ALLOC_SIZE 512
+
+/*! \brief function for incoming RTP via TCH.req
+ * \param rs RTP Socket
+ * \param[in] rtp_pl buffer containing RTP payload
+ * \param[in] rtp_pl_len length of \a rtp_pl
+ *
+ * This function prepares a msgb with a L1 PH-DATA.req primitive and
+ * queues it into lchan->dl_tch_queue.
+ *
+ * Note that the actual L1 primitive header is not fully initialized
+ * yet, as things like the frame number, etc. are unknown at the time we
+ * pre-fill the primtive.
+ */
+void l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len,
+ const uint8_t *rtp_pl, unsigned int rtp_pl_len)
+{
+ uint8_t *payload_type;
+ uint8_t *l1_payload;
+ int rc;
+
+ 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);
+ } else{
+ *payload_type = GsmL1_TchPlType_Hr;
+ rc = rtppayload_to_l1_hr(l1_payload,
+ rtp_pl, rtp_pl_len);
+ }
+ break;
+ case GSM48_CMODE_SPEECH_EFR:
+ *payload_type = GsmL1_TchPlType_Efr;
+ rc = rtppayload_to_l1_efr(l1_payload, rtp_pl,
+ rtp_pl_len);
+ break;
+ case GSM48_CMODE_SPEECH_AMR:
+ *payload_type = GsmL1_TchPlType_Amr;
+ rc = rtppayload_to_l1_amr(l1_payload, rtp_pl,
+ rtp_pl_len, lchan);
+ 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;
+ }
+
+ *len = rc + 1;
+
+ DEBUGP(DRTP, "%s RTP->L1: %s\n", gsm_lchan_name(lchan),
+ osmo_hexdump(data, *len));
+}
+
+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_type = data_ind->msgUnitParam.u8Buffer[0];
+ uint8_t *payload = data_ind->msgUnitParam.u8Buffer + 1;
+ uint8_t payload_len;
+ 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) {
+ LOGP(DL1C, LOGL_ERROR, "chan_nr %d Rx Payload size 0\n",
+ chan_nr);
+ return -EINVAL;
+ }
+ payload_len = data_ind->msgUnitParam.u8Size - 1;
+
+ switch (payload_type) {
+ case GsmL1_TchPlType_Fr:
+ case GsmL1_TchPlType_Efr:
+ if (lchan->type != GSM_LCHAN_TCH_F)
+ goto err_payload_match;
+ break;
+ case GsmL1_TchPlType_Hr:
+ if (lchan->type != GSM_LCHAN_TCH_H)
+ goto err_payload_match;
+ break;
+ case GsmL1_TchPlType_Amr:
+ if (lchan->type != GSM_LCHAN_TCH_H &&
+ lchan->type != GSM_LCHAN_TCH_F)
+ goto err_payload_match;
+ break;
+ default:
+ LOGP(DL1C, LOGL_NOTICE, "%s Rx Payload Type %s is unsupported\n",
+ gsm_lchan_name(lchan),
+ get_value_string(lc15bts_tch_pl_names, payload_type));
+ break;
+ }
+
+
+ switch (payload_type) {
+ case GsmL1_TchPlType_Fr:
+ rmsg = l1_to_rtppayload_fr(payload, payload_len);
+ break;
+ case GsmL1_TchPlType_Hr:
+ rmsg = l1_to_rtppayload_hr(payload, payload_len);
+ break;
+ case GsmL1_TchPlType_Efr:
+ rmsg = l1_to_rtppayload_efr(payload, payload_len);
+ break;
+ case GsmL1_TchPlType_Amr:
+ rmsg = l1_to_rtppayload_amr(payload, payload_len, lchan);
+ break;
+ }
+
+ if (rmsg) {
+ struct osmo_phsap_prim *l1sap;
+
+ LOGP(DL1C, LOGL_DEBUG, "%s Rx -> RTP: %s\n",
+ gsm_lchan_name(lchan), osmo_hexdump(rmsg->data, rmsg->len));
+
+ /* add l1sap header */
+ rmsg->l2h = rmsg->data;
+ msgb_push(rmsg, sizeof(*l1sap));
+ rmsg->l1h = rmsg->data;
+ l1sap = msgb_l1sap_prim(rmsg);
+ osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH, PRIM_OP_INDICATION, rmsg);
+ l1sap->u.tch.chan_nr = chan_nr;
+
+ return l1sap_up(trx, l1sap);
+ }
+
+ return 0;
+
+err_payload_match:
+ LOGP(DL1C, LOGL_ERROR, "%s Rx Payload Type %s incompatible with lchan\n",
+ gsm_lchan_name(lchan),
+ get_value_string(lc15bts_tch_pl_names, payload_type));
+ return -EINVAL;
+}
+
+struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan)
+{
+ 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;
+
+ 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:
+ *payload_type = GsmL1_TchPlType_Amr;
+ if (lchan->tch.last_sid.len) {
+ memcpy(l1_payload, lchan->tch.last_sid.buf,
+ lchan->tch.last_sid.len);
+ msu_param->u8Size = lchan->tch.last_sid.len+1;
+ } else {
+ /* FIXME: decide if we should send SPEECH_BAD or
+ * SID_BAD */
+#if 0
+ *payload_type = GsmL1_TchPlType_Amr_SidBad;
+ memset(l1_payload, 0xFF, 5);
+ msu_param->u8Size = 5 + 3;
+#else
+ /* send an all-zero SID */
+ msu_param->u8Size = 8;
+#endif
+ }
+ break;
+ default:
+ msgb_free(msg);
+ msg = NULL;
+ break;
+ }
+
+ return msg;
+}
diff --git a/src/osmo-bts-litecell15/utils.c b/src/osmo-bts-litecell15/utils.c
new file mode 100644
index 00000000..10d61994
--- /dev/null
+++ b/src/osmo-bts-litecell15/utils.c
@@ -0,0 +1,127 @@
+/* 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 "lc15bts.h"
+#include "l1_if.h"
+
+int band_lc152osmo(GsmL1_FreqBand_t band)
+{
+ switch (band) {
+ case GsmL1_FreqBand_850:
+ return GSM_BAND_850;
+ case GsmL1_FreqBand_900:
+ return GSM_BAND_900;
+ case GsmL1_FreqBand_1800:
+ return GSM_BAND_1800;
+ case GsmL1_FreqBand_1900:
+ return GSM_BAND_1900;
+ default:
+ return -1;
+ }
+}
+
+static int band_osmo2lc15(struct gsm_bts_trx *trx, enum gsm_band osmo_band)
+{
+ struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
+
+ /* check if the TRX hardware actually supports the given band */
+ if (!(fl1h->hw_info.band_support & osmo_band))
+ return -1;
+
+ /* if yes, convert from osmcoom style band definition to L1 band */
+ switch (osmo_band) {
+ case GSM_BAND_850:
+ return GsmL1_FreqBand_850;
+ case GSM_BAND_900:
+ return GsmL1_FreqBand_900;
+ case GSM_BAND_1800:
+ return GsmL1_FreqBand_1800;
+ case GSM_BAND_1900:
+ return GsmL1_FreqBand_1900;
+ default:
+ return -1;
+ }
+}
+
+/**
+ * Select the band that matches the ARFCN. In general the ARFCNs
+ * for GSM1800 and GSM1900 overlap and one needs to specify the
+ * rightband. When moving between GSM900/GSM1800 and GSM850/1900
+ * modifying the BTS configuration is a bit annoying. The auto-band
+ * configuration allows to ease with this transition.
+ */
+int lc15bts_select_lc15_band(struct gsm_bts_trx *trx, uint16_t arfcn)
+{
+ enum gsm_band band;
+ struct gsm_bts *bts = trx->bts;
+ struct gsm_bts_role_bts *btsb = bts_role_bts(bts);
+
+ if (!btsb->auto_band)
+ return band_osmo2lc15(trx, bts->band);
+
+ /*
+ * We need to check what will happen now.
+ */
+ band = gsm_arfcn2band(arfcn);
+
+ /* if we are already on the right band return */
+ if (band == bts->band)
+ return band_osmo2lc15(trx, bts->band);
+
+ /* Check if it is GSM1800/GSM1900 */
+ if (band == GSM_BAND_1800 && bts->band == GSM_BAND_1900)
+ return band_osmo2lc15(trx, bts->band);
+
+ /*
+ * Now to the actual autobauding. We just want DCS/DCS and
+ * PCS/PCS for PCS we check for 850/1800 though
+ */
+ if ((band == GSM_BAND_900 && bts->band == GSM_BAND_1800)
+ || (band == GSM_BAND_1800 && bts->band == GSM_BAND_900)
+ || (band == GSM_BAND_850 && bts->band == GSM_BAND_1900))
+ return band_osmo2lc15(trx, band);
+ if (band == GSM_BAND_1800 && bts->band == GSM_BAND_850)
+ return band_osmo2lc15(trx, GSM_BAND_1900);
+
+ /* give up */
+ return -1;
+}
+
+int lc15bts_get_nominal_power(struct gsm_bts_trx *trx)
+{
+ return 37;
+}
+
+struct lc15l1_hdl *trx_lc15l1_hdl(struct gsm_bts_trx *trx)
+{
+ return trx->role_bts.l1h;
+}
+
diff --git a/src/osmo-bts-litecell15/utils.h b/src/osmo-bts-litecell15/utils.h
new file mode 100644
index 00000000..115de653
--- /dev/null
+++ b/src/osmo-bts-litecell15/utils.h
@@ -0,0 +1,17 @@
+#ifndef _UTILS_H
+#define _UTILS_H
+
+#include <stdint.h>
+#include "lc15bts.h"
+
+struct gsm_bts_trx;
+
+int band_lc152osmo(GsmL1_FreqBand_t band);
+
+int lc15bts_select_lc15_band(struct gsm_bts_trx *trx, uint16_t arfcn);
+
+int lc15bts_get_nominal_power(struct gsm_bts_trx *trx);
+
+struct lc15l1_hdl *trx_lc15l1_hdl(struct gsm_bts_trx *trx);
+
+#endif