diff --git a/configure.ac b/configure.ac index f2d4f846c..9a8d58f13 100644 --- a/configure.ac +++ b/configure.ac @@ -279,6 +279,27 @@ if test "$enable_litecell15" = "yes"; then CPPFLAGS=$oldCPPFLAGS fi +AC_MSG_CHECKING([whether to enable NuRAN Wireless OC-2G hardware support]) +AC_ARG_ENABLE(oc2g, + AC_HELP_STRING([--enable-oc2g], + [enable code for NuRAN Wireless OC-2G bts [default=no]]), + [enable_oc2g="yes"],[enable_oc2g="no"]) +AC_ARG_WITH([oc2g], [AS_HELP_STRING([--with-oc2g=INCLUDE_DIR], [Location of the OC-2G API header files])], + [oc2g_incdir="$withval"],[oc2g_incdir="$incdir"]) +AC_SUBST([OC2G_INCDIR], -I$oc2g_incdir) +AC_MSG_RESULT([$enable_oc2g]) +AM_CONDITIONAL(ENABLE_OC2GBTS, test "x$enable_oc2g" = "xyes") +if test "$enable_oc2g" = "yes"; then + oldCPPFLAGS=$CPPFLAGS + CPPFLAGS="$CPPFLAGS $OC2G_INCDIR -I$srcdir/include" + AC_CHECK_HEADER([nrw/oc2g/oc2g.h],[], + [AC_MSG_ERROR([nrw/oc2g/oc2g.h can not be found in $oc2g_incdir])], + [#include ]) + PKG_CHECK_MODULES(LIBSYSTEMD, libsystemd) + PKG_CHECK_MODULES(LIBGPS, libgps) + CPPFLAGS=$oldCPPFLAGS +fi + # https://www.freedesktop.org/software/systemd/man/daemon.html AC_ARG_WITH([systemdsystemunitdir], [AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files])],, @@ -307,6 +328,7 @@ AC_OUTPUT( src/osmo-bts-omldummy/Makefile src/osmo-bts-sysmo/Makefile src/osmo-bts-litecell15/Makefile + src/osmo-bts-oc2g/Makefile src/osmo-bts-trx/Makefile src/osmo-bts-octphy/Makefile include/Makefile diff --git a/contrib/jenkins_bts_model.sh b/contrib/jenkins_bts_model.sh index 2488f7156..9aa943fa3 100755 --- a/contrib/jenkins_bts_model.sh +++ b/contrib/jenkins_bts_model.sh @@ -30,6 +30,10 @@ case "$bts_model" in ./contrib/jenkins_lc15.sh ;; + oc2g) + ./contrib/jenkins_oc2g.sh + ;; + trx) ./contrib/jenkins_bts_trx.sh ;; diff --git a/contrib/jenkins_oc2g.sh b/contrib/jenkins_oc2g.sh new file mode 100755 index 000000000..b8badce69 --- /dev/null +++ b/contrib/jenkins_oc2g.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# jenkins build helper script for osmo-bts-oc2g + +# shellcheck source=contrib/jenkins_common.sh +. $(dirname "$0")/jenkins_common.sh + +osmo-build-dep.sh libosmocore "" --disable-doxygen + +export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH" +export LD_LIBRARY_PATH="$inst/lib" + +osmo-build-dep.sh libosmo-abis + +cd "$deps" +osmo-layer1-headers.sh oc2g "$FIRMWARE_VERSION" + +configure_flags="\ + --enable-sanitize \ + --with-oc2g=$deps/layer1-headers/inc/ \ + --enable-oc2g \ + " + +build_bts "osmo-bts-oc2g" "$configure_flags" + +osmo-clean-workspace.sh diff --git a/contrib/systemd/oc2gbts-mgr.service b/contrib/systemd/oc2gbts-mgr.service new file mode 100644 index 000000000..ed915b336 --- /dev/null +++ b/contrib/systemd/oc2gbts-mgr.service @@ -0,0 +1,29 @@ +[Unit] +Description=osmo-bts manager for OC-2G +After=oc2g-sysdev-remap.service +Wants=oc2g-sysdev-remap.service + +[Service] +Type=simple +NotifyAccess=all +WatchdogSec=21780s +Restart=always +RestartSec=2 + +# Make sure directories and symbolic link exist +ExecStartPre=/bin/sh -c 'test -d /mnt/storage/var/run/oc2gbts-mgr || mkdir -p /mnt/storage/var/run/oc2gbts-mgr ; test -d /var/run/oc2gbts-mgr || ln -sf /mnt/storage/var/run/oc2gbts-mgr/ /var/run' +# Make sure BTS operation hour exist +ExecStartPre=/bin/sh -c 'test -f /mnt/storage/var/run/oc2gbts-mgr/hours-running || echo 0 > /mnt/storage/var/run/oc2gbts-mgr/hours-running' +# Shutdown all PA correctly +ExecStartPre=/bin/sh -c 'echo disabled > /var/oc2g/pa-state/pa0/state;' +#ExecStartPre=/bin/sh -c 'echo 0 > /var/oc2g/pa-supply/max_microvolts; echo 0 > /var/oc2g/pa-supply/min_microvolts' + +ExecStart=/usr/bin/oc2gbts-mgr -s -c /etc/osmocom/oc2gbts-mgr.cfg + +# Shutdown all PA correctly +ExecStopPost=/bin/sh -c 'echo disabled > /var/oc2g/pa-state/pa0/state;' +#ExecStopPost=/bin/sh -c 'echo 0 > /var/oc2g/pa-supply/max_microvolts; echo 0 > /var/oc2g/pa-supply/min_microvolts' + +[Install] +WantedBy=multi-user.target +Alias=osmo-bts-mgr.service diff --git a/contrib/systemd/osmo-bts-oc2g.service b/contrib/systemd/osmo-bts-oc2g.service new file mode 100644 index 000000000..2f2d8378f --- /dev/null +++ b/contrib/systemd/osmo-bts-oc2g.service @@ -0,0 +1,21 @@ +[Unit] +Description=osmo-bts for OC-2G + +[Service] +Type=simple +ExecStartPre=/bin/sh -c 'echo 1 > /sys/class/leds/usr0/brightness' +ExecStartPre=/bin/sh -c 'echo 1 > /sys/class/leds/usr1/brightness' +ExecStart=/usr/bin/osmo-bts-oc2g -s -c /etc/osmocom/osmo-bts.cfg -M +ExecStopPost=/bin/sh -c 'echo 1 > /sys/class/leds/usr0/brightness' +ExecStopPost=/bin/sh -c 'echo 0 > /sys/class/leds/usr1/brightness' +Restart=always +RestartSec=2 +RestartPreventExitStatus=1 + +# The msg queues must be read fast enough +CPUSchedulingPolicy=rr +CPUSchedulingPriority=1 + +[Install] +WantedBy=multi-user.target +Alias=osmo-bts.service diff --git a/doc/examples/oc2g/oc2gbts-mgr.cfg b/doc/examples/oc2g/oc2gbts-mgr.cfg new file mode 100644 index 000000000..8248f60d3 --- /dev/null +++ b/doc/examples/oc2g/oc2gbts-mgr.cfg @@ -0,0 +1,33 @@ +! +! oc2gbts-mgr (0.3.0.284-a7c2-dirty) configuration saved from vty +!! +! +log stderr + logging filter all 1 + logging color 1 + logging print category 0 + logging timestamp 0 + logging level temp info + logging level fw info + logging level find info + logging level calib info + logging level lglobal notice + logging level llapd notice + logging level linp notice + logging level lmux notice + logging level lmi notice + logging level lmib notice + logging level lsms notice + logging level lctrl notice + logging level lgtp notice +! +line vty + no login +! +oc2gbts-mgr + limits supply_volt + threshold warning min 17500 + threshold critical min 19000 + limits supply_pwr + threshold warning max 110 + threshold critical max 120 diff --git a/doc/examples/oc2g/osmo-bts.cfg b/doc/examples/oc2g/osmo-bts.cfg new file mode 100644 index 000000000..f985f3bce --- /dev/null +++ b/doc/examples/oc2g/osmo-bts.cfg @@ -0,0 +1,38 @@ +! +! OsmoBTS (0.0.1.100-0455-dirty) configuration saved from vty +!! +! +log stderr + logging color 1 + logging timestamp 0 + logging level rsl info + logging level oml info + logging level rll notice + logging level rr notice + logging level meas notice + logging level pag info + logging level l1c info + logging level l1p info + logging level dsp debug + logging level abis notice + logging level rtp notice + logging level lglobal notice + logging level llapd notice + logging level linp notice + logging level lmux notice + logging level lmi notice + logging level lmib notice + logging level lsms notice +! +line vty + no login +! +phy 0 + instance 0 + trx-calibration-path /mnt/rom/factory/calib +bts 0 + band 900 + ipa unit-id 1500 0 + oml remote-ip 10.42.0.1 + trx 0 + phy 0 instance 0 diff --git a/include/osmo-bts/gsm_data_shared.h b/include/osmo-bts/gsm_data_shared.h index 794eaeae3..61cf81f6b 100644 --- a/include/osmo-bts/gsm_data_shared.h +++ b/include/osmo-bts/gsm_data_shared.h @@ -289,6 +289,8 @@ struct gsm_lchan { /* indicates if DTXd was active during DL measurement period */ bool dl_active; + /* last UL SPEECH resume flag */ + bool is_speech_resume; } dtx; uint8_t last_cmr; uint32_t last_fn; @@ -411,6 +413,9 @@ struct gsm_bts_trx { uint16_t arfcn; int nominal_power; /* in dBm */ unsigned int max_power_red; /* in actual dB */ + uint8_t max_power_backoff_8psk; /* in actual dB OC-2G only */ + uint8_t c0_idle_power_red; /* in actual dB OC-2G only */ + struct trx_power_params power_params; int ms_power_control; @@ -437,6 +442,7 @@ struct gsm_bts_trx { enum gsm_bts_type_variant { BTS_UNKNOWN, BTS_OSMO_LITECELL15, + BTS_OSMO_OC2G, BTS_OSMO_OCTPHY, BTS_OSMO_SYSMO, BTS_OSMO_TRX, @@ -746,7 +752,14 @@ struct gsm_bts { struct timeval tv_clock; struct osmo_timer_list fn_timer; } vbts; - +#ifdef ENABLE_OC2GBTS + /* specific to Open Cellular 2G BTS */ + struct { + uint8_t led_ctrl_mode; /* 0: control by BTS, 1: not control by BTS */ + struct llist_head ceased_alarm_list; /* ceased alarm list*/ + unsigned int rtp_drift_thres_ms; /* RTP timestamp drift detection threshold */ + } oc2g; +#endif }; diff --git a/include/osmo-bts/phy_link.h b/include/osmo-bts/phy_link.h index 36e34e1d4..2472c0517 100644 --- a/include/osmo-bts/phy_link.h +++ b/include/osmo-bts/phy_link.h @@ -135,6 +135,20 @@ struct phy_instance { uint8_t tx_pwr_adj_mode; /* 0: no auto adjust power, 1: auto adjust power using RMS detector */ uint8_t tx_pwr_red_8psk; /* 8-PSK maximum Tx power reduction level in dB */ } lc15; + struct { + /* configuration */ + uint32_t dsp_trace_f; + char *calib_path; + int minTxPower; + int maxTxPower; + struct oc2gl1_hdl *hdl; + uint8_t max_cell_size; /* 0:166 qbits*/ + uint8_t pedestal_mode; /* 0: unused TS is OFF, 1: unused TS is in minimum Tx power */ + uint8_t dsp_alive_period; /* DSP alive timer period */ + uint8_t tx_pwr_adj_mode; /* 0: no auto adjust power, 1: auto adjust power using RMS detector */ + uint8_t tx_pwr_red_8psk; /* 8-PSK maximum Tx power reduction level in dB */ + uint8_t tx_c0_idle_pwr_red; /* C0 idle slot Tx power reduction level in dB */ + } oc2g; } u; }; diff --git a/src/Makefile.am b/src/Makefile.am index 501591d6d..70e4d968e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -15,3 +15,8 @@ endif if ENABLE_LC15BTS SUBDIRS += osmo-bts-litecell15 endif + +if ENABLE_OC2GBTS +SUBDIRS += osmo-bts-oc2g +endif + diff --git a/src/common/gsm_data_shared.c b/src/common/gsm_data_shared.c index 588d0fdd2..21e578142 100644 --- a/src/common/gsm_data_shared.c +++ b/src/common/gsm_data_shared.c @@ -71,6 +71,7 @@ const char *btsatttr2str(enum bts_attribute v) const struct value_string osmo_bts_variant_names[_NUM_BTS_VARIANT + 1] = { { BTS_UNKNOWN, "unknown" }, { BTS_OSMO_LITECELL15, "osmo-bts-lc15" }, + { BTS_OSMO_OC2G, "osmo-bts-oc2g" }, { BTS_OSMO_OCTPHY, "osmo-bts-octphy" }, { BTS_OSMO_SYSMO, "osmo-bts-sysmo" }, { BTS_OSMO_TRX, "omso-bts-trx" }, diff --git a/src/osmo-bts-oc2g/Makefile.am b/src/osmo-bts-oc2g/Makefile.am new file mode 100644 index 000000000..7188626fc --- /dev/null +++ b/src/osmo-bts-oc2g/Makefile.am @@ -0,0 +1,38 @@ +AUTOMAKE_OPTIONS = subdir-objects + +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include $(OC2G_INCDIR) +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS) $(ORTP_CFLAGS) $(LIBSYSTEMD_CFLAGS) +COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) $(ORTP_LIBS) + +AM_CFLAGS += -DENABLE_OC2GBTS + +EXTRA_DIST = misc/oc2gbts_mgr.h misc/oc2gbts_misc.h misc/oc2gbts_par.h misc/oc2gbts_led.h \ + misc/oc2gbts_temp.h misc/oc2gbts_power.h misc/oc2gbts_clock.h \ + misc/oc2gbts_bid.h misc/oc2gbts_nl.h \ + hw_misc.h l1_if.h l1_transp.h oc2gbts.h oml_router.h utils.h + +bin_PROGRAMS = osmo-bts-oc2g oc2gbts-mgr oc2gbts-util + +COMMON_SOURCES = main.c oc2gbts.c l1_if.c oml.c oc2gbts_vty.c tch.c hw_misc.c calib_file.c \ + utils.c misc/oc2gbts_par.c misc/oc2gbts_bid.c oml_router.c + +osmo_bts_oc2g_SOURCES = $(COMMON_SOURCES) l1_transp_hw.c +osmo_bts_oc2g_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD) + +oc2gbts_mgr_SOURCES = \ + misc/oc2gbts_mgr.c misc/oc2gbts_misc.c \ + misc/oc2gbts_par.c misc/oc2gbts_nl.c \ + misc/oc2gbts_temp.c misc/oc2gbts_power.c \ + misc/oc2gbts_clock.c misc/oc2gbts_bid.c \ + misc/oc2gbts_mgr_vty.c \ + misc/oc2gbts_mgr_nl.c \ + misc/oc2gbts_mgr_temp.c \ + misc/oc2gbts_mgr_calib.c \ + misc/oc2gbts_led.c \ + misc/oc2gbts_bts.c \ + misc/oc2gbts_swd.c + +oc2gbts_mgr_LDADD = $(top_builddir)/src/common/libbts.a $(LIBGPS_LIBS) $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCTRL_LIBS) $(LIBSYSTEMD_LIBS) $(COMMON_LDADD) + +oc2gbts_util_SOURCES = misc/oc2gbts_util.c misc/oc2gbts_par.c +oc2gbts_util_LDADD = $(LIBOSMOCORE_LIBS) diff --git a/src/osmo-bts-oc2g/calib_file.c b/src/osmo-bts-oc2g/calib_file.c new file mode 100644 index 000000000..497684806 --- /dev/null +++ b/src/osmo-bts-oc2g/calib_file.c @@ -0,0 +1,469 @@ +/* NuRAN Wireless OC-2G BTS L1 calibration file routines*/ + +/* Copyright (C) 2015 by Yves Godin + * Copyright (C) 2016 by Harald Welte + * + * Based on sysmoBTS: + * (C) 2012 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +#include "l1_if.h" +#include "oc2gbts.h" +#include "utils.h" +#include "osmo-bts/oml.h" + +/* Maximum calibration data chunk size */ +#define MAX_CALIB_TBL_SIZE 65536 +/* Calibration header version */ +#define CALIB_HDR_V1 0x01 + +struct calib_file_desc { + const char *fname; + int rx; + int trx; + int rxpath; +}; + +static const struct calib_file_desc calib_files[] = { + { + .fname = "calib_rx0.conf", + .rx = 1, + .trx = 0, + .rxpath = 0, + }, { + .fname = "calib_tx0.conf", + .rx = 0, + .trx = 0, + }, +}; + +struct calTbl_t +{ + union + { + struct + { + uint8_t u8Version; /* Header version (1) */ + uint8_t u8Parity; /* Parity byte (xor) */ + uint8_t u8Type; /* Table type (0:TX Downlink, 1:RX-A Uplink, 2:RX-B Uplink) */ + uint8_t u8Band; /* GSM Band (0:GSM-850, 1:EGSM-900, 2:DCS-1800, 3:PCS-1900) */ + uint32_t u32Len; /* Table length in bytes including the header */ + struct + { + uint32_t u32DescOfst; /* Description section offset */ + uint32_t u32DateOfst; /* Date section offset */ + uint32_t u32StationOfst; /* Calibration test station section offset */ + uint32_t u32FpgaFwVerOfst; /* Calibration FPGA firmware version section offset */ + uint32_t u32DspFwVerOfst; /* Calibration DSP firmware section offset */ + uint32_t u32DataOfst; /* Calibration data section offset */ + } toc; + } v1; + } hdr; + + uint8_t u8RawData[MAX_CALIB_TBL_SIZE - 32]; +}; + + +static int calib_file_send(struct oc2gl1_hdl *fl1h, + const struct calib_file_desc *desc); +static int calib_verify(struct oc2gl1_hdl *fl1h, + const struct calib_file_desc *desc); + +/* determine next calibration file index based on supported bands */ +static int get_next_calib_file_idx(struct oc2gl1_hdl *fl1h, int last_idx) +{ + struct phy_link *plink = fl1h->phy_inst->phy_link; + int i; + + for (i = last_idx+1; i < ARRAY_SIZE(calib_files); i++) { + if (calib_files[i].trx == plink->num) + return i; + } + return -1; +} + +static int calib_file_open(struct oc2gl1_hdl *fl1h, + const struct calib_file_desc *desc) +{ + struct calib_send_state *st = &fl1h->st; + char *calib_path = fl1h->phy_inst->u.oc2g.calib_path; + char fname[PATH_MAX]; + + if (st->fp) { + LOGP(DL1C, LOGL_NOTICE, "L1 calibration file was left opened !!\n"); + fclose(st->fp); + st->fp = NULL; + } + + fname[0] = '\0'; + snprintf(fname, sizeof(fname)-1, "%s/%s", calib_path, desc->fname); + fname[sizeof(fname)-1] = '\0'; + + st->fp = fopen(fname, "rb"); + if (!st->fp) { + LOGP(DL1C, LOGL_NOTICE, "Failed to open '%s' for calibration data.\n", fname); + + /* TODO (oramadan): Fix OML alarms + if( fl1h->phy_inst->trx ){ + fl1h->phy_inst->trx->mo.obj_inst.trx_nr = fl1h->phy_inst->trx->nr; + + alarm_sig_data.mo = &fl1h->phy_inst->trx->mo; + alarm_sig_data.add_text = (char*)&fname[0]; + osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_FAIL_OPEN_CALIB_ALARM, &alarm_sig_data); + } + */ + return -1; + } + return 0; +} + +static int calib_file_close(struct oc2gl1_hdl *fl1h) +{ + struct calib_send_state *st = &fl1h->st; + + if (st->fp) { + fclose(st->fp); + st->fp = NULL; + } + return 0; +} + +/* iteratively download the calibration data into the L1 */ + +static int calib_send_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data); + +/* send a chunk of calibration tabledata for a single specified file */ +static int calib_file_send_next_chunk(struct oc2gl1_hdl *fl1h) +{ + struct calib_send_state *st = &fl1h->st; + Oc2g_Prim_t *prim; + struct msgb *msg; + size_t n; + + msg = sysp_msgb_alloc(); + prim = msgb_sysprim(msg); + + prim->id = Oc2g_PrimId_SetCalibTblReq; + prim->u.setCalibTblReq.offset = (uint32_t)ftell(st->fp); + n = fread(prim->u.setCalibTblReq.u8Data, 1, + sizeof(prim->u.setCalibTblReq.u8Data), st->fp); + prim->u.setCalibTblReq.length = n; + + + if (n == 0) { + /* The table data has been completely sent and acknowledged */ + LOGP(DL1C, LOGL_NOTICE, "L1 calibration table %s loaded\n", + calib_files[st->last_file_idx].fname); + + calib_file_close(fl1h); + + msgb_free(msg); + + /* Send the next one if any */ + st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx); + if (st->last_file_idx >= 0) { + return calib_file_send(fl1h, + &calib_files[st->last_file_idx]); + } + + LOGP(DL1C, LOGL_INFO, "L1 calibration table loading complete!\n"); + return 0; + } + + return l1if_req_compl(fl1h, msg, calib_send_compl_cb, NULL); +} + +/* send the calibration table for a single specified file */ +static int calib_file_send(struct oc2gl1_hdl *fl1h, + const struct calib_file_desc *desc) +{ + struct calib_send_state *st = &fl1h->st; + int rc; + + rc = calib_file_open(fl1h, desc); + if (rc < 0) { + /* still, we'd like to continue trying to load + * calibration for all other bands */ + st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx); + if (st->last_file_idx >= 0) + return calib_file_send(fl1h, + &calib_files[st->last_file_idx]); + + LOGP(DL1C, LOGL_INFO, "L1 calibration table loading complete!\n"); + return 0; + } + + rc = calib_verify(fl1h, desc); + if (rc < 0) { + LOGP(DL1C, LOGL_NOTICE,"Verify L1 calibration table %s -> failed (%d)\n", desc->fname, rc); + + /* TODO (oramadan): Fix OML alarms + if (fl1h->phy_inst->trx) { + fl1h->phy_inst->trx->mo.obj_inst.trx_nr = fl1h->phy_inst->trx->nr; + + alarm_sig_data.mo = &fl1h->phy_inst->trx->mo; + alarm_sig_data.add_text = (char*)&desc->fname[0]; + memcpy(alarm_sig_data.spare, &rc, sizeof(int)); + osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_FAIL_VERIFY_CALIB_ALARM, &alarm_sig_data); + } + */ + + st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx); + + if (st->last_file_idx >= 0) + return calib_file_send(fl1h, + &calib_files[st->last_file_idx]); + return 0; + + } + + LOGP(DL1C, LOGL_INFO, "Verify L1 calibration table %s -> done\n", desc->fname); + + return calib_file_send_next_chunk(fl1h); +} + +/* completion callback after every SetCalibTbl is confirmed */ +static int calib_send_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx); + struct calib_send_state *st = &fl1h->st; + Oc2g_Prim_t *prim = msgb_sysprim(l1_msg); + + if (prim->u.setCalibTblCnf.status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_ERROR, "L1 rejected calibration table\n"); + + msgb_free(l1_msg); + + calib_file_close(fl1h); + + /* Skip this one and try the next one */ + st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx); + if (st->last_file_idx >= 0) { + return calib_file_send(fl1h, + &calib_files[st->last_file_idx]); + } + + LOGP(DL1C, LOGL_INFO, "L1 calibration table loading complete!\n"); + return 0; + } + + msgb_free(l1_msg); + + /* Keep sending the calibration file data */ + return calib_file_send_next_chunk(fl1h); +} + +int calib_load(struct oc2gl1_hdl *fl1h) +{ + int rc; + struct calib_send_state *st = &fl1h->st; + char *calib_path = fl1h->phy_inst->u.oc2g.calib_path; + + if (!calib_path) { + LOGP(DL1C, LOGL_NOTICE, "Calibration file path not specified\n"); + + /* TODO (oramadan): Fix OML alarms + if( fl1h->phy_inst->trx ){ + fl1h->phy_inst->trx->mo.obj_inst.trx_nr = fl1h->phy_inst->trx->nr; + + alarm_sig_data.mo = &fl1h->phy_inst->trx->mo; + osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_NO_CALIB_PATH_ALARM, &alarm_sig_data); + } + */ + return -1; + } + + rc = get_next_calib_file_idx(fl1h, -1); + if (rc < 0) { + return -1; + } + st->last_file_idx = rc; + + return calib_file_send(fl1h, &calib_files[st->last_file_idx]); +} + + +static int calib_verify(struct oc2gl1_hdl *fl1h, const struct calib_file_desc *desc) +{ + int rc, sz; + struct calib_send_state *st = &fl1h->st; + struct phy_link *plink = fl1h->phy_inst->phy_link; + char *rbuf; + struct calTbl_t *calTbl; + char calChkSum ; + + + /* calculate file size in bytes */ + fseek(st->fp, 0L, SEEK_END); + sz = ftell(st->fp); + + /* rewind read poiner */ + fseek(st->fp, 0L, SEEK_SET); + + /* read file */ + rbuf = (char *) malloc( sizeof(char) * sz ); + + rc = fread(rbuf, 1, sizeof(char) * sz, st->fp); + if (rc != sz) { + LOGP(DL1C, LOGL_ERROR, "%s reading error\n", desc->fname); + free(rbuf); + + /* close file */ + rc = calib_file_close(fl1h); + if (rc < 0 ) { + LOGP(DL1C, LOGL_ERROR, "%s can not close\n", desc->fname); + return rc; + } + + return -2; + } + + + calTbl = (struct calTbl_t*) rbuf; + /* calculate file checksum */ + calChkSum = 0; + while (sz--) { + calChkSum ^= rbuf[sz]; + } + + /* validate Tx calibration parity */ + if (calChkSum) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid checksum %x.\n", desc->fname, calChkSum); + return -4; + } + + /* validate Tx calibration header */ + if (calTbl->hdr.v1.u8Version != CALIB_HDR_V1) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid header version %u.\n", desc->fname, calTbl->hdr.v1.u8Version); + return -5; + } + + /* validate calibration description */ + if (calTbl->hdr.v1.toc.u32DescOfst == 0xFFFFFFFF) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration description offset.\n", desc->fname); + return -6; + } + + /* validate calibration date */ + if (calTbl->hdr.v1.toc.u32DateOfst == 0xFFFFFFFF) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration date offset.\n", desc->fname); + return -7; + } + + LOGP(DL1C, LOGL_INFO, "L1 calibration table %s created on %s\n", + desc->fname, + calTbl->u8RawData + calTbl->hdr.v1.toc.u32DateOfst); + + /* validate calibration station */ + if (calTbl->hdr.v1.toc.u32StationOfst == 0xFFFFFFFF) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration station ID offset.\n", desc->fname); + return -8; + } + + /* validate FPGA FW version */ + if (calTbl->hdr.v1.toc.u32FpgaFwVerOfst == 0xFFF) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid FPGA FW version offset.\n", desc->fname); + return -9; + } + + /* validate DSP FW version */ + if (calTbl->hdr.v1.toc.u32DspFwVerOfst == 0xFFFFFFFF) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid DSP FW version offset.\n", desc->fname); + return -10; + } + + /* validate Tx calibration data offset */ + if (calTbl->hdr.v1.toc.u32DataOfst == 0xFFFFFFFF) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration data offset.\n", desc->fname); + return -11; + } + + if (!desc->rx) { + + /* parse min/max Tx power */ + fl1h->phy_inst->u.oc2g.minTxPower = calTbl->u8RawData[calTbl->hdr.v1.toc.u32DataOfst + (5 << 2)]; + fl1h->phy_inst->u.oc2g.maxTxPower = calTbl->u8RawData[calTbl->hdr.v1.toc.u32DataOfst + (6 << 2)]; + + /* override nominal Tx power of given TRX if needed */ + if (fl1h->phy_inst->trx->nominal_power > fl1h->phy_inst->u.oc2g.maxTxPower) { + LOGP(DL1C, LOGL_INFO, "Set TRX %u nominal Tx power to %d dBm (%d)\n", + plink->num, + fl1h->phy_inst->u.oc2g.maxTxPower, + fl1h->phy_inst->trx->nominal_power); + + fl1h->phy_inst->trx->nominal_power = fl1h->phy_inst->u.oc2g.maxTxPower; + } + + if (fl1h->phy_inst->trx->nominal_power < fl1h->phy_inst->u.oc2g.minTxPower) { + LOGP(DL1C, LOGL_INFO, "Set TRX %u nominal Tx power to %d dBm (%d)\n", + plink->num, + fl1h->phy_inst->u.oc2g.minTxPower, + fl1h->phy_inst->trx->nominal_power); + + fl1h->phy_inst->trx->nominal_power = fl1h->phy_inst->u.lc15.minTxPower; + } + + if (fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm > to_mdB(fl1h->phy_inst->u.oc2g.maxTxPower) ) { + LOGP(DL1C, LOGL_INFO, "Set TRX %u Tx power parameter to %d dBm (%d)\n", + plink->num, + to_mdB(fl1h->phy_inst->u.oc2g.maxTxPower), + fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm); + + fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm = to_mdB(fl1h->phy_inst->u.oc2g.maxTxPower); + } + + if (fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm < to_mdB(fl1h->phy_inst->u.oc2g.minTxPower) ) { + LOGP(DL1C, LOGL_INFO, "Set TRX %u Tx power parameter to %d dBm (%d)\n", + plink->num, + to_mdB(fl1h->phy_inst->u.oc2g.minTxPower), + fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm); + + fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm = to_mdB(fl1h->phy_inst->u.oc2g.minTxPower); + } + + LOGP(DL1C, LOGL_DEBUG, "%s: minTxPower=%d, maxTxPower=%d\n", + desc->fname, + fl1h->phy_inst->u.oc2g.minTxPower, + fl1h->phy_inst->u.oc2g.maxTxPower ); + } + + /* rewind read pointer for subsequence tasks */ + fseek(st->fp, 0L, SEEK_SET); + free(rbuf); + + return 0; +} + diff --git a/src/osmo-bts-oc2g/hw_info.ver_major b/src/osmo-bts-oc2g/hw_info.ver_major new file mode 100644 index 000000000..e69de29bb diff --git a/src/osmo-bts-oc2g/hw_misc.c b/src/osmo-bts-oc2g/hw_misc.c new file mode 100644 index 000000000..31daf0786 --- /dev/null +++ b/src/osmo-bts-oc2g/hw_misc.c @@ -0,0 +1,88 @@ +/* Misc HW routines for NuRAN Wireless OC-2G BTS */ + +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * (C) 2012 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "hw_misc.h" + +int oc2gbts_led_set(enum oc2gbts_led_color c) +{ + int fd, rc; + uint8_t cmd[2]; + + switch (c) { + case LED_OFF: + cmd[0] = 0; + cmd[1] = 0; + break; + case LED_RED: + cmd[0] = 1; + cmd[1] = 0; + break; + case LED_GREEN: + cmd[0] = 0; + cmd[1] = 1; + break; + case LED_ORANGE: + cmd[0] = 1; + cmd[1] = 1; + break; + default: + return -EINVAL; + } + + fd = open("/var/oc2g/leds/led0/brightness", O_WRONLY); + if (fd < 0) + return -ENODEV; + + rc = write(fd, cmd[0] ? "1" : "0", 2); + if (rc != 2) { + close(fd); + return -1; + } + close(fd); + + fd = open("/var/oc2g/leds/led1/brightness", O_WRONLY); + if (fd < 0) + return -ENODEV; + + rc = write(fd, cmd[1] ? "1" : "0", 2); + if (rc != 2) { + close(fd); + return -1; + } + close(fd); + return 0; +} diff --git a/src/osmo-bts-oc2g/hw_misc.h b/src/osmo-bts-oc2g/hw_misc.h new file mode 100644 index 000000000..b8f3332af --- /dev/null +++ b/src/osmo-bts-oc2g/hw_misc.h @@ -0,0 +1,13 @@ +#ifndef _HW_MISC_H +#define _HW_MISC_H + +enum oc2gbts_led_color { + LED_OFF, + LED_RED, + LED_GREEN, + LED_ORANGE, +}; + +int oc2gbts_led_set(enum oc2gbts_led_color c); + +#endif diff --git a/src/osmo-bts-oc2g/l1_if.c b/src/osmo-bts-oc2g/l1_if.c new file mode 100644 index 000000000..d8fdd9686 --- /dev/null +++ b/src/osmo-bts-oc2g/l1_if.c @@ -0,0 +1,1755 @@ +/* Interface handler for NuRAN Wireless OC-2G L1 */ + +/* Copyright (C) 2015 by Yves Godin + * Copyright (C) 2016 by Harald Welte + * + * Based on sysmoBTS: + * (C) 2011-2014 by Harald Welte + * (C) 2014 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "oc2gbts.h" +#include "l1_if.h" +#include "l1_transp.h" +#include "hw_misc.h" +#include "misc/oc2gbts_par.h" +#include "misc/oc2gbts_bid.h" +#include "utils.h" + +extern unsigned int dsp_trace; + +struct wait_l1_conf { + struct llist_head list; /* internal linked list */ + struct osmo_timer_list timer; /* timer for L1 timeout */ + unsigned int conf_prim_id; /* primitive we expect in response */ + HANDLE conf_hLayer3; /* layer 3 handle we expect in response */ + unsigned int is_sys_prim; /* is this a system (1) or L1 (0) primitive */ + l1if_compl_cb *cb; + void *cb_data; +}; + +static void release_wlc(struct wait_l1_conf *wlc) +{ + osmo_timer_del(&wlc->timer); + talloc_free(wlc); +} + +static void l1if_req_timeout(void *data) +{ + struct wait_l1_conf *wlc = data; + + if (wlc->is_sys_prim) + LOGP(DL1C, LOGL_FATAL, "Timeout waiting for SYS primitive %s\n", + get_value_string(oc2gbts_sysprim_names, wlc->conf_prim_id)); + else + LOGP(DL1C, LOGL_FATAL, "Timeout waiting for L1 primitive %s\n", + get_value_string(oc2gbts_l1prim_names, wlc->conf_prim_id)); + exit(23); +} + +static HANDLE l1p_get_hLayer3(GsmL1_Prim_t *prim) +{ + switch (prim->id) { + case GsmL1_PrimId_MphInitReq: + return prim->u.mphInitReq.hLayer3; + case GsmL1_PrimId_MphCloseReq: + return prim->u.mphCloseReq.hLayer3; + case GsmL1_PrimId_MphConnectReq: + return prim->u.mphConnectReq.hLayer3; + case GsmL1_PrimId_MphDisconnectReq: + return prim->u.mphDisconnectReq.hLayer3; + case GsmL1_PrimId_MphActivateReq: + return prim->u.mphActivateReq.hLayer3; + case GsmL1_PrimId_MphDeactivateReq: + return prim->u.mphDeactivateReq.hLayer3; + case GsmL1_PrimId_MphConfigReq: + return prim->u.mphConfigReq.hLayer3; + case GsmL1_PrimId_MphMeasureReq: + return prim->u.mphMeasureReq.hLayer3; + case GsmL1_PrimId_MphInitCnf: + return prim->u.mphInitCnf.hLayer3; + case GsmL1_PrimId_MphCloseCnf: + return prim->u.mphCloseCnf.hLayer3; + case GsmL1_PrimId_MphConnectCnf: + return prim->u.mphConnectCnf.hLayer3; + case GsmL1_PrimId_MphDisconnectCnf: + return prim->u.mphDisconnectCnf.hLayer3; + case GsmL1_PrimId_MphActivateCnf: + return prim->u.mphActivateCnf.hLayer3; + case GsmL1_PrimId_MphDeactivateCnf: + return prim->u.mphDeactivateCnf.hLayer3; + case GsmL1_PrimId_MphConfigCnf: + return prim->u.mphConfigCnf.hLayer3; + case GsmL1_PrimId_MphMeasureCnf: + return prim->u.mphMeasureCnf.hLayer3; + case GsmL1_PrimId_MphTimeInd: + case GsmL1_PrimId_MphSyncInd: + case GsmL1_PrimId_PhEmptyFrameReq: + case GsmL1_PrimId_PhDataReq: + case GsmL1_PrimId_PhConnectInd: + case GsmL1_PrimId_PhReadyToSendInd: + case GsmL1_PrimId_PhDataInd: + case GsmL1_PrimId_PhRaInd: + break; + default: + LOGP(DL1C, LOGL_ERROR, "unknown L1 primitive %u\n", prim->id); + break; + } + return 0; +} + +static int _l1if_req_compl(struct oc2gl1_hdl *fl1h, struct msgb *msg, + int is_system_prim, l1if_compl_cb *cb, void *data) +{ + struct wait_l1_conf *wlc; + struct osmo_wqueue *wqueue; + unsigned int timeout_secs; + + /* allocate new wsc and store reference to mutex and conf_id */ + wlc = talloc_zero(fl1h, struct wait_l1_conf); + wlc->cb = cb; + wlc->cb_data = data; + + /* Make sure we actually have received a REQUEST type primitive */ + if (is_system_prim == 0) { + GsmL1_Prim_t *l1p = msgb_l1prim(msg); + + LOGP(DL1P, LOGL_DEBUG, "Tx L1 prim %s\n", + get_value_string(oc2gbts_l1prim_names, l1p->id)); + + if (oc2gbts_get_l1prim_type(l1p->id) != L1P_T_REQ) { + LOGP(DL1C, LOGL_ERROR, "L1 Prim %s is not a Request!\n", + get_value_string(oc2gbts_l1prim_names, l1p->id)); + talloc_free(wlc); + return -EINVAL; + } + wlc->is_sys_prim = 0; + wlc->conf_prim_id = oc2gbts_get_l1prim_conf(l1p->id); + wlc->conf_hLayer3 = l1p_get_hLayer3(l1p); + wqueue = &fl1h->write_q[MQ_L1_WRITE]; + timeout_secs = 30; + } else { + Oc2g_Prim_t *sysp = msgb_sysprim(msg); + + LOGP(DL1C, LOGL_DEBUG, "Tx SYS prim %s\n", + get_value_string(oc2gbts_sysprim_names, sysp->id)); + + if (oc2gbts_get_sysprim_type(sysp->id) != L1P_T_REQ) { + LOGP(DL1C, LOGL_ERROR, "SYS Prim %s is not a Request!\n", + get_value_string(oc2gbts_sysprim_names, sysp->id)); + talloc_free(wlc); + return -EINVAL; + } + wlc->is_sys_prim = 1; + wlc->conf_prim_id = oc2gbts_get_sysprim_conf(sysp->id); + wqueue = &fl1h->write_q[MQ_SYS_WRITE]; + timeout_secs = 30; + } + + /* enqueue the message in the queue and add wsc to list */ + if (osmo_wqueue_enqueue(wqueue, msg) != 0) { + /* So we will get a timeout but the log message might help */ + LOGP(DL1C, LOGL_ERROR, "Write queue for %s full. dropping msg.\n", + is_system_prim ? "system primitive" : "gsm"); + msgb_free(msg); + } + llist_add(&wlc->list, &fl1h->wlc_list); + + /* schedule a timer for timeout_secs seconds. If DSP fails to respond, we terminate */ + wlc->timer.data = wlc; + wlc->timer.cb = l1if_req_timeout; + osmo_timer_schedule(&wlc->timer, timeout_secs, 0); + + return 0; +} + +/* send a request primitive to the L1 and schedule completion call-back */ +int l1if_req_compl(struct oc2gl1_hdl *fl1h, struct msgb *msg, + l1if_compl_cb *cb, void *data) +{ + return _l1if_req_compl(fl1h, msg, 1, cb, data); +} + +int l1if_gsm_req_compl(struct oc2gl1_hdl *fl1h, struct msgb *msg, + l1if_compl_cb *cb, void *data) +{ + return _l1if_req_compl(fl1h, msg, 0, cb, data); +} + +/* allocate a msgb containing a GsmL1_Prim_t */ +struct msgb *l1p_msgb_alloc(void) +{ + struct msgb *msg = msgb_alloc(sizeof(GsmL1_Prim_t), "l1_prim"); + + if (msg) + msg->l1h = msgb_put(msg, sizeof(GsmL1_Prim_t)); + + return msg; +} + +/* allocate a msgb containing a Oc2g_Prim_t */ +struct msgb *sysp_msgb_alloc(void) +{ + struct msgb *msg = msgb_alloc(sizeof(Oc2g_Prim_t), "sys_prim"); + + if (msg) + msg->l1h = msgb_put(msg, sizeof(Oc2g_Prim_t)); + + return msg; +} + +static GsmL1_PhDataReq_t * +data_req_from_rts_ind(GsmL1_Prim_t *l1p, + const GsmL1_PhReadyToSendInd_t *rts_ind) +{ + GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq; + + l1p->id = GsmL1_PrimId_PhDataReq; + + /* copy fields from PH-RSS.ind */ + data_req->hLayer1 = rts_ind->hLayer1; + data_req->u8Tn = rts_ind->u8Tn; + data_req->u32Fn = rts_ind->u32Fn; + data_req->sapi = rts_ind->sapi; + data_req->subCh = rts_ind->subCh; + data_req->u8BlockNbr = rts_ind->u8BlockNbr; + + return data_req; +} + +static GsmL1_PhEmptyFrameReq_t * +empty_req_from_rts_ind(GsmL1_Prim_t *l1p, + const GsmL1_PhReadyToSendInd_t *rts_ind) +{ + GsmL1_PhEmptyFrameReq_t *empty_req = &l1p->u.phEmptyFrameReq; + + l1p->id = GsmL1_PrimId_PhEmptyFrameReq; + + empty_req->hLayer1 = rts_ind->hLayer1; + empty_req->u8Tn = rts_ind->u8Tn; + empty_req->u32Fn = rts_ind->u32Fn; + empty_req->sapi = rts_ind->sapi; + empty_req->subCh = rts_ind->subCh; + empty_req->u8BlockNbr = rts_ind->u8BlockNbr; + + return empty_req; +} + +/* fill PH-DATA.req from l1sap primitive */ +static GsmL1_PhDataReq_t * +data_req_from_l1sap(GsmL1_Prim_t *l1p, struct oc2gl1_hdl *fl1, + uint8_t tn, uint32_t fn, uint8_t sapi, uint8_t sub_ch, + uint8_t block_nr, uint8_t len) +{ + GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq; + + l1p->id = GsmL1_PrimId_PhDataReq; + + /* copy fields from PH-RSS.ind */ + data_req->hLayer1 = (HANDLE)fl1->hLayer1; + data_req->u8Tn = tn; + data_req->u32Fn = fn; + data_req->sapi = sapi; + data_req->subCh = sub_ch; + data_req->u8BlockNbr = block_nr; + + data_req->msgUnitParam.u8Size = len; + + return data_req; +} + +/* fill PH-EMPTY_FRAME.req from l1sap primitive */ +static GsmL1_PhEmptyFrameReq_t * +empty_req_from_l1sap(GsmL1_Prim_t *l1p, struct oc2gl1_hdl *fl1, + uint8_t tn, uint32_t fn, uint8_t sapi, + uint8_t subch, uint8_t block_nr) +{ + GsmL1_PhEmptyFrameReq_t *empty_req = &l1p->u.phEmptyFrameReq; + + l1p->id = GsmL1_PrimId_PhEmptyFrameReq; + + empty_req->hLayer1 = (HANDLE)fl1->hLayer1; + empty_req->u8Tn = tn; + empty_req->u32Fn = fn; + empty_req->sapi = sapi; + empty_req->subCh = subch; + empty_req->u8BlockNbr = block_nr; + + return empty_req; +} + +/* fill frame PH-DATA.req from l1sap primitive */ +static GsmL1_PhDataReq_t * +fill_req_from_l1sap(GsmL1_Prim_t *l1p, struct oc2gl1_hdl *fl1, + uint8_t tn, uint32_t fn, uint8_t sapi, uint8_t sub_ch, + uint8_t block_nr) +{ + GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq; + GsmL1_MsgUnitParam_t *msu_param; + uint8_t *l1_payload; + + msu_param = &data_req->msgUnitParam; + l1_payload = &msu_param->u8Buffer[0]; + l1p->id = GsmL1_PrimId_PhDataReq; + + memset(l1_payload, 0x2B, GSM_MACBLOCK_LEN); + /* address field */ + l1_payload[0] = 0x03; + /* control field */ + l1_payload[1] = 0x03; + /* length field */ + l1_payload[2] = 0x01; + + /* copy fields from PH-RTS.ind */ + data_req->hLayer1 = (HANDLE)fl1->hLayer1; + data_req->u8Tn = tn; + data_req->u32Fn = fn; + data_req->sapi = sapi; + data_req->subCh = sub_ch; + data_req->u8BlockNbr = block_nr; + data_req->msgUnitParam.u8Size = GSM_MACBLOCK_LEN; + + + LOGP(DL1C, LOGL_DEBUG, "Send fill frame on in none DTX mode Tn=%d, Fn=%d, SAPI=%d, SubCh=%d, BlockNr=%d dump=%s\n", + tn, + fn, + sapi, + sub_ch, + block_nr, + osmo_hexdump(data_req->msgUnitParam.u8Buffer, data_req->msgUnitParam.u8Size)); + + return data_req; +} + +static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap, bool use_cache) +{ + struct oc2gl1_hdl *fl1 = trx_oc2gl1_hdl(trx); + struct msgb *l1msg = l1p_msgb_alloc(); + struct gsm_lchan *lchan; + uint32_t u32Fn; + uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi = 0; + uint8_t chan_nr, link_id; + int len; + + if (!msg) { + LOGPFN(DL1C, LOGL_FATAL, l1sap->u.data.fn, "PH-DATA.req without msg. Please fix!\n"); + abort(); + } + + len = msgb_l2len(msg); + + chan_nr = l1sap->u.data.chan_nr; + link_id = l1sap->u.data.link_id; + u32Fn = l1sap->u.data.fn; + u8Tn = L1SAP_CHAN2TS(chan_nr); + subCh = 0x1f; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + if (L1SAP_IS_LINK_SACCH(link_id)) { + sapi = GsmL1_Sapi_Sacch; + if (!L1SAP_IS_CHAN_TCHF(chan_nr) && !L1SAP_IS_CHAN_PDCH(chan_nr)) + subCh = l1sap_chan2ss(chan_nr); + } else if (L1SAP_IS_CHAN_TCHF(chan_nr) || L1SAP_IS_CHAN_PDCH(chan_nr)) { + if (ts_is_pdch(&trx->ts[u8Tn])) { + if (L1SAP_IS_PTCCH(u32Fn)) { + sapi = GsmL1_Sapi_Ptcch; + u8BlockNbr = L1SAP_FN2PTCCHBLOCK(u32Fn); + } else { + sapi = GsmL1_Sapi_Pdtch; + u8BlockNbr = L1SAP_FN2MACBLOCK(u32Fn); + } + } else { + sapi = GsmL1_Sapi_FacchF; + u8BlockNbr = (u32Fn % 13) >> 2; + } + } else if (L1SAP_IS_CHAN_TCHH(chan_nr)) { + subCh = L1SAP_CHAN2SS_TCHH(chan_nr); + sapi = GsmL1_Sapi_FacchH; + u8BlockNbr = (u32Fn % 26) >> 3; + } else if (L1SAP_IS_CHAN_SDCCH4(chan_nr)) { + subCh = L1SAP_CHAN2SS_SDCCH4(chan_nr); + sapi = GsmL1_Sapi_Sdcch; + } else if (L1SAP_IS_CHAN_SDCCH8(chan_nr)) { + subCh = L1SAP_CHAN2SS_SDCCH8(chan_nr); + sapi = GsmL1_Sapi_Sdcch; + } else if (L1SAP_IS_CHAN_BCCH(chan_nr)) { + sapi = GsmL1_Sapi_Bcch; + } else if (L1SAP_IS_CHAN_CBCH(chan_nr)) { + sapi = GsmL1_Sapi_Cbch; + } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) { + /* The sapi depends on DSP configuration, not + * on the actual SYSTEM INFORMATION 3. */ + u8BlockNbr = l1sap_fn2ccch_block(u32Fn); + if (u8BlockNbr >= num_agch(trx, "PH-DATA-REQ")) + sapi = GsmL1_Sapi_Pch; + else + sapi = GsmL1_Sapi_Agch; + } else { + LOGPFN(DL1C, LOGL_NOTICE, u32Fn, "unknown prim %d op %d " + "chan_nr %d link_id %d\n", l1sap->oph.primitive, + l1sap->oph.operation, chan_nr, link_id); + msgb_free(l1msg); + return -EINVAL; + } + + /* convert l1sap message to GsmL1 primitive, keep payload */ + if (len) { + /* data request */ + GsmL1_Prim_t *l1p = msgb_l1prim(l1msg); + data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr, len); + if (use_cache) + memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer, + lchan->tch.dtx.facch, msgb_l2len(msg)); + else if (dtx_dl_amr_enabled(lchan) && + ((lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F) || + (lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F) || + (lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F))) { + if (sapi == GsmL1_Sapi_FacchF) { + sapi = GsmL1_Sapi_TchF; + } + if (sapi == GsmL1_Sapi_FacchH) { + sapi = GsmL1_Sapi_TchH; + subCh = L1SAP_CHAN2SS_TCHH(chan_nr); + u8BlockNbr = (u32Fn % 13) >> 2; + } + if (sapi == GsmL1_Sapi_TchH || sapi == GsmL1_Sapi_TchF) { + /* FACCH interruption of DTX silence */ + /* cache FACCH data */ + memcpy(lchan->tch.dtx.facch, msg->l2h, + msgb_l2len(msg)); + /* prepare ONSET or INH message */ + if(lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F) + l1p->u.phDataReq.msgUnitParam.u8Buffer[0] = + GsmL1_TchPlType_Amr_Onset; + else if(lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F) + l1p->u.phDataReq.msgUnitParam.u8Buffer[0] = + GsmL1_TchPlType_Amr_SidUpdateInH; + else if(lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F) + l1p->u.phDataReq.msgUnitParam.u8Buffer[0] = + GsmL1_TchPlType_Amr_SidFirstInH; + /* ignored CMR/CMI pair */ + l1p->u.phDataReq.msgUnitParam.u8Buffer[1] = 0; + l1p->u.phDataReq.msgUnitParam.u8Buffer[2] = 0; + /* update length */ + data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, + subCh, u8BlockNbr, 3); + /* update FN so it can be checked by TCH silence + resume handler */ + lchan->tch.dtx.fn = LCHAN_FN_DUMMY; + } + } else if (dtx_dl_amr_enabled(lchan) && + lchan->tch.dtx.dl_amr_fsm->state == ST_FACCH) { + /* update FN so it can be checked by TCH silence + resume handler */ + lchan->tch.dtx.fn = LCHAN_FN_DUMMY; + } + else { + OSMO_ASSERT(msgb_l2len(msg) <= sizeof(l1p->u.phDataReq.msgUnitParam.u8Buffer)); + memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer, msg->l2h, + msgb_l2len(msg)); + } + LOGPFN(DL1P, LOGL_DEBUG, u32Fn, "PH-DATA.req(%s)\n", + osmo_hexdump(l1p->u.phDataReq.msgUnitParam.u8Buffer, + l1p->u.phDataReq.msgUnitParam.u8Size)); + } else { + GsmL1_Prim_t *l1p = msgb_l1prim(l1msg); + if (lchan->rsl_cmode == RSL_CMOD_SPD_SIGN) + /* fill frame */ + fill_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr); + else { + if (lchan->ts->trx->bts->dtxd) + /* empty frame */ + empty_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr); + else + /* fill frame */ + fill_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr); + } + } + + /* send message to DSP's queue */ + if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], l1msg) != 0) { + LOGPFN(DL1P, LOGL_ERROR, u32Fn, "MQ_L1_WRITE queue full. Dropping msg.\n"); + msgb_free(l1msg); + } else + dtx_int_signal(lchan); + + if (dtx_recursion(lchan)) + ph_data_req(trx, msg, l1sap, true); + return 0; +} + +static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap, bool use_cache, bool marker) +{ + struct oc2gl1_hdl *fl1 = trx_oc2gl1_hdl(trx); + struct gsm_lchan *lchan; + uint32_t u32Fn; + uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi; + uint8_t chan_nr; + GsmL1_Prim_t *l1p; + struct msgb *nmsg = NULL; + int rc = -1; + + chan_nr = l1sap->u.tch.chan_nr; + u32Fn = l1sap->u.tch.fn; + u8Tn = L1SAP_CHAN2TS(chan_nr); + u8BlockNbr = (u32Fn % 13) >> 2; + if (L1SAP_IS_CHAN_TCHH(chan_nr)) { + subCh = L1SAP_CHAN2SS_TCHH(chan_nr); + sapi = GsmL1_Sapi_TchH; + } else { + subCh = 0x1f; + sapi = GsmL1_Sapi_TchF; + } + + lchan = get_lchan_by_chan_nr(trx, chan_nr); + + /* create new message and fill data */ + if (msg) { + msgb_pull(msg, sizeof(*l1sap)); + /* create new message */ + nmsg = l1p_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + l1p = msgb_l1prim(nmsg); + rc = l1if_tch_encode(lchan, + l1p->u.phDataReq.msgUnitParam.u8Buffer, + &l1p->u.phDataReq.msgUnitParam.u8Size, + msg->data, msg->len, u32Fn, use_cache, + l1sap->u.tch.marker); + if (rc < 0) { + /* no data encoded for L1: smth will be generated below */ + msgb_free(nmsg); + nmsg = NULL; + } + } + + /* no message/data, we might generate an empty traffic msg or re-send + cached SID in case of DTX */ + if (!nmsg) + nmsg = gen_empty_tch_msg(lchan, u32Fn); + + /* no traffic message, we generate an empty msg */ + if (!nmsg) { + nmsg = l1p_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + } + + l1p = msgb_l1prim(nmsg); + + /* if we provide data, or if data is already in nmsg */ + if (l1p->u.phDataReq.msgUnitParam.u8Size) { + /* data request */ + data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, + u8BlockNbr, + l1p->u.phDataReq.msgUnitParam.u8Size); + } else { + /* empty frame */ + if (trx->bts->dtxd && trx != trx->bts->c0) + lchan->tch.dtx.dl_active = true; + empty_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr); + } + /* send message to DSP's queue */ + osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], nmsg); + if (dtx_is_first_p1(lchan)) + dtx_dispatch(lchan, E_FIRST); + else + dtx_int_signal(lchan); + + if (dtx_recursion(lchan)) /* DTX: send voice after ONSET was sent */ + return ph_tch_req(trx, l1sap->oph.msg, l1sap, true, false); + + return 0; +} + +static int mph_info_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap) +{ + struct oc2gl1_hdl *fl1 = trx_oc2gl1_hdl(trx); + uint8_t chan_nr; + struct gsm_lchan *lchan; + int rc = 0; + + switch (l1sap->u.info.type) { + case PRIM_INFO_ACT_CIPH: + chan_nr = l1sap->u.info.u.ciph_req.chan_nr; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + if (l1sap->u.info.u.ciph_req.uplink) { + l1if_set_ciphering(fl1, lchan, 0); + lchan->ciph_state = LCHAN_CIPH_RX_REQ; + } + if (l1sap->u.info.u.ciph_req.downlink) { + l1if_set_ciphering(fl1, lchan, 1); + lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ; + } + if (l1sap->u.info.u.ciph_req.downlink + && l1sap->u.info.u.ciph_req.uplink) + lchan->ciph_state = LCHAN_CIPH_RXTX_REQ; + break; + case PRIM_INFO_ACTIVATE: + case PRIM_INFO_DEACTIVATE: + case PRIM_INFO_MODIFY: + chan_nr = l1sap->u.info.u.act_req.chan_nr; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + if (l1sap->u.info.type == PRIM_INFO_ACTIVATE) + l1if_rsl_chan_act(lchan); + else if (l1sap->u.info.type == PRIM_INFO_MODIFY) { + if (lchan->ho.active == HANDOVER_WAIT_FRAME) + l1if_rsl_chan_mod(lchan); + else + l1if_rsl_mode_modify(lchan); + } else if (l1sap->u.info.u.act_req.sacch_only) + l1if_rsl_deact_sacch(lchan); + else + l1if_rsl_chan_rel(lchan); + break; + default: + LOGP(DL1C, LOGL_NOTICE, "unknown MPH-INFO.req %d\n", + l1sap->u.info.type); + rc = -EINVAL; + } + + return rc; +} + +/* primitive from common part */ +int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) +{ + struct msgb *msg = l1sap->oph.msg; + int rc = 0; + + /* called functions MUST NOT take ownership of msgb, as it is + * free()d below */ + switch (OSMO_PRIM_HDR(&l1sap->oph)) { + case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST): + rc = ph_data_req(trx, msg, l1sap, false); + break; + case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST): + rc = ph_tch_req(trx, msg, l1sap, false, l1sap->u.tch.marker); + break; + case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST): + rc = mph_info_req(trx, msg, l1sap); + break; + default: + LOGP(DL1C, LOGL_NOTICE, "unknown prim %d op %d\n", + l1sap->oph.primitive, l1sap->oph.operation); + rc = -EINVAL; + } + + msgb_free(msg); + + return rc; +} + +static int handle_mph_time_ind(struct oc2gl1_hdl *fl1, + GsmL1_MphTimeInd_t *time_ind, + struct msgb *msg) +{ + struct gsm_bts_trx *trx = oc2gl1_hdl_trx(fl1); + struct gsm_bts *bts = trx->bts; + struct osmo_phsap_prim l1sap; + uint32_t fn; + + /* increment the primitive count for the alive timer */ + fl1->alive_prim_cnt++; + + /* ignore every time indication, except for c0 */ + if (trx != bts->c0) { + msgb_free(msg); + return 0; + } + + fn = time_ind->u32Fn; + + memset(&l1sap, 0, sizeof(l1sap)); + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, + PRIM_OP_INDICATION, NULL); + l1sap.u.info.type = PRIM_INFO_TIME; + l1sap.u.info.u.time_ind.fn = fn; + + msgb_free(msg); + + return l1sap_up(trx, &l1sap); +} + +static enum gsm_phys_chan_config pick_pchan(struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_PDCH: + if (ts->flags & TS_F_PDCH_ACTIVE) + return GSM_PCHAN_PDCH; + return GSM_PCHAN_TCH_F; + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + return ts->dyn.pchan_is; + default: + return ts->pchan; + } +} + +static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts, + GsmL1_Sapi_t sapi, GsmL1_SubCh_t subCh, + uint8_t u8Tn, uint32_t u32Fn) +{ + uint8_t cbits = 0; + enum gsm_phys_chan_config pchan = pick_pchan(ts); + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH); + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH); + + switch (sapi) { + case GsmL1_Sapi_Bcch: + cbits = 0x10; + break; + case GsmL1_Sapi_Cbch: + cbits = 0xc8 >> 3; /* Osmocom extension for CBCH via L1SAP */ + break; + case GsmL1_Sapi_Sacch: + switch(pchan) { + case GSM_PCHAN_TCH_F: + cbits = 0x01; + break; + case GSM_PCHAN_TCH_H: + cbits = 0x02 + subCh; + break; + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + cbits = 0x04 + subCh; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + cbits = 0x08 + subCh; + break; + default: + LOGP(DL1C, LOGL_ERROR, "SACCH for pchan %d?\n", + pchan); + return 0; + } + break; + case GsmL1_Sapi_Sdcch: + switch(pchan) { + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + cbits = 0x04 + subCh; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + cbits = 0x08 + subCh; + break; + default: + LOGP(DL1C, LOGL_ERROR, "SDCCH for pchan %d?\n", + pchan); + return 0; + } + break; + case GsmL1_Sapi_Agch: + case GsmL1_Sapi_Pch: + cbits = 0x12; + break; + case GsmL1_Sapi_Pdtch: + case GsmL1_Sapi_Pacch: + switch(pchan) { + case GSM_PCHAN_PDCH: + cbits = 0x01; + break; + default: + LOGP(DL1C, LOGL_ERROR, "PDTCH for pchan %d?\n", + pchan); + return 0; + } + break; + case GsmL1_Sapi_TchF: + cbits = 0x01; + break; + case GsmL1_Sapi_TchH: + cbits = 0x02 + subCh; + break; + case GsmL1_Sapi_FacchF: + cbits = 0x01; + break; + case GsmL1_Sapi_FacchH: + cbits = 0x02 + subCh; + break; + case GsmL1_Sapi_Ptcch: + if (!L1SAP_IS_PTCCH(u32Fn)) { + LOGP(DL1C, LOGL_FATAL, "Not expecting PTCCH at frame " + "number other than 12, got it at %u (%u). " + "Please fix!\n", u32Fn % 52, u32Fn); + abort(); + } + switch(pchan) { + case GSM_PCHAN_PDCH: + cbits = 0x01; + break; + default: + LOGP(DL1C, LOGL_ERROR, "PTCCH for pchan %d?\n", + pchan); + return 0; + } + break; + default: + return 0; + } + + /* not reached due to default case above */ + return (cbits << 3) | u8Tn; +} + +static int handle_ph_readytosend_ind(struct oc2gl1_hdl *fl1, + GsmL1_PhReadyToSendInd_t *rts_ind, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = oc2gl1_hdl_trx(fl1); + struct gsm_bts *bts = trx->bts; + struct msgb *resp_msg; + GsmL1_PhDataReq_t *data_req; + GsmL1_MsgUnitParam_t *msu_param; + struct gsm_time g_time; + uint32_t t3p; + int rc; + struct osmo_phsap_prim *l1sap; + uint8_t chan_nr, link_id; + uint32_t fn; + + /* check if primitive should be handled by common part */ + chan_nr = chan_nr_by_sapi(&trx->ts[rts_ind->u8Tn], rts_ind->sapi, + rts_ind->subCh, rts_ind->u8Tn, rts_ind->u32Fn); + if (chan_nr) { + fn = rts_ind->u32Fn; + if (rts_ind->sapi == GsmL1_Sapi_Sacch) + link_id = LID_SACCH; + else + link_id = LID_DEDIC; + /* recycle the msgb and use it for the L1 primitive, + * which means that we (or our caller) must not free it */ + rc = msgb_trim(l1p_msg, sizeof(*l1sap)); + if (rc < 0) + MSGB_ABORT(l1p_msg, "No room for primitive\n"); + l1sap = msgb_l1sap_prim(l1p_msg); + if (rts_ind->sapi == GsmL1_Sapi_TchF + || rts_ind->sapi == GsmL1_Sapi_TchH) { + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH_RTS, + PRIM_OP_INDICATION, l1p_msg); + l1sap->u.tch.chan_nr = chan_nr; + l1sap->u.tch.fn = fn; + } else { + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RTS, + PRIM_OP_INDICATION, l1p_msg); + l1sap->u.data.link_id = link_id; + l1sap->u.data.chan_nr = chan_nr; + l1sap->u.data.fn = fn; + } + + return l1sap_up(trx, l1sap); + } + + gsm_fn2gsmtime(&g_time, rts_ind->u32Fn); + + DEBUGPGT(DL1P, &g_time, "Rx PH-RTS.ind SAPI=%s\n", + get_value_string(oc2gbts_l1sapi_names, rts_ind->sapi)); + + /* in all other cases, we need to allocate a new PH-DATA.ind + * primitive msgb and start to fill it */ + resp_msg = l1p_msgb_alloc(); + data_req = data_req_from_rts_ind(msgb_l1prim(resp_msg), rts_ind); + msu_param = &data_req->msgUnitParam; + + /* set default size */ + msu_param->u8Size = GSM_MACBLOCK_LEN; + + switch (rts_ind->sapi) { + case GsmL1_Sapi_Sch: + /* compute T3prime */ + t3p = (g_time.t3 - 1) / 10; + /* fill SCH burst with data */ + msu_param->u8Size = 4; + msu_param->u8Buffer[0] = (bts->bsic << 2) | (g_time.t1 >> 9); + msu_param->u8Buffer[1] = (g_time.t1 >> 1); + msu_param->u8Buffer[2] = (g_time.t1 << 7) | (g_time.t2 << 2) | (t3p >> 1); + msu_param->u8Buffer[3] = (t3p & 1); + break; + case GsmL1_Sapi_Prach: + goto empty_frame; + break; + case GsmL1_Sapi_Cbch: + /* get them from bts->si_buf[] */ + bts_cbch_get(bts, msu_param->u8Buffer, &g_time); + break; + default: + memcpy(msu_param->u8Buffer, fill_frame, GSM_MACBLOCK_LEN); + break; + } +tx: + + /* transmit */ + if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], resp_msg) != 0) { + LOGPGT(DL1C, LOGL_ERROR, &g_time, "MQ_L1_WRITE queue full. Dropping msg.\n"); + msgb_free(resp_msg); + } + + /* free the msgb, as we have not handed it to l1sap and thus + * need to release its memory */ + msgb_free(l1p_msg); + return 0; + +empty_frame: + /* in case we decide to send an empty frame... */ + empty_req_from_rts_ind(msgb_l1prim(resp_msg), rts_ind); + + goto tx; +} + +static void dump_meas_res(int ll, GsmL1_MeasParam_t *m) +{ + LOGPC(DL1C, ll, ", Meas: RSSI %-3.2f dBm, Qual %-3.2f dB, " + "BER %-3.2f, Timing %d\n", m->fRssi, m->fLinkQuality, + m->fBer, m->i16BurstTiming); +} + +static int process_meas_res(struct gsm_bts_trx *trx, uint8_t chan_nr, + GsmL1_MeasParam_t *m, uint32_t fn) +{ + struct osmo_phsap_prim l1sap; + memset(&l1sap, 0, sizeof(l1sap)); + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, + PRIM_OP_INDICATION, NULL); + l1sap.u.info.type = PRIM_INFO_MEAS; + l1sap.u.info.u.meas_ind.chan_nr = chan_nr; + l1sap.u.info.u.meas_ind.ta_offs_256bits = m->i16BurstTiming*64; + l1sap.u.info.u.meas_ind.ber10k = (unsigned int) (m->fBer * 10000); + l1sap.u.info.u.meas_ind.inv_rssi = (uint8_t) (m->fRssi * -1); + l1sap.u.info.u.meas_ind.fn = fn; + + /* l1sap wants to take msgb ownership. However, as there is no + * msg, it will msgb_free(l1sap.oph.msg == NULL) */ + return l1sap_up(trx, &l1sap); +} + +static int handle_ph_data_ind(struct oc2gl1_hdl *fl1, GsmL1_PhDataInd_t *data_ind, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = oc2gl1_hdl_trx(fl1); + uint8_t chan_nr, link_id; + struct osmo_phsap_prim *l1sap; + uint32_t fn; + struct gsm_time g_time; + uint8_t *data, len; + int rc = 0; + int8_t rssi; + + chan_nr = chan_nr_by_sapi(&trx->ts[data_ind->u8Tn], data_ind->sapi, + data_ind->subCh, data_ind->u8Tn, data_ind->u32Fn); + fn = data_ind->u32Fn; + link_id = (data_ind->sapi == GsmL1_Sapi_Sacch) ? LID_SACCH : LID_DEDIC; + gsm_fn2gsmtime(&g_time, fn); + + if (!chan_nr) { + LOGPGT(DL1C, LOGL_ERROR, &g_time, "PH-DATA-INDICATION for unknown sapi %s (%d)\n", + get_value_string(oc2gbts_l1sapi_names, data_ind->sapi), data_ind->sapi); + msgb_free(l1p_msg); + return ENOTSUP; + } + + process_meas_res(trx, chan_nr, &data_ind->measParam, fn); + + + DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind %s (hL2 %08x): %s\n", + get_value_string(oc2gbts_l1sapi_names, data_ind->sapi), (uint32_t)data_ind->hLayer2, + osmo_hexdump(data_ind->msgUnitParam.u8Buffer, data_ind->msgUnitParam.u8Size)); + dump_meas_res(LOGL_DEBUG, &data_ind->measParam); + + /* check for TCH */ + if (data_ind->sapi == GsmL1_Sapi_TchF + || data_ind->sapi == GsmL1_Sapi_TchH) { + /* TCH speech frame handling */ + rc = l1if_tch_rx(trx, chan_nr, l1p_msg); + msgb_free(l1p_msg); + return rc; + } + + /* get rssi */ + rssi = (int8_t) (data_ind->measParam.fRssi); + /* get data pointer and length */ + data = data_ind->msgUnitParam.u8Buffer; + len = data_ind->msgUnitParam.u8Size; + /* pull lower header part before data */ + msgb_pull(l1p_msg, data - l1p_msg->data); + /* trim remaining data to it's size, to get rid of upper header part */ + rc = msgb_trim(l1p_msg, len); + if (rc < 0) + MSGB_ABORT(l1p_msg, "No room for primitive data\n"); + l1p_msg->l2h = l1p_msg->data; + /* push new l1 header */ + l1p_msg->l1h = msgb_push(l1p_msg, sizeof(*l1sap)); + /* fill header */ + l1sap = msgb_l1sap_prim(l1p_msg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA, + PRIM_OP_INDICATION, l1p_msg); + l1sap->u.data.link_id = link_id; + l1sap->u.data.chan_nr = chan_nr; + l1sap->u.data.fn = fn; + l1sap->u.data.rssi = rssi; + if (!pcu_direct) { + l1sap->u.data.ber10k = data_ind->measParam.fBer * 10000; + l1sap->u.data.ta_offs_256bits = data_ind->measParam.i16BurstTiming*64; + l1sap->u.data.lqual_cb = data_ind->measParam.fLinkQuality * 10; + } + return l1sap_up(trx, l1sap); +} + +static int handle_ph_ra_ind(struct oc2gl1_hdl *fl1, GsmL1_PhRaInd_t *ra_ind, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = oc2gl1_hdl_trx(fl1); + struct gsm_bts *bts = trx->bts; + struct gsm_lchan *lchan; + struct osmo_phsap_prim *l1sap; + int rc; + struct ph_rach_ind_param rach_ind_param; + + /* FIXME: this should be deprecated/obsoleted as it bypasses rach.busy counting */ + if (ra_ind->measParam.fLinkQuality < bts->min_qual_rach) { + msgb_free(l1p_msg); + return 0; + } + + dump_meas_res(LOGL_DEBUG, &ra_ind->measParam); + + if ((ra_ind->msgUnitParam.u8Size != 1) && + (ra_ind->msgUnitParam.u8Size != 2)) { + LOGPFN(DL1P, LOGL_ERROR, ra_ind->u32Fn, "PH-RACH-INDICATION has %d bits\n", ra_ind->sapi); + msgb_free(l1p_msg); + return 0; + } + + /* We need to evaluate ra_ind before below msgb_trim(), since that invalidates *ra_ind. */ + rach_ind_param = (struct ph_rach_ind_param) { + /* .chan_nr set below */ + /* .ra set below */ + .acc_delay = 0, + .fn = ra_ind->u32Fn, + /* .is_11bit set below */ + /* .burst_type set below */ + .rssi = (int8_t) ra_ind->measParam.fRssi, + .ber10k = (unsigned int) (ra_ind->measParam.fBer * 10000.0), + .acc_delay_256bits = ra_ind->measParam.i16BurstTiming * 64, + }; + + lchan = l1if_hLayer_to_lchan(trx, (uint32_t)ra_ind->hLayer2); + if (!lchan || lchan->ts->pchan == GSM_PCHAN_CCCH || + lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4 || + lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH) + rach_ind_param.chan_nr = 0x88; + else + rach_ind_param.chan_nr = gsm_lchan2chan_nr(lchan); + + if (ra_ind->msgUnitParam.u8Size == 2) { + uint16_t temp; + uint16_t ra = ra_ind->msgUnitParam.u8Buffer[0]; + ra = ra << 3; + temp = (ra_ind->msgUnitParam.u8Buffer[1] & 0x7); + ra = ra | temp; + rach_ind_param.is_11bit = 1; + rach_ind_param.ra = ra; + } else { + rach_ind_param.is_11bit = 0; + rach_ind_param.ra = ra_ind->msgUnitParam.u8Buffer[0]; + } + + /* the old legacy full-bits acc_delay cannot express negative values */ + if (ra_ind->measParam.i16BurstTiming > 0) + rach_ind_param.acc_delay = ra_ind->measParam.i16BurstTiming >> 2; + + /* mapping of the burst type, the values are specific to + * osmo-bts-oc2g */ + switch (ra_ind->burstType) { + case GsmL1_BurstType_Access_0: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_ACCESS_0; + break; + case GsmL1_BurstType_Access_1: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_ACCESS_1; + break; + case GsmL1_BurstType_Access_2: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_ACCESS_2; + break; + default: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_NONE; + break; + } + + /* msgb_trim() invalidates ra_ind, make that abundantly clear: */ + ra_ind = NULL; + rc = msgb_trim(l1p_msg, sizeof(*l1sap)); + if (rc < 0) + MSGB_ABORT(l1p_msg, "No room for primitive data\n"); + l1sap = msgb_l1sap_prim(l1p_msg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, + l1p_msg); + l1sap->u.rach_ind = rach_ind_param; + + return l1sap_up(trx, l1sap); +} + +/* handle any random indication from the L1 */ +static int l1if_handle_ind(struct oc2gl1_hdl *fl1, struct msgb *msg) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(msg); + int rc = 0; + + /* all the below called functions must take ownership of the msgb */ + switch (l1p->id) { + case GsmL1_PrimId_MphTimeInd: + rc = handle_mph_time_ind(fl1, &l1p->u.mphTimeInd, msg); + break; + case GsmL1_PrimId_MphSyncInd: + msgb_free(msg); + break; + case GsmL1_PrimId_PhConnectInd: + msgb_free(msg); + break; + case GsmL1_PrimId_PhReadyToSendInd: + rc = handle_ph_readytosend_ind(fl1, &l1p->u.phReadyToSendInd, + msg); + break; + case GsmL1_PrimId_PhDataInd: + rc = handle_ph_data_ind(fl1, &l1p->u.phDataInd, msg); + break; + case GsmL1_PrimId_PhRaInd: + rc = handle_ph_ra_ind(fl1, &l1p->u.phRaInd, msg); + break; + default: + msgb_free(msg); + } + + return rc; +} + +static inline int is_prim_compat(GsmL1_Prim_t *l1p, struct wait_l1_conf *wlc) +{ + if (wlc->is_sys_prim != 0) + return 0; + if (l1p->id != wlc->conf_prim_id) + return 0; + if (l1p_get_hLayer3(l1p) != wlc->conf_hLayer3) + return 0; + return 1; +} + +int l1if_handle_l1prim(int wq, struct oc2gl1_hdl *fl1h, struct msgb *msg) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(msg); + struct wait_l1_conf *wlc; + int rc; + + switch (l1p->id) { + case GsmL1_PrimId_MphTimeInd: + /* silent, don't clog the log file */ + break; + default: + LOGP(DL1P, LOGL_DEBUG, "Rx L1 prim %s on queue %d\n", + get_value_string(oc2gbts_l1prim_names, l1p->id), wq); + } + + /* check if this is a resposne to a sync-waiting request */ + llist_for_each_entry(wlc, &fl1h->wlc_list, list) { + if (is_prim_compat(l1p, wlc)) { + llist_del(&wlc->list); + if (wlc->cb) { + /* call-back function must take + * ownership of msgb */ + rc = wlc->cb(oc2gl1_hdl_trx(fl1h), msg, + wlc->cb_data); + } else { + rc = 0; + msgb_free(msg); + } + release_wlc(wlc); + return rc; + } + } + + /* if we reach here, it is not a Conf for a pending Req */ + return l1if_handle_ind(fl1h, msg); +} + +int l1if_handle_sysprim(struct oc2gl1_hdl *fl1h, struct msgb *msg) +{ + Oc2g_Prim_t *sysp = msgb_sysprim(msg); + struct wait_l1_conf *wlc; + int rc; + + LOGP(DL1P, LOGL_DEBUG, "Rx SYS prim %s\n", + get_value_string(oc2gbts_sysprim_names, sysp->id)); + + /* check if this is a resposne to a sync-waiting request */ + llist_for_each_entry(wlc, &fl1h->wlc_list, list) { + /* the limitation here is that we cannot have multiple callers + * sending the same primitive */ + if (wlc->is_sys_prim && sysp->id == wlc->conf_prim_id) { + llist_del(&wlc->list); + if (wlc->cb) { + /* call-back function must take + * ownership of msgb */ + rc = wlc->cb(oc2gl1_hdl_trx(fl1h), msg, + wlc->cb_data); + } else { + rc = 0; + msgb_free(msg); + } + release_wlc(wlc); + return rc; + } + } + /* if we reach here, it is not a Conf for a pending Req */ + return l1if_handle_ind(fl1h, msg); +} + +static int activate_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + Oc2g_Prim_t *sysp = msgb_sysprim(resp); + GsmL1_Status_t status; + int on = 0; + unsigned int i; + struct gsm_bts *bts = trx->bts; + + if (sysp->id == Oc2g_PrimId_ActivateRfCnf) + on = 1; + + if (on) + status = sysp->u.activateRfCnf.status; + else + status = sysp->u.deactivateRfCnf.status; + + LOGP(DL1C, LOGL_INFO, "Rx RF-%sACT.conf (status=%s)\n", on ? "" : "DE", + get_value_string(oc2gbts_l1status_names, status)); + + + if (on) { + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_FATAL, "RF-ACT.conf with status %s\n", + get_value_string(oc2gbts_l1status_names, status)); + bts_shutdown(trx->bts, "RF-ACT failure"); + } else + bts_update_status(BTS_STATUS_RF_ACTIVE, 1); + + /* signal availability */ + oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OK); + oml_mo_tx_sw_act_rep(&trx->mo); + oml_mo_state_chg(&trx->bb_transc.mo, -1, NM_AVSTATE_OK); + oml_mo_tx_sw_act_rep(&trx->bb_transc.mo); + + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) + oml_mo_state_chg(&trx->ts[i].mo, NM_OPSTATE_DISABLED, NM_AVSTATE_DEPENDENCY); + } else { + bts_update_status(BTS_STATUS_RF_ACTIVE, 0); + oml_mo_state_chg(&trx->mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE); + oml_mo_state_chg(&trx->bb_transc.mo, NM_OPSTATE_DISABLED, NM_AVSTATE_OFF_LINE); + } + + msgb_free(resp); + + return 0; +} + +/* activate or de-activate the entire RF-Frontend */ +int l1if_activate_rf(struct oc2gl1_hdl *hdl, int on) +{ + struct msgb *msg = sysp_msgb_alloc(); + Oc2g_Prim_t *sysp = msgb_sysprim(msg); + struct phy_instance *pinst = hdl->phy_inst; + + if (on) { + sysp->id = Oc2g_PrimId_ActivateRfReq; + sysp->u.activateRfReq.msgq.u8UseTchMsgq = 0; + sysp->u.activateRfReq.msgq.u8UsePdtchMsgq = pcu_direct; + + sysp->u.activateRfReq.u8UnusedTsMode = pinst->u.oc2g.pedestal_mode; + + /* maximum cell size in quarter-bits, 90 == 12.456 km */ + sysp->u.activateRfReq.u8MaxCellSize = pinst->u.oc2g.max_cell_size; + + /* auto tx power adjustment mode 0:none, 1: automatic*/ + sysp->u.activateRfReq.u8EnAutoPowerAdjust = pinst->u.oc2g.tx_pwr_adj_mode; + + } else { + sysp->id = Oc2g_PrimId_DeactivateRfReq; + } + + return l1if_req_compl(hdl, msg, activate_rf_compl_cb, NULL); +} + +static void mute_handle_ts(struct gsm_bts_trx_ts *ts, int is_muted) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ts->lchan); i++) { + struct gsm_lchan *lchan = &ts->lchan[i]; + + if (!is_muted) + continue; + + if (lchan->state != LCHAN_S_ACTIVE) + continue; + + /* skip channels that might be active for another reason */ + if (lchan->type == GSM_LCHAN_CCCH) + continue; + if (lchan->type == GSM_LCHAN_PDTCH) + continue; + + if (lchan->s <= 0) + continue; + + lchan->s = 0; + rsl_tx_conn_fail(lchan, RSL_ERR_RADIO_LINK_FAIL); + } +} + +static int mute_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx); + Oc2g_Prim_t *sysp = msgb_sysprim(resp); + GsmL1_Status_t status; + + status = sysp->u.muteRfCnf.status; + + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_ERROR, "Rx RF-MUTE.conf with status %s\n", + get_value_string(oc2gbts_l1status_names, status)); + oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 0); + } else { + int i; + + LOGP(DL1C, LOGL_INFO, "Rx RF-MUTE.conf with status=%s\n", + get_value_string(oc2gbts_l1status_names, status)); + bts_update_status(BTS_STATUS_RF_MUTE, fl1h->last_rf_mute[0]); + oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 1); + + osmo_static_assert( + ARRAY_SIZE(trx->ts) >= ARRAY_SIZE(fl1h->last_rf_mute), + ts_array_size); + + for (i = 0; i < ARRAY_SIZE(fl1h->last_rf_mute); ++i) + mute_handle_ts(&trx->ts[i], fl1h->last_rf_mute[i]); + } + + msgb_free(resp); + + return 0; +} + +/* mute/unmute RF time slots */ +int l1if_mute_rf(struct oc2gl1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb) +{ + struct msgb *msg = sysp_msgb_alloc(); + Oc2g_Prim_t *sysp = msgb_sysprim(msg); + + LOGP(DL1C, LOGL_INFO, "Tx RF-MUTE.req (%d, %d, %d, %d, %d, %d, %d, %d)\n", + mute[0], mute[1], mute[2], mute[3], + mute[4], mute[5], mute[6], mute[7] + ); + + sysp->id = Oc2g_PrimId_MuteRfReq; + memcpy(sysp->u.muteRfReq.u8Mute, mute, sizeof(sysp->u.muteRfReq.u8Mute)); + /* save for later use */ + memcpy(hdl->last_rf_mute, mute, sizeof(hdl->last_rf_mute)); + + return l1if_req_compl(hdl, msg, cb ? cb : mute_rf_compl_cb, NULL); +} + +/* call-back on arrival of DSP+FPGA version + band capability */ +static int info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + Oc2g_Prim_t *sysp = msgb_sysprim(resp); + Oc2g_SystemInfoCnf_t *sic = &sysp->u.systemInfoCnf; + struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx); + int rc; + + fl1h->hw_info.dsp_version[0] = sic->dspVersion.major; + fl1h->hw_info.dsp_version[1] = sic->dspVersion.minor; + fl1h->hw_info.dsp_version[2] = sic->dspVersion.build; + + fl1h->hw_info.fpga_version[0] = sic->fpgaVersion.major; + fl1h->hw_info.fpga_version[1] = sic->fpgaVersion.minor; + fl1h->hw_info.fpga_version[2] = sic->fpgaVersion.build; + + LOGP(DL1C, LOGL_INFO, "DSP v%u.%u.%u, FPGA v%u.%u.%u\n", + sic->dspVersion.major, sic->dspVersion.minor, + sic->dspVersion.build, sic->fpgaVersion.major, + sic->fpgaVersion.minor, sic->fpgaVersion.build); + + LOGP(DL1C, LOGL_INFO, "Band support %s", gsm_band_name(fl1h->hw_info.band_support)); + + if (!(fl1h->hw_info.band_support & trx->bts->band)) + LOGP(DL1C, LOGL_FATAL, "BTS band %s not supported by hw\n", + gsm_band_name(trx->bts->band)); + + /* Request the activation */ + l1if_activate_rf(fl1h, 1); + + /* load calibration tables */ + rc = calib_load(fl1h); + if (rc < 0) + LOGP(DL1C, LOGL_ERROR, "Operating without calibration; " + "unable to load tables!\n"); + + msgb_free(resp); + return 0; +} + +/* request DSP+FPGA code versions */ +static int l1if_get_info(struct oc2gl1_hdl *hdl) +{ + struct msgb *msg = sysp_msgb_alloc(); + Oc2g_Prim_t *sysp = msgb_sysprim(msg); + + sysp->id = Oc2g_PrimId_SystemInfoReq; + + return l1if_req_compl(hdl, msg, info_compl_cb, NULL); +} + +static int reset_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx); + Oc2g_Prim_t *sysp = msgb_sysprim(resp); + GsmL1_Status_t status = sysp->u.layer1ResetCnf.status; + + LOGP(DL1C, LOGL_NOTICE, "Rx L1-RESET.conf (status=%s)\n", + get_value_string(oc2gbts_l1status_names, status)); + + msgb_free(resp); + + /* If we're coming out of reset .. */ + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_FATAL, "L1-RESET.conf with status %s\n", + get_value_string(oc2gbts_l1status_names, status)); + bts_shutdown(trx->bts, "L1-RESET failure"); + } + + /* as we cannot get the current DSP trace flags, we simply + * set them to zero (or whatever dsp_trace_f has been initialized to */ + l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f); + + /* obtain version information on DSP/FPGA and band capabilities */ + l1if_get_info(fl1h); + + return 0; +} + +int l1if_reset(struct oc2gl1_hdl *hdl) +{ + struct msgb *msg = sysp_msgb_alloc(); + Oc2g_Prim_t *sysp = msgb_sysprim(msg); + sysp->id = Oc2g_PrimId_Layer1ResetReq; + + return l1if_req_compl(hdl, msg, reset_compl_cb, NULL); +} + +/* set the trace flags within the DSP */ +int l1if_set_trace_flags(struct oc2gl1_hdl *hdl, uint32_t flags) +{ + struct msgb *msg = sysp_msgb_alloc(); + Oc2g_Prim_t *sysp = msgb_sysprim(msg); + + LOGP(DL1C, LOGL_INFO, "Tx SET-TRACE-FLAGS.req (0x%08x)\n", + flags); + + sysp->id = Oc2g_PrimId_SetTraceFlagsReq; + sysp->u.setTraceFlagsReq.u32Tf = flags; + + hdl->dsp_trace_f = flags; + + /* There is no confirmation we could wait for */ + if (osmo_wqueue_enqueue(&hdl->write_q[MQ_SYS_WRITE], msg) != 0) { + LOGP(DL1C, LOGL_ERROR, "MQ_SYS_WRITE queue full. Dropping msg\n"); + msgb_free(msg); + return -EAGAIN; + } + return 0; +} + +static int get_hwinfo(struct oc2gl1_hdl *fl1h) +{ + int rc = 0; + char rev_maj = 0, rev_min = 0; + + oc2gbts_rev_get(&rev_maj, &rev_min); + if (rc < 0) + return rc; + fl1h->hw_info.ver_major = (uint8_t)rev_maj; + fl1h->hw_info.ver_minor = (uint8_t)rev_min; + + rc = oc2gbts_model_get(); + if (rc < 0) + return rc; + fl1h->hw_info.options = (uint32_t)rc; + + rc = oc2gbts_option_get(OC2GBTS_OPTION_BAND); + if (rc < 0) + return rc; + + switch (rc) { + case OC2GBTS_BAND_850: + fl1h->hw_info.band_support = GSM_BAND_850; + break; + case OC2GBTS_BAND_900: + fl1h->hw_info.band_support = GSM_BAND_900; + break; + case OC2GBTS_BAND_1800: + fl1h->hw_info.band_support = GSM_BAND_1800; + break; + case OC2GBTS_BAND_1900: + fl1h->hw_info.band_support = GSM_BAND_1900; + break; + default: + return -1; + } + return 0; +} + +struct oc2gl1_hdl *l1if_open(struct phy_instance *pinst) +{ + struct oc2gl1_hdl *fl1h; + int rc; + + LOGP(DL1C, LOGL_INFO, "OC-2G BTS L1IF compiled against API headers " + "v%u.%u.%u\n", OC2G_API_VERSION >> 16, + (OC2G_API_VERSION >> 8) & 0xff, + OC2G_API_VERSION & 0xff); + + fl1h = talloc_zero(pinst, struct oc2gl1_hdl); + if (!fl1h) + return NULL; + INIT_LLIST_HEAD(&fl1h->wlc_list); + + fl1h->phy_inst = pinst; + fl1h->dsp_trace_f = pinst->u.oc2g.dsp_trace_f; + + get_hwinfo(fl1h); + + rc = l1if_transport_open(MQ_SYS_WRITE, fl1h); + if (rc < 0) { + talloc_free(fl1h); + return NULL; + } + + rc = l1if_transport_open(MQ_L1_WRITE, fl1h); + if (rc < 0) { + l1if_transport_close(MQ_SYS_WRITE, fl1h); + talloc_free(fl1h); + return NULL; + } + + return fl1h; +} + +int l1if_close(struct oc2gl1_hdl *fl1h) +{ + l1if_transport_close(MQ_L1_WRITE, fl1h); + l1if_transport_close(MQ_SYS_WRITE, fl1h); + return 0; +} + +/* TODO(oramadan) MERGE */ +#ifdef MERGE_ME +static void dsp_alive_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) +{ + Oc2g_Prim_t *sysp = msgb_sysprim(resp); + Oc2g_IsAliveCnf_t *sac = &sysp->u.isAliveCnf; + struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx); + + fl1h->hw_alive.dsp_alive_cnt++; + LOGP(DL1C, LOGL_DEBUG, "Rx SYS prim %s, status=%d (%d)\n", + get_value_string(oc2gbts_sysprim_names, sysp->id), sac->status, trx->nr); + + msgb_free(resp); +} + +static int dsp_alive_timer_cb(void *data) +{ + struct oc2gl1_hdl *fl1h = data; + struct gsm_bts_trx *trx = fl1h->phy_inst->trx; + struct msgb *msg = sysp_msgb_alloc(); + int rc; + struct oml_alarm_list *alarm_sent; + + Oc2g_Prim_t *sys_prim = msgb_sysprim(msg); + sys_prim->id = Oc2g_PrimId_IsAliveReq; + + if (fl1h->hw_alive.dsp_alive_cnt == 0) { + /* check for the alarm has already sent or not */ + llist_for_each_entry(alarm_sent, &fl1h->alarm_list, list) { + llist_del(&alarm_sent->list); + if (alarm_sent->alarm_signal != S_NM_OML_BTS_DSP_ALIVE_ALARM) + continue; + + LOGP(DL1C, LOGL_ERROR, "Alarm %d has removed from sent alarm list (%d)\n", alarm_sent->alarm_signal, trx->nr); + exit(23); + } + + LOGP(DL1C, LOGL_ERROR, "Timeout waiting for SYS prim %s primitive (%d)\n", + get_value_string(oc2gbts_sysprim_names, sys_prim->id + 1), trx->nr); + + if( fl1h->phy_inst->trx ){ + fl1h->phy_inst->trx->mo.obj_inst.trx_nr = fl1h->phy_inst->trx->nr; + + alarm_sig_data.mo = &fl1h->phy_inst->trx->mo; + memcpy(alarm_sig_data.spare, &sys_prim->id, sizeof(unsigned int)); + osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_DSP_ALIVE_ALARM, &alarm_sig_data); + if (!alarm_sig_data.rc) { + /* allocate new list of sent alarms */ + alarm_sent = talloc_zero(fl1h, struct oml_alarm_list); + if (!alarm_sent) + return -EIO; + + alarm_sent->alarm_signal = S_NM_OML_BTS_DSP_ALIVE_ALARM; + /* add alarm to sent list */ + llist_add(&alarm_sent->list, &fl1h->alarm_list); + LOGP(DL1C, LOGL_ERROR, "Alarm %d has added to sent alarm list (%d)\n", alarm_sent->alarm_signal, trx->nr); + } + } + } + + LOGP(DL1C, LOGL_DEBUG, "Tx SYS prim %s (%d)\n", + get_value_string(oc2gbts_sysprim_names, sys_prim->id), trx->nr); + + rc = l1if_req_compl(fl1h, msg, dsp_alive_compl_cb, NULL); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "Failed to send %s primitive\n", get_value_string(oc2gbts_sysprim_names, sys_prim->id)); + return -EIO; + } + + /* restart timer */ + fl1h->hw_alive.dsp_alive_cnt = 0; + osmo_timer_schedule(&fl1h->hw_alive.dsp_alive_timer, fl1h->hw_alive.dsp_alive_period, 0); + + return 0; +} +#endif + +int bts_model_phy_link_open(struct phy_link *plink) +{ + struct phy_instance *pinst = phy_instance_by_num(plink, 0); + + OSMO_ASSERT(pinst); + + if (!pinst->trx) { + LOGP(DL1C, LOGL_NOTICE, "Ignoring phy link %d instance %d " + "because no TRX is associated with it\n", plink->num, pinst->num); + return 0; + } + phy_link_state_set(plink, PHY_LINK_CONNECTING); + + pinst->u.oc2g.hdl = l1if_open(pinst); + if (!pinst->u.oc2g.hdl) { + LOGP(DL1C, LOGL_FATAL, "Cannot open L1 interface\n"); + return -EIO; + } + + /* Set default PHY parameters */ + if (!pinst->u.oc2g.max_cell_size) + pinst->u.oc2g.max_cell_size = OC2G_BTS_MAX_CELL_SIZE_DEFAULT; + + if (!pinst->u.oc2g.pedestal_mode) + pinst->u.oc2g.pedestal_mode = OC2G_BTS_PEDESTAL_MODE_DEFAULT; + + if (!pinst->u.oc2g.dsp_alive_period) + pinst->u.oc2g.dsp_alive_period = OC2G_BTS_DSP_ALIVE_TMR_DEFAULT; + + if (!pinst->u.oc2g.tx_pwr_adj_mode) + pinst->u.oc2g.tx_pwr_adj_mode = OC2G_BTS_TX_PWR_ADJ_DEFAULT; + + if (!pinst->u.oc2g.tx_pwr_red_8psk) + pinst->u.oc2g.tx_pwr_red_8psk = OC2G_BTS_TX_RED_PWR_8PSK_DEFAULT; + + if (!pinst->u.oc2g.tx_c0_idle_pwr_red) + pinst->u.oc2g.tx_c0_idle_pwr_red = OC2G_BTS_TX_C0_IDLE_RED_PWR_DEFAULT; + + struct oc2gl1_hdl *fl1h = pinst->u.oc2g.hdl; + fl1h->dsp_trace_f = dsp_trace; + + l1if_reset(pinst->u.oc2g.hdl); + + phy_link_state_set(plink, PHY_LINK_CONNECTED); + + /* TODO (oramadan) MERGE) + / * Send first IS_ALIVE primitive * / + struct msgb *msg = sysp_msgb_alloc(); + int rc; + + Oc2g_Prim_t *sys_prim = msgb_sysprim(msg); + sys_prim->id = Oc2g_PrimId_IsAliveReq; + + rc = l1if_req_compl(fl1h, msg, dsp_alive_compl_cb, NULL); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "Failed to send %s primitive\n", get_value_string(oc2gbts_sysprim_names, sys_prim->id)); + return -EIO; + } + + /* initialize DSP heart beat alive timer * / + fl1h->hw_alive.dsp_alive_timer.cb = dsp_alive_timer_cb; + fl1h->hw_alive.dsp_alive_timer.data = fl1h; + fl1h->hw_alive.dsp_alive_cnt = 0; + fl1h->hw_alive.dsp_alive_period = pinst->u.oc2g.dsp_alive_period; + osmo_timer_schedule(&fl1h->hw_alive.dsp_alive_timer, fl1h->hw_alive.dsp_alive_period, 0); */ + return 0; +} diff --git a/src/osmo-bts-oc2g/l1_if.h b/src/osmo-bts-oc2g/l1_if.h new file mode 100644 index 000000000..38699e011 --- /dev/null +++ b/src/osmo-bts-oc2g/l1_if.h @@ -0,0 +1,145 @@ +#ifndef _L1_IF_H +#define _L1_IF_H + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +enum { + MQ_SYS_READ, + MQ_L1_READ, + MQ_TCH_READ, + MQ_PDTCH_READ, + _NUM_MQ_READ +}; + +enum { + MQ_SYS_WRITE, + MQ_L1_WRITE, + MQ_TCH_WRITE, + MQ_PDTCH_WRITE, + _NUM_MQ_WRITE +}; + +struct calib_send_state { + FILE *fp; + const char *path; + int last_file_idx; +}; + +struct oc2gl1_hdl { + struct gsm_time gsm_time; + HANDLE hLayer1; /* handle to the L1 instance in the DSP */ + uint32_t dsp_trace_f; /* currently operational DSP trace flags */ + struct llist_head wlc_list; + struct llist_head alarm_list; /* list of sent alarms */ + + struct phy_instance *phy_inst; + + struct osmo_timer_list alive_timer; + unsigned int alive_prim_cnt; + + struct osmo_fd read_ofd[_NUM_MQ_READ]; /* osmo file descriptors */ + struct osmo_wqueue write_q[_NUM_MQ_WRITE]; + + struct { + /* from DSP/FPGA after L1 Init */ + uint8_t dsp_version[3]; + uint8_t fpga_version[3]; + uint32_t band_support; + uint8_t ver_major; + uint8_t ver_minor; + uint32_t options; + } hw_info; + + struct calib_send_state st; + + uint8_t last_rf_mute[8]; + + struct { + struct osmo_timer_list dsp_alive_timer; + unsigned int dsp_alive_cnt; + uint8_t dsp_alive_period; + } hw_alive; +}; + +#define msgb_l1prim(msg) ((GsmL1_Prim_t *)(msg)->l1h) +#define msgb_sysprim(msg) ((Oc2g_Prim_t *)(msg)->l1h) + +typedef int l1if_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, void *data); + +/* send a request primitive to the L1 and schedule completion call-back */ +int l1if_req_compl(struct oc2gl1_hdl *fl1h, struct msgb *msg, + l1if_compl_cb *cb, void *cb_data); +int l1if_gsm_req_compl(struct oc2gl1_hdl *fl1h, struct msgb *msg, + l1if_compl_cb *cb, void *cb_data); + +struct oc2gl1_hdl *l1if_open(struct phy_instance *pinst); +int l1if_close(struct oc2gl1_hdl *hdl); +int l1if_reset(struct oc2gl1_hdl *hdl); +int l1if_activate_rf(struct oc2gl1_hdl *hdl, int on); +int l1if_set_trace_flags(struct oc2gl1_hdl *hdl, uint32_t flags); +int l1if_set_txpower(struct oc2gl1_hdl *fl1h, float tx_power); +int l1if_set_txpower_backoff_8psk(struct oc2gl1_hdl *fl1h, uint8_t backoff); +int l1if_set_txpower_c0_idle_pwr_red(struct oc2gl1_hdl *fl1h, uint8_t red); +int l1if_set_max_cell_size(struct oc2gl1_hdl *fl1h, uint8_t cell_size); +int l1if_mute_rf(struct oc2gl1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb); + +struct msgb *l1p_msgb_alloc(void); +struct msgb *sysp_msgb_alloc(void); + +uint32_t l1if_lchan_to_hLayer(struct gsm_lchan *lchan); +struct gsm_lchan *l1if_hLayer_to_lchan(struct gsm_bts_trx *trx, uint32_t hLayer); + +/* tch.c */ +int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len, + const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn, + bool use_cache, bool marker); +int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg); +int l1if_tch_fill(struct gsm_lchan *lchan, uint8_t *l1_buffer); +struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan, uint32_t fn); + +/* ciphering */ +int l1if_set_ciphering(struct oc2gl1_hdl *fl1h, + struct gsm_lchan *lchan, + int dir_downlink); + +/* channel control */ +int l1if_rsl_chan_act(struct gsm_lchan *lchan); +int l1if_rsl_chan_rel(struct gsm_lchan *lchan); +int l1if_rsl_chan_mod(struct gsm_lchan *lchan); +int l1if_rsl_deact_sacch(struct gsm_lchan *lchan); +int l1if_rsl_mode_modify(struct gsm_lchan *lchan); + +/* calibration loading */ +int calib_load(struct oc2gl1_hdl *fl1h); + +/* public helpers for test */ +int bts_check_for_ciph_cmd(struct oc2gl1_hdl *fl1h, + struct msgb *msg, struct gsm_lchan *lchan); +int l1if_ms_pwr_ctrl(struct gsm_lchan *lchan, const int uplink_target, + const uint8_t ms_power, const float rxLevel); + +static inline struct oc2gl1_hdl *trx_oc2gl1_hdl(struct gsm_bts_trx *trx) +{ + struct phy_instance *pinst = trx_phy_instance(trx); + OSMO_ASSERT(pinst); + return pinst->u.oc2g.hdl; +} + +static inline struct gsm_bts_trx *oc2gl1_hdl_trx(struct oc2gl1_hdl *fl1h) +{ + OSMO_ASSERT(fl1h->phy_inst); + return fl1h->phy_inst->trx; +} + +#endif /* _L1_IF_H */ diff --git a/src/osmo-bts-oc2g/l1_transp.h b/src/osmo-bts-oc2g/l1_transp.h new file mode 100644 index 000000000..5af79dcb9 --- /dev/null +++ b/src/osmo-bts-oc2g/l1_transp.h @@ -0,0 +1,14 @@ +#ifndef _L1_TRANSP_H +#define _L1_TRANSP_H + +#include + +/* functions a transport calls on arrival of primitive from BTS */ +int l1if_handle_l1prim(int wq, struct oc2gl1_hdl *fl1h, struct msgb *msg); +int l1if_handle_sysprim(struct oc2gl1_hdl *fl1h, struct msgb *msg); + +/* functions exported by a transport */ +int l1if_transport_open(int q, struct oc2gl1_hdl *fl1h); +int l1if_transport_close(int q, struct oc2gl1_hdl *fl1h); + +#endif /* _L1_TRANSP_H */ diff --git a/src/osmo-bts-oc2g/l1_transp_hw.c b/src/osmo-bts-oc2g/l1_transp_hw.c new file mode 100644 index 000000000..e1d46581e --- /dev/null +++ b/src/osmo-bts-oc2g/l1_transp_hw.c @@ -0,0 +1,326 @@ +/* Interface handler for Nuran Wireless OC-2G L1 (real hardware) */ + +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * (C) 2011 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "oc2gbts.h" +#include "l1_if.h" +#include "l1_transp.h" + + +#define DEV_SYS_DSP2ARM_NAME "/dev/msgq/oc2g_dsp2arm_trx" +#define DEV_SYS_ARM2DSP_NAME "/dev/msgq/oc2g_arm2dsp_trx" +#define DEV_L1_DSP2ARM_NAME "/dev/msgq/gsml1_sig_dsp2arm_trx" +#define DEV_L1_ARM2DSP_NAME "/dev/msgq/gsml1_sig_arm2dsp_trx" + +#define DEV_TCH_DSP2ARM_NAME "/dev/msgq/gsml1_tch_dsp2arm_trx" +#define DEV_TCH_ARM2DSP_NAME "/dev/msgq/gsml1_tch_arm2dsp_trx" +#define DEV_PDTCH_DSP2ARM_NAME "/dev/msgq/gsml1_pdtch_dsp2arm_trx" +#define DEV_PDTCH_ARM2DSP_NAME "/dev/msgq/gsml1_pdtch_arm2dsp_trx" + +static const char *rd_devnames[] = { + [MQ_SYS_READ] = DEV_SYS_DSP2ARM_NAME, + [MQ_L1_READ] = DEV_L1_DSP2ARM_NAME, + [MQ_TCH_READ] = DEV_TCH_DSP2ARM_NAME, + [MQ_PDTCH_READ] = DEV_PDTCH_DSP2ARM_NAME, +}; + +static const char *wr_devnames[] = { + [MQ_SYS_WRITE] = DEV_SYS_ARM2DSP_NAME, + [MQ_L1_WRITE] = DEV_L1_ARM2DSP_NAME, + [MQ_TCH_WRITE] = DEV_TCH_ARM2DSP_NAME, + [MQ_PDTCH_WRITE]= DEV_PDTCH_ARM2DSP_NAME, +}; + +/* + * Make sure that all structs we read fit into the OC2GBTS_PRIM_SIZE + */ +osmo_static_assert(sizeof(GsmL1_Prim_t) + 128 <= OC2GBTS_PRIM_SIZE, l1_prim) +osmo_static_assert(sizeof(Oc2g_Prim_t) + 128 <= OC2GBTS_PRIM_SIZE, super_prim) + +static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what) +{ + struct osmo_wqueue *queue; + + queue = container_of(fd, struct osmo_wqueue, bfd); + + if (what & BSC_FD_READ) + queue->read_cb(fd); + + if (what & BSC_FD_EXCEPT) + queue->except_cb(fd); + + if (what & BSC_FD_WRITE) { + struct iovec iov[5]; + struct msgb *msg, *tmp; + int written, count = 0; + + fd->when &= ~BSC_FD_WRITE; + + llist_for_each_entry(msg, &queue->msg_queue, list) { + /* more writes than we have */ + if (count >= ARRAY_SIZE(iov)) + break; + + iov[count].iov_base = msg->l1h; + iov[count].iov_len = msgb_l1len(msg); + count += 1; + } + + /* TODO: check if all lengths are the same. */ + + + /* Nothing scheduled? This should not happen. */ + if (count == 0) { + if (!llist_empty(&queue->msg_queue)) + fd->when |= BSC_FD_WRITE; + return 0; + } + + written = writev(fd->fd, iov, count); + if (written < 0) { + /* nothing written?! */ + if (!llist_empty(&queue->msg_queue)) + fd->when |= BSC_FD_WRITE; + return 0; + } + + /* now delete the written entries */ + written = written / iov[0].iov_len; + count = 0; + llist_for_each_entry_safe(msg, tmp, &queue->msg_queue, list) { + queue->current_length -= 1; + + llist_del(&msg->list); + msgb_free(msg); + + count += 1; + if (count >= written) + break; + } + + if (!llist_empty(&queue->msg_queue)) + fd->when |= BSC_FD_WRITE; + } + + return 0; +} + +static int prim_size_for_queue(int queue) +{ + switch (queue) { + case MQ_SYS_WRITE: + return sizeof(Oc2g_Prim_t); + case MQ_L1_WRITE: + case MQ_TCH_WRITE: + case MQ_PDTCH_WRITE: + return sizeof(GsmL1_Prim_t); + default: + /* The compiler can't know that priv_nr is an enum. Assist. */ + LOGP(DL1C, LOGL_FATAL, "writing on a wrong queue: %d\n", + queue); + assert(false); + break; + } +} + +/* callback when there's something to read from the l1 msg_queue */ +static int read_dispatch_one(struct oc2gl1_hdl *fl1h, struct msgb *msg, int queue) +{ + switch (queue) { + case MQ_SYS_WRITE: + return l1if_handle_sysprim(fl1h, msg); + case MQ_L1_WRITE: + case MQ_TCH_WRITE: + case MQ_PDTCH_WRITE: + return l1if_handle_l1prim(queue, fl1h, msg); + default: + /* The compiler can't know that priv_nr is an enum. Assist. */ + LOGP(DL1C, LOGL_FATAL, "writing on a wrong queue: %d\n", + queue); + assert(false); + break; + } +}; + +static int l1if_fd_cb(struct osmo_fd *ofd, unsigned int what) +{ + int i, rc; + + const uint32_t prim_size = prim_size_for_queue(ofd->priv_nr); + uint32_t count; + + struct iovec iov[3]; + struct msgb *msg[ARRAY_SIZE(iov)]; + + for (i = 0; i < ARRAY_SIZE(iov); ++i) { + msg[i] = msgb_alloc_headroom(prim_size + 128, 128, "1l_fd"); + msg[i]->l1h = msg[i]->data; + + iov[i].iov_base = msg[i]->l1h; + iov[i].iov_len = msgb_tailroom(msg[i]); + } + + rc = readv(ofd->fd, iov, ARRAY_SIZE(iov)); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "failed to read from fd: %s\n", strerror(errno)); + /* N. B: we do not abort to let the cycle below cleanup allocated memory properly, + the return value is ignored by the caller anyway. + TODO: use libexplain's explain_readv() to provide detailed error description */ + count = 0; + } else + count = rc / prim_size; + + for (i = 0; i < count; ++i) { + msgb_put(msg[i], prim_size); + read_dispatch_one(ofd->data, msg[i], ofd->priv_nr); + } + + for (i = count; i < ARRAY_SIZE(iov); ++i) + msgb_free(msg[i]); + + return 1; +} + +/* callback when we can write to one of the l1 msg_queue devices */ +static int l1fd_write_cb(struct osmo_fd *ofd, struct msgb *msg) +{ + int rc; + + rc = write(ofd->fd, msg->l1h, msgb_l1len(msg)); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "error writing to L1 msg_queue: %s\n", + strerror(errno)); + return rc; + } else if (rc < msg->len) { + LOGP(DL1C, LOGL_ERROR, "short write to L1 msg_queue: " + "%u < %u\n", rc, msg->len); + return -EIO; + } + + return 0; +} + +int l1if_transport_open(int q, struct oc2gl1_hdl *hdl) +{ + struct phy_link *plink = hdl->phy_inst->phy_link; + int rc; + char buf[PATH_MAX]; + + /* Step 1: Open all msg_queue file descriptors */ + struct osmo_fd *read_ofd = &hdl->read_ofd[q]; + struct osmo_wqueue *wq = &hdl->write_q[q]; + struct osmo_fd *write_ofd = &hdl->write_q[q].bfd; + + snprintf(buf, sizeof(buf)-1, "%s%d", rd_devnames[q], plink->num); + buf[sizeof(buf)-1] = '\0'; + + rc = open(buf, O_RDONLY); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "unable to open msg_queue %s: %s\n", + buf, strerror(errno)); + return rc; + } + read_ofd->fd = rc; + read_ofd->priv_nr = q; + read_ofd->data = hdl; + read_ofd->cb = l1if_fd_cb; + read_ofd->when = BSC_FD_READ; + rc = osmo_fd_register(read_ofd); + if (rc < 0) { + close(read_ofd->fd); + read_ofd->fd = -1; + return rc; + } + + snprintf(buf, sizeof(buf)-1, "%s%d", wr_devnames[q], plink->num); + buf[sizeof(buf)-1] = '\0'; + + rc = open(buf, O_WRONLY); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "unable to open msg_queue %s: %s\n", + buf, strerror(errno)); + goto out_read; + } + osmo_wqueue_init(wq, 10); + wq->write_cb = l1fd_write_cb; + write_ofd->cb = wqueue_vector_cb; + write_ofd->fd = rc; + write_ofd->priv_nr = q; + write_ofd->data = hdl; + write_ofd->when = BSC_FD_WRITE; + rc = osmo_fd_register(write_ofd); + if (rc < 0) { + close(write_ofd->fd); + write_ofd->fd = -1; + goto out_read; + } + + return 0; + +out_read: + close(hdl->read_ofd[q].fd); + osmo_fd_unregister(&hdl->read_ofd[q]); + + return rc; +} + +int l1if_transport_close(int q, struct oc2gl1_hdl *hdl) +{ + struct osmo_fd *read_ofd = &hdl->read_ofd[q]; + struct osmo_fd *write_ofd = &hdl->write_q[q].bfd; + + osmo_fd_unregister(read_ofd); + close(read_ofd->fd); + read_ofd->fd = -1; + + osmo_fd_unregister(write_ofd); + close(write_ofd->fd); + write_ofd->fd = -1; + + return 0; +} diff --git a/src/osmo-bts-oc2g/main.c b/src/osmo-bts-oc2g/main.c new file mode 100644 index 000000000..9777c0929 --- /dev/null +++ b/src/osmo-bts-oc2g/main.c @@ -0,0 +1,240 @@ +/* Main program for NuRAN Wireless OC-2G BTS */ + +/* Copyright (C) 2015 by Yves Godin + * Copyright (C) 2016 by Harald Welte + * + * Based on sysmoBTS: + * (C) 2011-2013 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +static int write_status_file(char *status_file, char *status_str) +{ + FILE *outf; + char tmp[PATH_MAX+1]; + + snprintf(tmp, sizeof(tmp)-1, "/var/run/osmo-bts/%s", status_file); + tmp[PATH_MAX-1] = '\0'; + + outf = fopen(tmp, "w"); + if (!outf) + return -1; + + fprintf(outf, "%s\n", status_str); + + fclose(outf); + + return 0; +} + +/*NTQD: Change how rx_nr is handle in multi-trx*/ +#define OC2GBTS_RF_LOCK_PATH "/var/lock/bts_rf_lock" + +#include "utils.h" +#include "l1_if.h" +#include "hw_misc.h" +#include "oml_router.h" +#include "misc/oc2gbts_bid.h" + +unsigned int dsp_trace = 0x00000000; + +int bts_model_init(struct gsm_bts *bts) +{ + struct gsm_bts_trx *trx; + struct stat st; + static struct osmo_fd accept_fd, read_fd; + int rc; + + bts->variant = BTS_OSMO_OC2G; + bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3); + /* specific default values for OC2G platform */ + + /* TODO(oramadan) MERGE + bts->oc2g.led_ctrl_mode = OC2G_BTS_LED_CTRL_MODE_DEFAULT; + /* RTP drift threshold default * / + bts->oc2g.rtp_drift_thres_ms = OC2G_BTS_RTP_DRIFT_THRES_DEFAULT; + */ + + rc = oml_router_init(bts, OML_ROUTER_PATH, &accept_fd, &read_fd); + if (rc < 0) { + fprintf(stderr, "Error creating the OML router: %s rc=%d\n", + OML_ROUTER_PATH, rc); + exit(1); + } + + llist_for_each_entry(trx, &bts->trx_list, list) { + trx->nominal_power = 40; + trx->power_params.trx_p_max_out_mdBm = to_mdB(bts->c0->nominal_power); + } + + if (stat(OC2GBTS_RF_LOCK_PATH, &st) == 0) { + LOGP(DL1C, LOGL_NOTICE, "Not starting BTS due to RF_LOCK file present\n"); + exit(23); + } + + gsm_bts_set_feature(bts, BTS_FEAT_GPRS); + gsm_bts_set_feature(bts, BTS_FEAT_EGPRS); + gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS); + gsm_bts_set_feature(bts, BTS_FEAT_AGCH_PCH_PROP); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_EFR); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_AMR); + gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_AMR); + + bts_model_vty_init(bts); + + return 0; +} + +void bts_model_phy_link_set_defaults(struct phy_link *plink) +{ +} + +void bts_model_phy_instance_set_defaults(struct phy_instance *pinst) +{ +} + +int bts_model_oml_estab(struct gsm_bts *bts) +{ + /* update status file */ + write_status_file("state", ""); + + return 0; +} + +void bts_update_status(enum bts_global_status which, int on) +{ + static uint64_t states = 0; + uint64_t old_states = states; + int led_rf_active_on; + + if (on) + states |= (1ULL << which); + else + states &= ~(1ULL << which); + + led_rf_active_on = + (states & (1ULL << BTS_STATUS_RF_ACTIVE)) && + !(states & (1ULL << BTS_STATUS_RF_MUTE)); + + LOGP(DL1C, LOGL_INFO, + "Set global status #%d to %d (%04llx -> %04llx), LEDs: ACT %d\n", + which, on, + (long long)old_states, (long long)states, + led_rf_active_on); + + oc2gbts_led_set(led_rf_active_on ? LED_GREEN : LED_OFF); +} + +void bts_model_print_help() +{ + printf( " -w --hw-version Print the targeted HW Version\n" + " -M --pcu-direct Force PCU to access message queue for PDCH dchannel directly\n" + " -p --dsp-trace Set DSP trace flags\n" + ); +} + +static void print_hwversion() +{ + printf(get_hwversion_desc()); + printf("\n"); +} + +int bts_model_handle_options(int argc, char **argv) +{ + int num_errors = 0; + + while (1) { + int option_idx = 0, c; + static const struct option long_options[] = { + { "dsp-trace", 1, 0, 'p' }, + { "hw-version", 0, 0, 'w' }, + { "pcu-direct", 0, 0, 'M' }, + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "p:wM", + long_options, &option_idx); + if (c == -1) + break; + + switch (c) { + case 'p': + dsp_trace = strtoul(optarg, NULL, 16); + break; + case 'M': + pcu_direct = 1; + break; + case 'w': + print_hwversion(); + exit(0); + break; + default: + num_errors++; + break; + } + } + + return num_errors; +} + +void bts_model_abis_close(struct gsm_bts *bts) +{ + /* write to status file */ + write_status_file("state", "ABIS DOWN"); + + /* for now, we simply terminate the program and re-spawn */ + bts_shutdown(bts, "Abis close"); +} + +int main(int argc, char **argv) +{ + /* create status file with initial state */ + write_status_file("state", "ABIS DOWN"); + + return bts_main(argc, argv); +} diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_bid.c b/src/osmo-bts-oc2g/misc/oc2gbts_bid.c new file mode 100644 index 000000000..6eaa9c698 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_bid.c @@ -0,0 +1,175 @@ +/* Copyright (C) 2015 by Yves Godin + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "oc2gbts_bid.h" + +#define BOARD_REV_MAJ_SYSFS "/var/oc2g/platform/rev/major" +#define BOARD_REV_MIN_SYSFS "/var/oc2g/platform/rev/minor" +#define BOARD_OPT_SYSFS "/var/oc2g/platform/option" + +static const int option_type_mask[_NUM_OPTION_TYPES] = { + [OC2GBTS_OPTION_OCXO] = 0x07, + [OC2GBTS_OPTION_FPGA] = 0x03, + [OC2GBTS_OPTION_PA] = 0x01, + [OC2GBTS_OPTION_BAND] = 0x03, + [OC2GBTS_OPTION_TX_ATT] = 0x01, + [OC2GBTS_OPTION_TX_FIL] = 0x01, + [OC2GBTS_OPTION_RX_ATT] = 0x01, + [OC2GBTS_OPTION_RMS_FWD] = 0x01, + [OC2GBTS_OPTION_RMS_REFL] = 0x01, + [OC2GBTS_OPTION_DDR_32B] = 0x01, + [OC2GBTS_OPTION_DDR_ECC] = 0x01, + [OC2GBTS_OPTION_PA_TEMP] = 0x01, +}; + +static const int option_type_shift[_NUM_OPTION_TYPES] = { + [OC2GBTS_OPTION_OCXO] = 0, + [OC2GBTS_OPTION_FPGA] = 3, + [OC2GBTS_OPTION_PA] = 5, + [OC2GBTS_OPTION_BAND] = 6, + [OC2GBTS_OPTION_TX_ATT] = 8, + [OC2GBTS_OPTION_TX_FIL] = 9, + [OC2GBTS_OPTION_RX_ATT] = 10, + [OC2GBTS_OPTION_RMS_FWD] = 11, + [OC2GBTS_OPTION_RMS_REFL] = 12, + [OC2GBTS_OPTION_DDR_32B] = 13, + [OC2GBTS_OPTION_DDR_ECC] = 14, + [OC2GBTS_OPTION_PA_TEMP] = 15, +}; + + +static char board_rev_maj = -1; +static char board_rev_min = -1; +static int board_option = -1; + +void oc2gbts_rev_get(char *rev_maj, char *rev_min) +{ + FILE *fp; + char rev; + + *rev_maj = 0; + *rev_min = 0; + + if (board_rev_maj != -1 && board_rev_min != -1) { + *rev_maj = board_rev_maj; + *rev_min = board_rev_min; + } + + fp = fopen(BOARD_REV_MAJ_SYSFS, "r"); + if (fp == NULL) return; + + if (fscanf(fp, "%c", &rev) != 1) { + fclose(fp); + return; + } + + fclose(fp); + + *rev_maj = board_rev_maj = rev; + + fp = fopen(BOARD_REV_MIN_SYSFS, "r"); + if (fp == NULL) return; + + if (fscanf(fp, "%c", &rev) != 1) { + fclose(fp); + return; + } + + fclose(fp); + + *rev_min = board_rev_min = rev; +} + +const char* get_hwversion_desc() +{ + int rev; + int model; + size_t len; + static char model_name[64] = {0, }; + len = snprintf(model_name, sizeof(model_name), "NuRAN OC-2G BTS"); + + char rev_maj = 0, rev_min = 0; + + int rc = 0; + oc2gbts_rev_get(&rev_maj, &rev_min); + if (rc < 0) + return rc; + if (rev >= 0) { + len += snprintf(model_name + len, sizeof(model_name) - len, + " Rev %d.%d", (uint8_t)rev_maj, (uint8_t)rev_min); + } + + model = oc2gbts_model_get(); + if (model >= 0) { + snprintf(model_name + len, sizeof(model_name) - len, + "%s (%05X)", model_name, model); + } + return model_name; +} + +int oc2gbts_model_get(void) +{ + FILE *fp; + int opt; + + + if (board_option == -1) { + fp = fopen(BOARD_OPT_SYSFS, "r"); + if (fp == NULL) { + return -1; + } + + if (fscanf(fp, "%X", &opt) != 1) { + fclose( fp ); + return -1; + } + fclose(fp); + + board_option = opt; + } + return board_option; +} + +int oc2gbts_option_get(enum oc2gbts_option_type type) +{ + int rc; + int option; + + if (type >= _NUM_OPTION_TYPES) { + return -EINVAL; + } + + if (board_option == -1) { + rc = oc2gbts_model_get(); + if (rc < 0) return rc; + } + + option = (board_option >> option_type_shift[type]) + & option_type_mask[type]; + + return option; +} diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_bid.h b/src/osmo-bts-oc2g/misc/oc2gbts_bid.h new file mode 100644 index 000000000..00cf38992 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_bid.h @@ -0,0 +1,47 @@ +#ifndef _OC2GBTS_BOARD_H +#define _OC2GBTS_BOARD_H + +#include + +enum oc2gbts_option_type { + OC2GBTS_OPTION_OCXO, + OC2GBTS_OPTION_FPGA, + OC2GBTS_OPTION_PA, + OC2GBTS_OPTION_BAND, + OC2GBTS_OPTION_TX_ATT, + OC2GBTS_OPTION_TX_FIL, + OC2GBTS_OPTION_RX_ATT, + OC2GBTS_OPTION_RMS_FWD, + OC2GBTS_OPTION_RMS_REFL, + OC2GBTS_OPTION_DDR_32B, + OC2GBTS_OPTION_DDR_ECC, + OC2GBTS_OPTION_PA_TEMP, + _NUM_OPTION_TYPES +}; + +enum oc2gbts_ocxo_type { + OC2GBTS_OCXO_BILAY_NVG45AV2072, + OC2GBTS_OCXO_TAITIEN_NJ26M003, + _NUM_OCXO_TYPES +}; + +enum oc2gbts_fpga_type { + OC2GBTS_FPGA_35T, + OC2GBTS_FPGA_50T, + OC2GBTS_FPGA_75T, + OC2GBTS_FPGA_100T, + _NUM_FPGA_TYPES +}; + +enum oc2gbts_gsm_band { + OC2GBTS_BAND_850, + OC2GBTS_BAND_900, + OC2GBTS_BAND_1800, + OC2GBTS_BAND_1900, +}; + +void oc2gbts_rev_get(char *rev_maj, char *rev_min); +int oc2gbts_model_get(void); +int oc2gbts_option_get(enum oc2gbts_option_type type); + +#endif diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_bts.c b/src/osmo-bts-oc2g/misc/oc2gbts_bts.c new file mode 100644 index 000000000..b3dae76e4 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_bts.c @@ -0,0 +1,129 @@ +/* Copyright (C) 2016 by NuRAN Wireless + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include "oc2gbts_mgr.h" +#include "oc2gbts_bts.h" + +static int check_eth_status(char *dev_name) +{ + int fd, rc; + struct ifreq ifr; + + fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); + if (fd < 0) + return fd; + + memset(&ifr, 0, sizeof(ifr)); + memcpy(&ifr.ifr_name, dev_name, sizeof(ifr.ifr_name)); + rc = ioctl(fd, SIOCGIFFLAGS, &ifr); + close(fd); + + if (rc < 0) + return rc; + + if ((ifr.ifr_flags & IFF_UP) && (ifr.ifr_flags & IFF_RUNNING)) + return 0; + + return 1; +} + +void check_bts_led_pattern(uint8_t *led) +{ + FILE *fp; + char str[64] = "\0"; + int rc; + + /* check for existing of BTS state file */ + if ((fp = fopen("/var/run/osmo-bts/state", "r")) == NULL) { + led[BLINK_PATTERN_INIT] = 1; + return; + } + + /* check Ethernet interface status */ + rc = check_eth_status("eth0"); + if (rc > 0) { + LOGP(DTEMP, LOGL_DEBUG,"External link is DOWN\n"); + led[BLINK_PATTERN_EXT_LINK_MALFUNC] = 1; + fclose(fp); + return; + } + + /* check for BTS is still alive */ + if (system("pidof osmo-bts-oc2g > /dev/null")) { + LOGP(DTEMP, LOGL_DEBUG,"BTS process has stopped\n"); + led[BLINK_PATTERN_INT_PROC_MALFUNC] = 1; + fclose(fp); + return; + } + + /* check for BTS state */ + while (fgets(str, 64, fp) != NULL) { + LOGP(DTEMP, LOGL_DEBUG,"BTS state is %s\n", (strstr(str, "ABIS DOWN") != NULL) ? "DOWN" : "UP"); + if (strstr(str, "ABIS DOWN") != NULL) + led[BLINK_PATTERN_INT_PROC_MALFUNC] = 1; + } + fclose(fp); + + return; +} + +int check_sensor_led_pattern( struct oc2gbts_mgr_instance *mgr, uint8_t *led) +{ + if(mgr->alarms.temp_high == 1) + led[BLINK_PATTERN_TEMP_HIGH] = 1; + + if(mgr->alarms.temp_max == 1) + led[BLINK_PATTERN_TEMP_MAX] = 1; + + if(mgr->alarms.supply_low == 1) + led[BLINK_PATTERN_SUPPLY_VOLT_LOW] = 1; + + if(mgr->alarms.supply_min == 1) + led[BLINK_PATTERN_SUPPLY_VOLT_MIN] = 1; + + if(mgr->alarms.vswr_high == 1) + led[BLINK_PATTERN_VSWR_HIGH] = 1; + + if(mgr->alarms.vswr_max == 1) + led[BLINK_PATTERN_VSWR_MAX] = 1; + + if(mgr->alarms.supply_pwr_high == 1) + led[BLINK_PATTERN_SUPPLY_PWR_HIGH] = 1; + + if(mgr->alarms.supply_pwr_max == 1) + led[BLINK_PATTERN_SUPPLY_PWR_MAX] = 1; + + if(mgr->alarms.pa_pwr_high == 1) + led[BLINK_PATTERN_PA_PWR_HIGH] = 1; + + if(mgr->alarms.pa_pwr_max == 1) + led[BLINK_PATTERN_PA_PWR_MAX] = 1; + + if(mgr->alarms.gps_fix_lost == 1) + led[BLINK_PATTERN_GPS_FIX_LOST] = 1; + + return 0; +} + diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_bts.h b/src/osmo-bts-oc2g/misc/oc2gbts_bts.h new file mode 100644 index 000000000..b003bdcd2 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_bts.h @@ -0,0 +1,21 @@ +#ifndef _OC2GBTS_BTS_H_ +#define _OC2GBTS_BTS_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* public function prototypes */ +void check_bts_led_pattern(uint8_t *led); +int check_sensor_led_pattern( struct oc2gbts_mgr_instance *mgr, uint8_t *led); + +#endif diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_clock.c b/src/osmo-bts-oc2g/misc/oc2gbts_clock.c new file mode 100644 index 000000000..acbbbc5c8 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_clock.c @@ -0,0 +1,263 @@ +/* Copyright (C) 2015 by Yves Godin + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "oc2gbts_clock.h" + +#define CLKERR_ERR_SYSFS "/var/oc2g/clkerr/clkerr1_average" +#define CLKERR_ACC_SYSFS "/var/oc2g/clkerr/clkerr1_average_accuracy" +#define CLKERR_INT_SYSFS "/var/oc2g/clkerr/clkerr1_average_interval" +#define CLKERR_FLT_SYSFS "/var/oc2g/clkerr/clkerr1_fault" +#define CLKERR_RFS_SYSFS "/var/oc2g/clkerr/refresh" +#define CLKERR_RST_SYSFS "/var/oc2g/clkerr/reset" + +#define OCXODAC_VAL_SYSFS "/var/oc2g/ocxo/voltage" +#define OCXODAC_ROM_SYSFS "/var/oc2g/ocxo/eeprom" + +/* clock error */ +static int clkerr_fd_err = -1; +static int clkerr_fd_accuracy = -1; +static int clkerr_fd_interval = -1; +static int clkerr_fd_fault = -1; +static int clkerr_fd_refresh = -1; +static int clkerr_fd_reset = -1; + +/* ocxo dac */ +static int ocxodac_fd_value = -1; +static int ocxodac_fd_save = -1; + + +static int sysfs_read_val(int fd, int *val) +{ + int rc; + char szVal[32] = {0}; + + lseek( fd, 0, SEEK_SET ); + + rc = read(fd, szVal, sizeof(szVal) - 1); + if (rc < 0) { + return -errno; + } + + rc = sscanf(szVal, "%d", val); + if (rc != 1) { + return -1; + } + + return 0; +} + +static int sysfs_write_val(int fd, int val) +{ + int n, rc; + char szVal[32] = {0}; + + n = sprintf(szVal, "%d", val); + + lseek(fd, 0, SEEK_SET); + rc = write(fd, szVal, n+1); + if (rc < 0) { + return -errno; + } + return 0; +} + +static int sysfs_write_str(int fd, const char *str) +{ + int rc; + + lseek( fd, 0, SEEK_SET ); + rc = write(fd, str, strlen(str)+1); + if (rc < 0) { + return -errno; + } + return 0; +} + + +int oc2gbts_clock_err_open(void) +{ + int rc; + int fault; + + if (clkerr_fd_err < 0) { + clkerr_fd_err = open(CLKERR_ERR_SYSFS, O_RDONLY); + if (clkerr_fd_err < 0) { + oc2gbts_clock_err_close(); + return clkerr_fd_err; + } + } + + if (clkerr_fd_accuracy < 0) { + clkerr_fd_accuracy = open(CLKERR_ACC_SYSFS, O_RDONLY); + if (clkerr_fd_accuracy < 0) { + oc2gbts_clock_err_close(); + return clkerr_fd_accuracy; + } + } + + if (clkerr_fd_interval < 0) { + clkerr_fd_interval = open(CLKERR_INT_SYSFS, O_RDONLY); + if (clkerr_fd_interval < 0) { + oc2gbts_clock_err_close(); + return clkerr_fd_interval; + } + } + + if (clkerr_fd_fault < 0) { + clkerr_fd_fault = open(CLKERR_FLT_SYSFS, O_RDONLY); + if (clkerr_fd_fault < 0) { + oc2gbts_clock_err_close(); + return clkerr_fd_fault; + } + } + + if (clkerr_fd_refresh < 0) { + clkerr_fd_refresh = open(CLKERR_RFS_SYSFS, O_WRONLY); + if (clkerr_fd_refresh < 0) { + oc2gbts_clock_err_close(); + return clkerr_fd_refresh; + } + } + + if (clkerr_fd_reset < 0) { + clkerr_fd_reset = open(CLKERR_RST_SYSFS, O_WRONLY); + if (clkerr_fd_reset < 0) { + oc2gbts_clock_err_close(); + return clkerr_fd_reset; + } + } + return 0; +} + +void oc2gbts_clock_err_close(void) +{ + if (clkerr_fd_err >= 0) { + close(clkerr_fd_err); + clkerr_fd_err = -1; + } + + if (clkerr_fd_accuracy >= 0) { + close(clkerr_fd_accuracy); + clkerr_fd_accuracy = -1; + } + + if (clkerr_fd_interval >= 0) { + close(clkerr_fd_interval); + clkerr_fd_interval = -1; + } + + if (clkerr_fd_fault >= 0) { + close(clkerr_fd_fault); + clkerr_fd_fault = -1; + } + + if (clkerr_fd_refresh >= 0) { + close(clkerr_fd_refresh); + clkerr_fd_refresh = -1; + } + + if (clkerr_fd_reset >= 0) { + close(clkerr_fd_reset); + clkerr_fd_reset = -1; + } +} + +int oc2gbts_clock_err_reset(void) +{ + return sysfs_write_val(clkerr_fd_reset, 1); +} + +int oc2gbts_clock_err_get(int *fault, int *error_ppt, + int *accuracy_ppq, int *interval_sec) +{ + int rc; + + rc = sysfs_write_str(clkerr_fd_refresh, "once"); + if (rc < 0) { + return -1; + } + + rc = sysfs_read_val(clkerr_fd_fault, fault); + rc |= sysfs_read_val(clkerr_fd_err, error_ppt); + rc |= sysfs_read_val(clkerr_fd_accuracy, accuracy_ppq); + rc |= sysfs_read_val(clkerr_fd_interval, interval_sec); + if (rc) { + return -1; + } + return 0; +} + + +int oc2gbts_clock_dac_open(void) +{ + if (ocxodac_fd_value < 0) { + ocxodac_fd_value = open(OCXODAC_VAL_SYSFS, O_RDWR); + if (ocxodac_fd_value < 0) { + oc2gbts_clock_dac_close(); + return ocxodac_fd_value; + } + } + + if (ocxodac_fd_save < 0) { + ocxodac_fd_save = open(OCXODAC_ROM_SYSFS, O_WRONLY); + if (ocxodac_fd_save < 0) { + oc2gbts_clock_dac_close(); + return ocxodac_fd_save; + } + } + return 0; +} + +void oc2gbts_clock_dac_close(void) +{ + if (ocxodac_fd_value >= 0) { + close(ocxodac_fd_value); + ocxodac_fd_value = -1; + } + + if (ocxodac_fd_save >= 0) { + close(ocxodac_fd_save); + ocxodac_fd_save = -1; + } +} + +int oc2gbts_clock_dac_get(int *dac_value) +{ + return sysfs_read_val(ocxodac_fd_value, dac_value); +} + +int oc2gbts_clock_dac_set(int dac_value) +{ + return sysfs_write_val(ocxodac_fd_value, dac_value); +} + +int oc2gbts_clock_dac_save(void) +{ + return sysfs_write_val(ocxodac_fd_save, 1); +} + + diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_clock.h b/src/osmo-bts-oc2g/misc/oc2gbts_clock.h new file mode 100644 index 000000000..1444b5cf7 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_clock.h @@ -0,0 +1,16 @@ +#ifndef _OC2GBTS_CLOCK_H +#define _OC2GBTS_CLOCK_H + +int oc2gbts_clock_err_open(void); +void oc2gbts_clock_err_close(void); +int oc2gbts_clock_err_reset(void); +int oc2gbts_clock_err_get(int *fault, int *error_ppt, + int *accuracy_ppq, int *interval_sec); + +int oc2gbts_clock_dac_open(void); +void oc2gbts_clock_dac_close(void); +int oc2gbts_clock_dac_get(int *dac_value); +int oc2gbts_clock_dac_set(int dac_value); +int oc2gbts_clock_dac_save(void); + +#endif diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_led.c b/src/osmo-bts-oc2g/misc/oc2gbts_led.c new file mode 100644 index 000000000..b8758b8ee --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_led.c @@ -0,0 +1,332 @@ +/* Copyright (C) 2016 by NuRAN Wireless + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include "oc2gbts_led.h" +#include "oc2gbts_bts.h" +#include +#include + +static struct oc2gbts_led led_entries[] = { + { + .name = "led0", + .fullname = "led red", + .path = "/var/oc2g/leds/led0/brightness" + }, + { + .name = "led1", + .fullname = "led green", + .path = "/var/oc2g/leds/led1/brightness" + } +}; + +static const struct value_string oc2gbts_led_strs[] = { + { OC2GBTS_LED_RED, "LED red" }, + { OC2GBTS_LED_GREEN, "LED green" }, + { OC2GBTS_LED_ORANGE, "LED orange" }, + { OC2GBTS_LED_OFF, "LED off" }, + { 0, NULL } +}; + +static uint8_t led_priority[] = { + BLINK_PATTERN_INIT, + BLINK_PATTERN_INT_PROC_MALFUNC, + BLINK_PATTERN_SUPPLY_PWR_MAX, + BLINK_PATTERN_PA_PWR_MAX, + BLINK_PATTERN_VSWR_MAX, + BLINK_PATTERN_SUPPLY_VOLT_MIN, + BLINK_PATTERN_TEMP_MAX, + BLINK_PATTERN_EXT_LINK_MALFUNC, + BLINK_PATTERN_SUPPLY_VOLT_LOW, + BLINK_PATTERN_TEMP_HIGH, + BLINK_PATTERN_VSWR_HIGH, + BLINK_PATTERN_SUPPLY_PWR_HIGH, + BLINK_PATTERN_PA_PWR_HIGH, + BLINK_PATTERN_GPS_FIX_LOST, + BLINK_PATTERN_NORMAL +}; + + +char *blink_pattern_command[] = BLINK_PATTERN_COMMAND; + +static int oc2gbts_led_write(char *path, char *str) +{ + int fd; + + if ((fd = open(path, O_WRONLY)) == -1) + { + return 0; + } + + write(fd, str, strlen(str)+1); + close(fd); + return 1; +} + +static void led_set_red() +{ + oc2gbts_led_write(led_entries[0].path, "1"); + oc2gbts_led_write(led_entries[1].path, "0"); +} + +static void led_set_green() +{ + oc2gbts_led_write(led_entries[0].path, "0"); + oc2gbts_led_write(led_entries[1].path, "1"); +} + +static void led_set_orange() +{ + oc2gbts_led_write(led_entries[0].path, "1"); + oc2gbts_led_write(led_entries[1].path, "1"); +} + +static void led_set_off() +{ + oc2gbts_led_write(led_entries[0].path, "0"); + oc2gbts_led_write(led_entries[1].path, "0"); +} + +static void led_sleep( struct oc2gbts_mgr_instance *mgr, struct oc2gbts_led_timer *led_timer, void (*led_timer_cb)(void *_data)) { + /* Cancel any pending timer */ + osmo_timer_del(&led_timer->timer); + /* Start LED timer */ + led_timer->timer.cb = led_timer_cb; + led_timer->timer.data = mgr; + mgr->oc2gbts_leds.active_timer = led_timer->idx; + osmo_timer_schedule(&led_timer->timer, led_timer->param.sleep_sec, led_timer->param.sleep_usec); + LOGP(DTEMP, LOGL_DEBUG,"%s timer scheduled for %d sec + %d usec\n", + get_value_string(oc2gbts_led_strs, led_timer->idx), + led_timer->param.sleep_sec, + led_timer->param.sleep_usec); + + switch (led_timer->idx) { + case OC2GBTS_LED_RED: + led_set_red(); + break; + case OC2GBTS_LED_GREEN: + led_set_green(); + break; + case OC2GBTS_LED_ORANGE: + led_set_orange(); + break; + case OC2GBTS_LED_OFF: + led_set_off(); + break; + default: + led_set_off(); + } +} + +static void led_sleep_cb(void *_data) { + struct oc2gbts_mgr_instance *mgr = _data; + struct oc2gbts_led_timer_list *led_list; + + /* make sure the timer list is not empty */ + if (llist_empty(&mgr->oc2gbts_leds.list)) + return; + + llist_for_each_entry(led_list, &mgr->oc2gbts_leds.list, list) { + if (led_list->led_timer.idx == mgr->oc2gbts_leds.active_timer) { + LOGP(DTEMP, LOGL_DEBUG,"Delete expired %s timer %d sec + %d usec\n", + get_value_string(oc2gbts_led_strs, led_list->led_timer.idx), + led_list->led_timer.param.sleep_sec, + led_list->led_timer.param.sleep_usec); + + /* Delete current timer */ + osmo_timer_del(&led_list->led_timer.timer); + /* Rotate the timer list */ + llist_move_tail(led_list, &mgr->oc2gbts_leds.list); + break; + } + } + + /* Execute next timer */ + led_list = llist_first_entry(&mgr->oc2gbts_leds.list, struct oc2gbts_led_timer_list, list); + if (led_list) { + LOGP(DTEMP, LOGL_DEBUG,"Execute %s timer %d sec + %d usec, total entries=%d\n", + get_value_string(oc2gbts_led_strs, led_list->led_timer.idx), + led_list->led_timer.param.sleep_sec, + led_list->led_timer.param.sleep_usec, + llist_count(&mgr->oc2gbts_leds.list)); + + led_sleep(mgr, &led_list->led_timer, led_sleep_cb); + } + +} + +static void delete_led_timer_entries(struct oc2gbts_mgr_instance *mgr) +{ + struct oc2gbts_led_timer_list *led_list, *led_list2; + + if (llist_empty(&mgr->oc2gbts_leds.list)) + return; + + llist_for_each_entry_safe(led_list, led_list2, &mgr->oc2gbts_leds.list, list) { + /* Delete the timer in list */ + if (led_list) { + LOGP(DTEMP, LOGL_DEBUG,"Delete %s timer entry from list, %d sec + %d usec\n", + get_value_string(oc2gbts_led_strs, led_list->led_timer.idx), + led_list->led_timer.param.sleep_sec, + led_list->led_timer.param.sleep_usec); + + /* Delete current timer */ + osmo_timer_del(&led_list->led_timer.timer); + llist_del(&led_list->list); + talloc_free(led_list); + } + } + return; +} + +static int add_led_timer_entry(struct oc2gbts_mgr_instance *mgr, char *cmdstr) +{ + double sec, int_sec, frac_sec; + struct oc2gbts_sleep_time led_param; + + led_param.sleep_sec = 0; + led_param.sleep_usec = 0; + + if (strstr(cmdstr, "set red") != NULL) + mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_RED; + else if (strstr(cmdstr, "set green") != NULL) + mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_GREEN; + else if (strstr(cmdstr, "set orange") != NULL) + mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_ORANGE; + else if (strstr(cmdstr, "set off") != NULL) + mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_OFF; + else if (strstr(cmdstr, "sleep") != NULL) { + sec = atof(cmdstr + 6); + /* split time into integer and fractional of seconds */ + frac_sec = modf(sec, &int_sec) * 1000000.0; + led_param.sleep_sec = (int)int_sec; + led_param.sleep_usec = (int)frac_sec; + + if ((mgr->oc2gbts_leds.led_idx >= OC2GBTS_LED_RED) && (mgr->oc2gbts_leds.led_idx < _OC2GBTS_LED_MAX)) { + struct oc2gbts_led_timer_list *led_list; + + /* allocate timer entry */ + led_list = talloc_zero(tall_mgr_ctx, struct oc2gbts_led_timer_list); + if (led_list) { + led_list->led_timer.idx = mgr->oc2gbts_leds.led_idx; + led_list->led_timer.param.sleep_sec = led_param.sleep_sec; + led_list->led_timer.param.sleep_usec = led_param.sleep_usec; + llist_add_tail(&led_list->list, &mgr->oc2gbts_leds.list); + + LOGP(DTEMP, LOGL_DEBUG,"Add %s timer to list, %d sec + %d usec, total entries=%d\n", + get_value_string(oc2gbts_led_strs, mgr->oc2gbts_leds.led_idx), + led_list->led_timer.param.sleep_sec, + led_list->led_timer.param.sleep_usec, + llist_count(&mgr->oc2gbts_leds.list)); + } + } + } else + return -1; + + return 0; +} + +static int parse_led_pattern(char *pattern, struct oc2gbts_mgr_instance *mgr) +{ + char str[1024]; + char *pstr; + char *sep; + int rc = 0; + + strcpy(str, pattern); + pstr = str; + while ((sep = strsep(&pstr, ";")) != NULL) { + rc = add_led_timer_entry(mgr, sep); + if (rc < 0) { + break; + } + + } + return rc; +} + +/*** led interface ***/ + +void led_set(struct oc2gbts_mgr_instance *mgr, int pattern_id) +{ + int rc; + struct oc2gbts_led_timer_list *led_list; + + if (pattern_id > BLINK_PATTERN_MAX_ITEM - 1) { + LOGP(DTEMP, LOGL_ERROR, "Invalid LED pattern : %d. LED pattern must be between %d..%d\n", + pattern_id, + BLINK_PATTERN_POWER_ON, + BLINK_PATTERN_MAX_ITEM - 1); + return; + } + if (pattern_id == mgr->oc2gbts_leds.last_pattern_id) + return; + + mgr->oc2gbts_leds.last_pattern_id = pattern_id; + + LOGP(DTEMP, LOGL_INFO, "blink pattern command : %d\n", pattern_id); + LOGP(DTEMP, LOGL_INFO, "%s\n", blink_pattern_command[pattern_id]); + + /* Empty existing LED timer in the list */ + delete_led_timer_entries(mgr); + + /* parse LED pattern */ + rc = parse_led_pattern(blink_pattern_command[pattern_id], mgr); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR,"LED pattern not found or invalid LED pattern\n"); + return; + } + + /* make sure the timer list is not empty */ + if (llist_empty(&mgr->oc2gbts_leds.list)) + return; + + /* Start the first LED timer in the list */ + led_list = llist_first_entry(&mgr->oc2gbts_leds.list, struct oc2gbts_led_timer_list, list); + if (led_list) { + LOGP(DTEMP, LOGL_DEBUG,"Execute timer %s for %d sec + %d usec\n", + get_value_string(oc2gbts_led_strs, led_list->led_timer.idx), + led_list->led_timer.param.sleep_sec, + led_list->led_timer.param.sleep_usec); + + led_sleep(mgr, &led_list->led_timer, led_sleep_cb); + } + +} + +void select_led_pattern(struct oc2gbts_mgr_instance *mgr) +{ + int i; + uint8_t led[BLINK_PATTERN_MAX_ITEM] = {0}; + + /* set normal LED pattern at first */ + led[BLINK_PATTERN_NORMAL] = 1; + + /* check on-board sensors for new LED pattern */ + check_sensor_led_pattern(mgr, led); + + /* check BTS status for new LED pattern */ + check_bts_led_pattern(led); + + /* check by priority */ + for (i = 0; i < sizeof(led_priority)/sizeof(uint8_t); i++) { + if(led[led_priority[i]] == 1) { + led_set(mgr, led_priority[i]); + break; + } + } +} diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_led.h b/src/osmo-bts-oc2g/misc/oc2gbts_led.h new file mode 100644 index 000000000..cb627e3cf --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_led.h @@ -0,0 +1,22 @@ +#ifndef _OC2GBTS_LED_H +#define _OC2GBTS_LED_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "oc2gbts_mgr.h" + +/* public function prototypes */ +void led_set(struct oc2gbts_mgr_instance *mgr, int pattern_id); + +void select_led_pattern(struct oc2gbts_mgr_instance *mgr); + +#endif diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr.c new file mode 100644 index 000000000..45ecc65da --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr.c @@ -0,0 +1,353 @@ +/* Main program for NuRAN Wireless OC-2G BTS management daemon */ + +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * sysmobts_mgr.c + * (C) 2012 by Harald Welte + * (C) 2014 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "misc/oc2gbts_misc.h" +#include "misc/oc2gbts_mgr.h" +#include "misc/oc2gbts_par.h" +#include "misc/oc2gbts_bid.h" +#include "misc/oc2gbts_power.h" +#include "misc/oc2gbts_swd.h" + +#include "oc2gbts_led.h" + +static int no_rom_write = 0; +static int daemonize = 0; +void *tall_mgr_ctx; + +/* every 6 hours means 365*4 = 1460 rom writes per year (max) */ +#define SENSOR_TIMER_SECS (6 * 3600) + +/* every 1 hours means 365*24 = 8760 rom writes per year (max) */ +#define HOURS_TIMER_SECS (1 * 3600) + +/* the initial state */ +static struct oc2gbts_mgr_instance manager = { + .config_file = "oc2gbts-mgr.cfg", + .temp = { + .supply_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -40, + }, + .soc_temp_limit = { + .thresh_warn_max = 95, + .thresh_crit_max = 100, + .thresh_warn_min = -40, + }, + .fpga_temp_limit = { + .thresh_warn_max = 95, + .thresh_crit_max = 100, + .thresh_warn_min = -40, + }, + .rmsdet_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -40, + }, + .ocxo_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -40, + }, + .tx_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -20, + }, + .pa_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -40, + } + }, + .volt = { + .supply_volt_limit = { + .thresh_warn_max = 30000, + .thresh_crit_max = 30500, + .thresh_warn_min = 19000, + .thresh_crit_min = 17500, + } + }, + .pwr = { + .supply_pwr_limit = { + .thresh_warn_max = 30, + .thresh_crit_max = 40, + }, + .pa_pwr_limit = { + .thresh_warn_max = 20, + .thresh_crit_max = 30, + } + }, + .vswr = { + .vswr_limit = { + .thresh_warn_max = 3000, + .thresh_crit_max = 5000, + } + }, + .gps = { + .gps_fix_limit = { + .thresh_warn_max = 7, + } + }, + .state = { + .action_norm = SENSOR_ACT_NORM_PA_ON, + .action_warn = 0, + .action_crit = 0, + .action_comb = 0, + .state = STATE_NORMAL, + } +}; + +static struct osmo_timer_list sensor_timer; +static void check_sensor_timer_cb(void *unused) +{ + oc2gbts_check_temp(no_rom_write); + oc2gbts_check_power(no_rom_write); + oc2gbts_check_vswr(no_rom_write); + osmo_timer_schedule(&sensor_timer, SENSOR_TIMER_SECS, 0); + /* TODO checks if oc2gbts_check_temp/oc2gbts_check_power/oc2gbts_check_vswr went ok */ + oc2gbts_swd_event(&manager, SWD_CHECK_SENSOR); +} + +static struct osmo_timer_list hours_timer; +static void hours_timer_cb(void *unused) +{ + oc2gbts_update_hours(no_rom_write); + + osmo_timer_schedule(&hours_timer, HOURS_TIMER_SECS, 0); + /* TODO: validates if oc2gbts_update_hours went correctly */ + oc2gbts_swd_event(&manager, SWD_UPDATE_HOURS); +} + +static void print_help(void) +{ + printf("oc2gbts-mgr [-nsD] [-d cat]\n"); + printf(" -n Do not write to ROM\n"); + printf(" -s Disable color\n"); + printf(" -d CAT enable debugging\n"); + printf(" -D daemonize\n"); + printf(" -c Specify the filename of the config file\n"); +} + +static int parse_options(int argc, char **argv) +{ + int opt; + + while ((opt = getopt(argc, argv, "nhsd:c:")) != -1) { + switch (opt) { + case 'n': + no_rom_write = 1; + break; + case 'h': + print_help(); + return -1; + case 's': + log_set_use_color(osmo_stderr_target, 0); + break; + case 'd': + log_parse_category_mask(osmo_stderr_target, optarg); + break; + case 'D': + daemonize = 1; + break; + case 'c': + manager.config_file = optarg; + break; + default: + return -1; + } + } + + return 0; +} + +static void signal_handler(int signal) +{ + fprintf(stderr, "signal %u received\n", signal); + + switch (signal) { + case SIGINT: + oc2gbts_check_temp(no_rom_write); + oc2gbts_check_power(no_rom_write); + oc2gbts_check_vswr(no_rom_write); + oc2gbts_update_hours(no_rom_write); + exit(0); + break; + case SIGABRT: + case SIGUSR1: + case SIGUSR2: + talloc_report_full(tall_mgr_ctx, stderr); + break; + default: + break; + } +} + +static struct log_info_cat mgr_log_info_cat[] = { + [DTEMP] = { + .name = "DTEMP", + .description = "Temperature monitoring", + .color = "\033[1;35m", + .enabled = 1, .loglevel = LOGL_INFO, + }, + [DFW] = { + .name = "DFW", + .description = "Firmware management", + .color = "\033[1;36m", + .enabled = 1, .loglevel = LOGL_INFO, + }, + [DFIND] = { + .name = "DFIND", + .description = "ipaccess-find handling", + .color = "\033[1;37m", + .enabled = 1, .loglevel = LOGL_INFO, + }, + [DCALIB] = { + .name = "DCALIB", + .description = "Calibration handling", + .color = "\033[1;37m", + .enabled = 1, .loglevel = LOGL_INFO, + }, + [DSWD] = { + .name = "DSWD", + .description = "Software Watchdog", + .color = "\033[1;37m", + .enabled = 1, .loglevel = LOGL_INFO, + }, +}; + +static const struct log_info mgr_log_info = { + .cat = mgr_log_info_cat, + .num_cat = ARRAY_SIZE(mgr_log_info_cat), +}; + +static int mgr_log_init(void) +{ + osmo_init_logging(&mgr_log_info); + return 0; +} + +int main(int argc, char **argv) +{ + void *tall_msgb_ctx; + int rc; + pthread_t tid; + + tall_mgr_ctx = talloc_named_const(NULL, 1, "bts manager"); + tall_msgb_ctx = talloc_named_const(tall_mgr_ctx, 1, "msgb"); + msgb_set_talloc_ctx(tall_msgb_ctx); + + mgr_log_init(); + + osmo_init_ignore_signals(); + signal(SIGINT, &signal_handler); + signal(SIGUSR1, &signal_handler); + signal(SIGUSR2, &signal_handler); + + rc = parse_options(argc, argv); + if (rc < 0) + exit(2); + + oc2gbts_mgr_vty_init(); + logging_vty_add_cmds(&mgr_log_info); + rc = oc2gbts_mgr_parse_config(&manager); + if (rc < 0) { + LOGP(DFIND, LOGL_FATAL, "Cannot parse config file\n"); + exit(1); + } + + rc = telnet_init(tall_mgr_ctx, NULL, OSMO_VTY_PORT_BTSMGR); + if (rc < 0) { + fprintf(stderr, "Error initializing telnet\n"); + exit(1); + } + + INIT_LLIST_HEAD(&manager.oc2gbts_leds.list); + INIT_LLIST_HEAD(&manager.alarms.list); + + /* Initialize the service watchdog notification for SWD_LAST event(s) */ + if (oc2gbts_swd_init(&manager, (int)(SWD_LAST)) != 0) + exit(3); + + /* start temperature check timer */ + sensor_timer.cb = check_sensor_timer_cb; + check_sensor_timer_cb(NULL); + + /* start operational hours timer */ + hours_timer.cb = hours_timer_cb; + hours_timer_cb(NULL); + + if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) { + /* Enable the PAs */ + rc = oc2gbts_power_set(OC2GBTS_POWER_PA, 1); + if (rc < 0) { + exit(3); + } + } + /* handle broadcast messages for ipaccess-find */ + if (oc2gbts_mgr_nl_init() != 0) + exit(3); + + /* Initialize the sensor control */ + oc2gbts_mgr_sensor_init(&manager); + + if (oc2gbts_mgr_calib_init(&manager) != 0) + exit(3); + + if (oc2gbts_mgr_control_init(&manager)) + exit(3); + + if (daemonize) { + rc = osmo_daemonize(); + if (rc < 0) { + perror("Error during daemonize"); + exit(1); + } + } + + while (1) { + log_reset_context(); + osmo_select_main(0); + oc2gbts_swd_event(&manager, SWD_MAINLOOP); + } +} diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr.h b/src/osmo-bts-oc2g/misc/oc2gbts_mgr.h new file mode 100644 index 000000000..1f50fb6b9 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr.h @@ -0,0 +1,398 @@ +#ifndef _OC2GBTS_MGR_H +#define _OC2GBTS_MGR_H + +#include +#include + +#include +#include + +#include +#include + +#define OC2GBTS_SENSOR_TIMER_DURATION 60 +#define OC2GBTS_PREVENT_TIMER_DURATION 15 * 60 +#define OC2GBTS_PREVENT_TIMER_SHORT_DURATION 5 * 60 +#define OC2GBTS_PREVENT_TIMER_NONE 0 +#define OC2GBTS_PREVENT_RETRY INT_MAX - 1 + +enum BLINK_PATTERN { + BLINK_PATTERN_POWER_ON = 0, //hardware set + BLINK_PATTERN_INIT, + BLINK_PATTERN_NORMAL, + BLINK_PATTERN_EXT_LINK_MALFUNC, + BLINK_PATTERN_INT_PROC_MALFUNC, + BLINK_PATTERN_SUPPLY_VOLT_LOW, + BLINK_PATTERN_SUPPLY_VOLT_MIN, + BLINK_PATTERN_VSWR_HIGH, + BLINK_PATTERN_VSWR_MAX, + BLINK_PATTERN_TEMP_HIGH, + BLINK_PATTERN_TEMP_MAX, + BLINK_PATTERN_SUPPLY_PWR_HIGH, + BLINK_PATTERN_SUPPLY_PWR_MAX, + BLINK_PATTERN_PA_PWR_HIGH, + BLINK_PATTERN_PA_PWR_MAX, + BLINK_PATTERN_GPS_FIX_LOST, + BLINK_PATTERN_MAX_ITEM +}; + +#define BLINK_PATTERN_COMMAND {\ + "set red; sleep 5.0",\ + "set orange; sleep 5.0",\ + "set green; sleep 2.5; set off; sleep 2.5",\ + "set red; sleep 0.5; set off; sleep 0.5",\ + "set red; sleep 2.5; set off; sleep 2.5",\ + "set green; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5",\ + "set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5 ",\ + "set green; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\ + "set red; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\ + "set orange; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5 ",\ + "set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\ + "set green; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\ + "set red; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\ + "set green; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\ + "set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\ + "set green; sleep 2.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\ +} + +enum { + DTEMP, + DFW, + DFIND, + DCALIB, + DSWD, +}; + +// TODO NTQD: Define new actions like reducing output power, limit ARM core speed, shutdown second TRX/PA, ... +enum { +#if 0 + SENSOR_ACT_PWR_CONTRL = 0x1, +#endif + SENSOR_ACT_PA_OFF = 0x2, + SENSOR_ACT_BTS_SRV_OFF = 0x10, +}; + +/* actions only for normal state */ +enum { +#if 0 + SENSOR_ACT_NORM_PW_CONTRL = 0x1, +#endif + SENSOR_ACT_NORM_PA_ON = 0x2, + SENSOR_ACT_NORM_BTS_SRV_ON= 0x10, +}; + +enum oc2gbts_sensor_state { + STATE_NORMAL, /* Everything is fine */ + STATE_WARNING_HYST, /* Go back to normal next? */ + STATE_WARNING, /* We are above the warning threshold */ + STATE_CRITICAL, /* We have an issue. Wait for below warning */ +}; + +enum oc2gbts_leds_name { + OC2GBTS_LED_RED = 0, + OC2GBTS_LED_GREEN, + OC2GBTS_LED_ORANGE, + OC2GBTS_LED_OFF, + _OC2GBTS_LED_MAX +}; + +struct oc2gbts_led{ + char *name; + char *fullname; + char *path; +}; + +/** + * Temperature Limits. We separate from a threshold + * that will generate a warning and one that is so + * severe that an action will be taken. + */ +struct oc2gbts_temp_limit { + int thresh_warn_max; + int thresh_crit_max; + int thresh_warn_min; +}; + +struct oc2gbts_volt_limit { + int thresh_warn_max; + int thresh_crit_max; + int thresh_warn_min; + int thresh_crit_min; +}; + +struct oc2gbts_pwr_limit { + int thresh_warn_max; + int thresh_crit_max; +}; + +struct oc2gbts_vswr_limit { + int thresh_warn_max; + int thresh_crit_max; +}; + +struct oc2gbts_gps_fix_limit { + int thresh_warn_max; +}; + +struct oc2gbts_sleep_time { + int sleep_sec; + int sleep_usec; +}; + +struct oc2gbts_led_timer { + uint8_t idx; + struct osmo_timer_list timer; + struct oc2gbts_sleep_time param; +}; + +struct oc2gbts_led_timer_list { + struct llist_head list; + struct oc2gbts_led_timer led_timer; +}; + +struct oc2gbts_preventive_list { + struct llist_head list; + struct oc2gbts_sleep_time param; + int action_flag; +}; + +enum mgr_vty_node { + MGR_NODE = _LAST_OSMOVTY_NODE + 1, + + ACT_NORM_NODE, + ACT_WARN_NODE, + ACT_CRIT_NODE, + LIMIT_SUPPLY_TEMP_NODE, + LIMIT_SOC_NODE, + LIMIT_FPGA_NODE, + LIMIT_RMSDET_NODE, + LIMIT_OCXO_NODE, + LIMIT_TX_TEMP_NODE, + LIMIT_PA_TEMP_NODE, + LIMIT_SUPPLY_VOLT_NODE, + LIMIT_VSWR_NODE, + LIMIT_SUPPLY_PWR_NODE, + LIMIT_PA_PWR_NODE, + LIMIT_GPS_FIX_NODE, +}; + +enum mgr_vty_limit_type { + MGR_LIMIT_TYPE_TEMP = 0, + MGR_LIMIT_TYPE_VOLT, + MGR_LIMIT_TYPE_VSWR, + MGR_LIMIT_TYPE_PWR, + _MGR_LIMIT_TYPE_MAX, +}; + +struct oc2gbts_mgr_instance { + const char *config_file; + + struct { + struct oc2gbts_temp_limit supply_temp_limit; + struct oc2gbts_temp_limit soc_temp_limit; + struct oc2gbts_temp_limit fpga_temp_limit; + struct oc2gbts_temp_limit rmsdet_temp_limit; + struct oc2gbts_temp_limit ocxo_temp_limit; + struct oc2gbts_temp_limit tx_temp_limit; + struct oc2gbts_temp_limit pa_temp_limit; + } temp; + + struct { + struct oc2gbts_volt_limit supply_volt_limit; + } volt; + + struct { + struct oc2gbts_pwr_limit supply_pwr_limit; + struct oc2gbts_pwr_limit pa_pwr_limit; + } pwr; + + struct { + struct oc2gbts_vswr_limit vswr_limit; + int last_vswr; + } vswr; + + struct { + struct oc2gbts_gps_fix_limit gps_fix_limit; + int last_update; + time_t last_gps_fix; + time_t gps_fix_now; + int gps_open; + struct osmo_fd gpsfd; + struct gps_data_t gpsdata; + struct osmo_timer_list fix_timeout; + } gps; + + struct { + int action_norm; + int action_warn; + int action_crit; + int action_comb; + + enum oc2gbts_sensor_state state; + } state; + + struct { + int state; + int calib_from_loop; + struct osmo_timer_list calib_timeout; + } calib; + + struct { + int state; + int swd_from_loop; + unsigned long long int swd_events; + unsigned long long int swd_events_cache; + unsigned long long int swd_eventmasks; + int num_events; + struct osmo_timer_list swd_timeout; + } swd; + + struct { + uint8_t led_idx; + uint8_t last_pattern_id; + uint8_t active_timer; + struct llist_head list; + } oc2gbts_leds; + + struct { + int is_up; + uint32_t last_seqno; + struct osmo_timer_list recon_timer; + struct ipa_client_conn *bts_conn; + uint32_t crit_flags; + uint32_t warn_flags; + } oc2gbts_ctrl; + + struct oc2gbts_alarms { + int temp_high; + int temp_max; + int supply_low; + int supply_min; + int vswr_high; + int vswr_max; + int supply_pwr_high; + int supply_pwr_max; + int pa_pwr_high; + int pa_pwr_max; + int gps_fix_lost; + struct llist_head list; + struct osmo_timer_list preventive_timer; + int preventive_duration; + int preventive_retry; + } alarms; + +}; + +enum oc2gbts_mgr_fail_evt_rep_crit_sig { + /* Critical alarms */ + S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM = (1 << 0), + S_MGR_TEMP_SOC_CRIT_MAX_ALARM = (1 << 1), + S_MGR_TEMP_FPGA_CRIT_MAX_ALARM = (1 << 2), + S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM = (1 << 3), + S_MGR_TEMP_OCXO_CRIT_MAX_ALARM = (1 << 4), + S_MGR_TEMP_TRX_CRIT_MAX_ALARM = (1 << 5), + S_MGR_TEMP_PA_CRIT_MAX_ALARM = (1 << 6), + S_MGR_SUPPLY_CRIT_MAX_ALARM = (1 << 7), + S_MGR_SUPPLY_CRIT_MIN_ALARM = (1 << 8), + S_MGR_VSWR_CRIT_MAX_ALARM = (1 << 9), + S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM = (1 << 10), + S_MGR_PWR_PA_CRIT_MAX_ALARM = (1 << 11), + _S_MGR_CRIT_ALARM_MAX, +}; + +enum oc2gbts_mgr_fail_evt_rep_warn_sig { + /* Warning alarms */ + S_MGR_TEMP_SUPPLY_WARN_MIN_ALARM = (1 << 0), + S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM = (1 << 1), + S_MGR_TEMP_SOC_WARN_MIN_ALARM = (1 << 2), + S_MGR_TEMP_SOC_WARN_MAX_ALARM = (1 << 3), + S_MGR_TEMP_FPGA_WARN_MIN_ALARM = (1 << 4), + S_MGR_TEMP_FPGA_WARN_MAX_ALARM = (1 << 5), + S_MGR_TEMP_RMS_DET_WARN_MIN_ALARM = (1 << 6), + S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM = (1 << 7), + S_MGR_TEMP_OCXO_WARN_MIN_ALARM = (1 << 8), + S_MGR_TEMP_OCXO_WARN_MAX_ALARM = (1 << 9), + S_MGR_TEMP_TRX_WARN_MIN_ALARM = (1 << 10), + S_MGR_TEMP_TRX_WARN_MAX_ALARM = (1 << 11), + S_MGR_TEMP_PA_WARN_MIN_ALARM = (1 << 12), + S_MGR_TEMP_PA_WARN_MAX_ALARM = (1 << 13), + S_MGR_SUPPLY_WARN_MIN_ALARM = (1 << 14), + S_MGR_SUPPLY_WARN_MAX_ALARM = (1 << 15), + S_MGR_VSWR_WARN_MAX_ALARM = (1 << 16), + S_MGR_PWR_SUPPLY_WARN_MAX_ALARM = (1 << 17), + S_MGR_PWR_PA_WARN_MAX_ALARM = (1 << 18), + S_MGR_GPS_FIX_WARN_ALARM = (1 << 19), + _S_MGR_WARN_ALARM_MAX, +}; + +enum oc2gbts_mgr_failure_event_causes { + /* Critical causes */ + NM_EVT_CAUSE_CRIT_TEMP_SUPPLY_MAX_FAIL = 0x4100, + NM_EVT_CAUSE_CRIT_TEMP_FPGA_MAX_FAIL = 0x4101, + NM_EVT_CAUSE_CRIT_TEMP_SOC_MAX_FAIL = 0x4102, + NM_EVT_CAUSE_CRIT_TEMP_RMS_DET_MAX_FAIL = 0x4103, + NM_EVT_CAUSE_CRIT_TEMP_OCXO_MAX_FAIL = 0x4104, + NM_EVT_CAUSE_CRIT_TEMP_TRX_MAX_FAIL = 0x4105, + NM_EVT_CAUSE_CRIT_TEMP_PA_MAX_FAIL = 0x4106, + NM_EVT_CAUSE_CRIT_SUPPLY_MAX_FAIL = 0x4107, + NM_EVT_CAUSE_CRIT_SUPPLY_MIN_FAIL = 0x4108, + NM_EVT_CAUSE_CRIT_VSWR_MAX_FAIL = 0x4109, + NM_EVT_CAUSE_CRIT_PWR_SUPPLY_MAX_FAIL = 0x410A, + NM_EVT_CAUSE_CRIT_PWR_PA_MAX_FAIL = 0x410B, + /* Warning causes */ + NM_EVT_CAUSE_WARN_TEMP_SUPPLY_LOW_FAIL = 0x4400, + NM_EVT_CAUSE_WARN_TEMP_SUPPLY_HIGH_FAIL = 0x4401, + NM_EVT_CAUSE_WARN_TEMP_FPGA_LOW_FAIL = 0x4402, + NM_EVT_CAUSE_WARN_TEMP_FPGA_HIGH_FAIL = 0x4403, + NM_EVT_CAUSE_WARN_TEMP_SOC_LOW_FAIL = 0x4404, + NM_EVT_CAUSE_WARN_TEMP_SOC_HIGH_FAIL = 0x4405, + NM_EVT_CAUSE_WARN_TEMP_RMS_DET_LOW_FAIL = 0x4406, + NM_EVT_CAUSE_WARN_TEMP_RMS_DET_HIGH_FAIL= 0x4407, + NM_EVT_CAUSE_WARN_TEMP_OCXO_LOW_FAIL = 0x4408, + NM_EVT_CAUSE_WARN_TEMP_OCXO_HIGH_FAIL = 0x4409, + NM_EVT_CAUSE_WARN_TEMP_TRX_LOW_FAIL = 0x440A, + NM_EVT_CAUSE_WARN_TEMP_TRX_HIGH_FAIL = 0x440B, + NM_EVT_CAUSE_WARN_TEMP_PA_LOW_FAIL = 0x440C, + NM_EVT_CAUSE_WARN_TEMP_PA_HIGH_FAIL = 0x440D, + NM_EVT_CAUSE_WARN_SUPPLY_LOW_FAIL = 0x440E, + NM_EVT_CAUSE_WARN_SUPPLY_HIGH_FAIL = 0x440F, + NM_EVT_CAUSE_WARN_VSWR_HIGH_FAIL = 0x4410, + NM_EVT_CAUSE_WARN_PWR_SUPPLY_HIGH_FAIL = 0x4411, + NM_EVT_CAUSE_WARN_PWR_PA_HIGH_FAIL = 0x4412, + NM_EVT_CAUSE_WARN_GPS_FIX_FAIL = 0x4413, +}; + +/* This defines the list of notification events for systemd service watchdog. + all these events must be notified in a certain service defined timeslot + or the service (this app) would be restarted (only if related systemd service + unit file has WatchdogSec!=0). + WARNING: swd events must begin with event 0. Last events must be + SWD_LAST (max 64 events in this list). +*/ +enum mgr_swd_events { + SWD_MAINLOOP = 0, + SWD_CHECK_SENSOR, + SWD_UPDATE_HOURS, + SWD_CHECK_TEMP_SENSOR, + SWD_CHECK_LED_CTRL, + SWD_CHECK_CALIB, + SWD_CHECK_BTS_CONNECTION, + SWD_LAST +}; + +int oc2gbts_mgr_vty_init(void); +int oc2gbts_mgr_parse_config(struct oc2gbts_mgr_instance *mgr); +int oc2gbts_mgr_nl_init(void); +int oc2gbts_mgr_sensor_init(struct oc2gbts_mgr_instance *mgr); +const char *oc2gbts_mgr_sensor_get_state(enum oc2gbts_sensor_state state); + +int oc2gbts_mgr_calib_init(struct oc2gbts_mgr_instance *mgr); +int oc2gbts_mgr_control_init(struct oc2gbts_mgr_instance *mgr); +int oc2gbts_mgr_calib_run(struct oc2gbts_mgr_instance *mgr); +void oc2gbts_mgr_dispatch_alarm(struct oc2gbts_mgr_instance *mgr, const int cause, const char *key, const char *text); +void handle_alert_actions(struct oc2gbts_mgr_instance *mgr); +void handle_ceased_actions(struct oc2gbts_mgr_instance *mgr); +void handle_warn_actions(struct oc2gbts_mgr_instance *mgr); +extern void *tall_mgr_ctx; + +#endif diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c new file mode 100644 index 000000000..961626215 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c @@ -0,0 +1,750 @@ +/* OCXO calibration control for OC-2G BTS management daemon */ + +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * sysmobts_mgr_calib.c + * (C) 2014,2015 by Holger Hans Peter Freyther + * (C) 2014 by Harald Welte for the IPA code from the oml router + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include "misc/oc2gbts_mgr.h" +#include "misc/oc2gbts_misc.h" +#include "misc/oc2gbts_clock.h" +#include "misc/oc2gbts_swd.h" +#include "misc/oc2gbts_par.h" +#include "misc/oc2gbts_led.h" +#include "osmo-bts/msg_utils.h" + +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include + +static void calib_adjust(struct oc2gbts_mgr_instance *mgr); +static void calib_state_reset(struct oc2gbts_mgr_instance *mgr, int reason); +static void calib_loop_run(void *_data); + +static int ocxodac_saved_value = -1; + +enum calib_state { + CALIB_INITIAL, + CALIB_IN_PROGRESS, + CALIB_GPS_WAIT_FOR_FIX, +}; + +enum calib_result { + CALIB_FAIL_START, + CALIB_FAIL_GPSFIX, + CALIB_FAIL_CLKERR, + CALIB_FAIL_OCXODAC, + CALIB_SUCCESS, +}; + +static int oc2gbts_par_get_uptime(void *ctx, int *ret) +{ + char *fpath; + FILE *fp; + int rc; + + fpath = talloc_asprintf(ctx, "%s", UPTIME_TMP_PATH); + if (!fpath) + return NULL; + + fp = fopen(fpath, "r"); + if (!fp) + fprintf(stderr, "Failed to open %s due to '%s' error\n", fpath, strerror(errno)); + + talloc_free(fpath); + + if (fp == NULL) { + return -errno; + } + + rc = fscanf(fp, "%d", ret); + if (rc != 1) { + fclose(fp); + return -EIO; + } + fclose(fp); + + return 0; +} + +static int oc2gbts_par_set_uptime(void *ctx, int val) +{ + char *fpath; + FILE *fp; + int rc; + + fpath = talloc_asprintf(ctx, "%s", UPTIME_TMP_PATH); + if (!fpath) + return NULL; + + fp = fopen(fpath, "w"); + if (!fp) + fprintf(stderr, "Failed to open %s due to '%s' error\n", fpath, strerror(errno)); + + talloc_free(fpath); + + if (fp == NULL) { + return -errno; + } + + rc = fprintf(fp, "%d", val); + if (rc < 0) { + fclose(fp); + return -EIO; + } + fsync(fp); + fclose(fp); + + return 0; +} + +static void mgr_gps_close(struct oc2gbts_mgr_instance *mgr) +{ + if (!mgr->gps.gps_open) + return; + + osmo_timer_del(&mgr->gps.fix_timeout); + + osmo_fd_unregister(&mgr->gps.gpsfd); + gps_close(&mgr->gps.gpsdata); + memset(&mgr->gps.gpsdata, 0, sizeof(mgr->gps.gpsdata)); + mgr->gps.gps_open = 0; +} + +static void mgr_gps_checkfix(struct oc2gbts_mgr_instance *mgr) +{ + struct gps_data_t *data = &mgr->gps.gpsdata; + + /* No 3D fix yet */ + if (data->fix.mode < MODE_3D) { + LOGP(DCALIB, LOGL_DEBUG, "Fix mode not enough: %d\n", + data->fix.mode); + return; + } + + /* Satellite used checking */ + if (data->satellites_used < 1) { + LOGP(DCALIB, LOGL_DEBUG, "Not enough satellites used: %d\n", + data->satellites_used); + return; + } + + mgr->gps.gps_fix_now = (time_t) data->fix.time; + LOGP(DCALIB, LOGL_INFO, "Got a GPS fix, satellites used: %d, timestamp: %ld\n", + data->satellites_used, mgr->gps.gps_fix_now); + osmo_timer_del(&mgr->gps.fix_timeout); + mgr_gps_close(mgr); +} + +static int mgr_gps_read(struct osmo_fd *fd, unsigned int what) +{ + int rc; + struct oc2gbts_mgr_instance *mgr = fd->data; + + rc = gps_read(&mgr->gps.gpsdata); + if (rc == -1) { + LOGP(DCALIB, LOGL_ERROR, "gpsd vanished during read.\n"); + calib_state_reset(mgr, CALIB_FAIL_GPSFIX); + return -1; + } + + if (rc > 0) + mgr_gps_checkfix(mgr); + return 0; +} + +static void mgr_gps_fix_timeout(void *_data) +{ + struct oc2gbts_mgr_instance *mgr = _data; + + LOGP(DCALIB, LOGL_ERROR, "Failed to acquire GPS fix.\n"); + mgr_gps_close(mgr); +} + +static void mgr_gps_open(struct oc2gbts_mgr_instance *mgr) +{ + int rc; + + if (mgr->gps.gps_open) + return; + + rc = gps_open("localhost", DEFAULT_GPSD_PORT, &mgr->gps.gpsdata); + if (rc != 0) { + LOGP(DCALIB, LOGL_ERROR, "Failed to connect to GPS %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_GPSFIX); + return; + } + + mgr->gps.gps_open = 1; + gps_stream(&mgr->gps.gpsdata, WATCH_ENABLE, NULL); + + mgr->gps.gpsfd.data = mgr; + mgr->gps.gpsfd.cb = mgr_gps_read; + mgr->gps.gpsfd.when = BSC_FD_READ | BSC_FD_EXCEPT; + mgr->gps.gpsfd.fd = mgr->gps.gpsdata.gps_fd; + if (osmo_fd_register(&mgr->gps.gpsfd) < 0) { + LOGP(DCALIB, LOGL_ERROR, "Failed to register GPSD fd\n"); + calib_state_reset(mgr, CALIB_FAIL_GPSFIX); + } + + mgr->calib.state = CALIB_GPS_WAIT_FOR_FIX; + mgr->gps.fix_timeout.data = mgr; + mgr->gps.fix_timeout.cb = mgr_gps_fix_timeout; + osmo_timer_schedule(&mgr->gps.fix_timeout, 60, 0); + LOGP(DCALIB, LOGL_INFO, "Opened the GPSD connection waiting for fix: %d\n", + mgr->gps.gpsfd.fd); +} + +/* OC2G CTRL interface related functions */ +static void send_ctrl_cmd(struct oc2gbts_mgr_instance *mgr, struct msgb *msg) +{ + ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL); + ipa_prepend_header(msg, IPAC_PROTO_OSMO); + ipa_client_conn_send(mgr->oc2gbts_ctrl.bts_conn, msg); +} + +static void send_set_ctrl_cmd_int(struct oc2gbts_mgr_instance *mgr, const char *key, const int val) +{ + struct msgb *msg; + int ret; + + msg = msgb_alloc_headroom(1024, 128, "CTRL SET"); + ret = snprintf((char *) msg->data, 4096, "SET %u %s %d", + mgr->oc2gbts_ctrl.last_seqno++, key, val); + msg->l2h = msgb_put(msg, ret); + return send_ctrl_cmd(mgr, msg); +} + +static void send_set_ctrl_cmd(struct oc2gbts_mgr_instance *mgr, const char *key, const int val, const char *text) +{ + struct msgb *msg; + int ret; + + msg = msgb_alloc_headroom(1024, 128, "CTRL SET"); + ret = snprintf((char *) msg->data, 4096, "SET %u %s %d, %s", + mgr->oc2gbts_ctrl.last_seqno++, key, val, text); + msg->l2h = msgb_put(msg, ret); + return send_ctrl_cmd(mgr, msg); +} + +static void calib_start(struct oc2gbts_mgr_instance *mgr) +{ + int rc; + + rc = oc2gbts_clock_err_open(); + if (rc != 0) { + LOGP(DCALIB, LOGL_ERROR, "Failed to open clock error module %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_CLKERR); + return; + } + + rc = oc2gbts_clock_dac_open(); + if (rc != 0) { + LOGP(DCALIB, LOGL_ERROR, "Failed to open OCXO dac module %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_OCXODAC); + return; + } + + calib_adjust(mgr); +} +static int get_uptime(int *uptime) +{ + struct sysinfo s_info; + int rc; + rc = sysinfo(&s_info); + if(!rc) + *uptime = s_info.uptime /(60 * 60); + + return rc; +} + +static void calib_adjust(struct oc2gbts_mgr_instance *mgr) +{ + int rc; + int fault; + int error_ppt; + int accuracy_ppq; + int interval_sec; + int dac_value; + int new_dac_value; + int dac_correction; + int now = 0; + + /* Get GPS time via GPSD */ + mgr_gps_open(mgr); + + rc = oc2gbts_clock_err_get(&fault, &error_ppt, + &accuracy_ppq, &interval_sec); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to get clock error measurement %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_CLKERR); + return; + } + + /* get current up time */ + rc = get_uptime(&now); + if (rc < 0) + LOGP(DTEMP, LOGL_ERROR, "Unable to read up time hours: %d (%s)\n", rc, strerror(errno)); + + /* read last up time */ + rc = oc2gbts_par_get_uptime(tall_mgr_ctx, &mgr->gps.last_update); + if (rc < 0) + LOGP(DCALIB, LOGL_NOTICE, "Last GPS 3D fix can not read (%d). Last GPS 3D fix sets to zero\n", rc); + + if (fault) { + LOGP(DCALIB, LOGL_NOTICE, "GPS has no fix, warn_flags=0x%08x, last=%d, now=%d\n", + mgr->oc2gbts_ctrl.warn_flags, mgr->gps.last_update, now); + if (now >= mgr->gps.last_update + mgr->gps.gps_fix_limit.thresh_warn_max * 24) { + if (!(mgr->oc2gbts_ctrl.warn_flags & S_MGR_GPS_FIX_WARN_ALARM)) { + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_GPS_FIX_WARN_ALARM; + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_GPS_FIX_FAIL, "oc2g-oml-alert", "GPS 3D fix has been lost"); + + LOGP(DCALIB, LOGL_NOTICE, "GPS has no fix since the last verification, warn_flags=0x%08x, last=%d, now=%d\n", + mgr->oc2gbts_ctrl.warn_flags, mgr->gps.last_update, now); + + /* schedule LED pattern for GPS fix lost */ + mgr->alarms.gps_fix_lost = 1; + /* update LED pattern */ + select_led_pattern(mgr); + } + } else { + /* read from last GPS 3D fix timestamp */ + rc = oc2gbts_par_get_gps_fix(tall_mgr_ctx, &mgr->gps.last_gps_fix); + if (rc < 0) + LOGP(DCALIB, LOGL_NOTICE, "Last GPS 3D fix timestamp can not read (%d)\n", rc); + + if (difftime(mgr->gps.gps_fix_now, mgr->gps.last_gps_fix) > mgr->gps.gps_fix_limit.thresh_warn_max * 24 * 60 * 60) { + if (!(mgr->oc2gbts_ctrl.warn_flags & S_MGR_GPS_FIX_WARN_ALARM)) { + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_GPS_FIX_WARN_ALARM; + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_GPS_FIX_FAIL, "oc2g-oml-alert", "GPS 3D fix has been lost"); + + LOGP(DCALIB, LOGL_NOTICE, "GPS has no fix since the last known GPS fix, warn_flags=0x%08x, gps_last=%ld, gps_now=%ld\n", + mgr->oc2gbts_ctrl.warn_flags, mgr->gps.last_gps_fix, mgr->gps.gps_fix_now); + + /* schedule LED pattern for GPS fix lost */ + mgr->alarms.gps_fix_lost = 1; + /* update LED pattern */ + select_led_pattern(mgr); + } + } + } + + rc = oc2gbts_clock_err_reset(); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to reset clock error module %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_CLKERR); + return; + } + + calib_state_reset(mgr, CALIB_FAIL_GPSFIX); + return; + } + + if (!interval_sec) { + LOGP(DCALIB, LOGL_INFO, "Skipping this iteration, no integration time\n"); + calib_state_reset(mgr, CALIB_SUCCESS); + return; + } + + /* We got GPS 3D fix */ + LOGP(DCALIB, LOGL_DEBUG, "Got GPS 3D fix warn_flags=0x%08x, uptime_last=%d, uptime_now=%d, gps_last=%ld, gps_now=%ld\n", + mgr->oc2gbts_ctrl.warn_flags, mgr->gps.last_update, now, mgr->gps.last_gps_fix, mgr->gps.gps_fix_now); + + if (mgr->oc2gbts_ctrl.warn_flags & S_MGR_GPS_FIX_WARN_ALARM) { + /* Store GPS fix as soon as we send ceased alarm */ + LOGP(DCALIB, LOGL_NOTICE, "Store GPS fix as soon as we send ceased alarm last=%ld, now=%ld\n", + mgr->gps.last_gps_fix , mgr->gps.gps_fix_now); + rc = oc2gbts_par_set_gps_fix(tall_mgr_ctx, mgr->gps.gps_fix_now); + if (rc < 0) + LOGP(DCALIB, LOGL_ERROR, "Failed to store GPS 3D fix to storage %d\n", rc); + + /* Store last up time */ + rc = oc2gbts_par_set_uptime(tall_mgr_ctx, now); + if (rc < 0) + LOGP(DCALIB, LOGL_ERROR, "Failed to store uptime to storage %d\n", rc); + + mgr->gps.last_update = now; + + /* schedule LED pattern for GPS fix resume */ + mgr->alarms.gps_fix_lost = 0; + /* update LED pattern */ + select_led_pattern(mgr); + /* send ceased alarm if possible */ + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_GPS_FIX_FAIL, "oc2g-oml-ceased", "GPS 3D fix has been lost"); + mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_GPS_FIX_WARN_ALARM; + + } + /* Store GPS fix at every hour */ + if (now > mgr->gps.last_update) { + /* Store GPS fix every 60 minutes */ + LOGP(DCALIB, LOGL_INFO, "Store GPS fix every hour last=%ld, now=%ld\n", + mgr->gps.last_gps_fix , mgr->gps.gps_fix_now); + rc = oc2gbts_par_set_gps_fix(tall_mgr_ctx, mgr->gps.gps_fix_now); + if (rc < 0) + LOGP(DCALIB, LOGL_ERROR, "Failed to store GPS 3D fix to storage %d\n", rc); + + /* Store last up time every 60 minutes */ + rc = oc2gbts_par_set_uptime(tall_mgr_ctx, now); + if (rc < 0) + LOGP(DCALIB, LOGL_ERROR, "Failed to store uptime to storage %d\n", rc); + + /* update last uptime */ + mgr->gps.last_update = now; + } + + rc = oc2gbts_clock_dac_get(&dac_value); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to get OCXO dac value %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_OCXODAC); + return; + } + + /* Set OCXO initial dac value */ + if (ocxodac_saved_value < 0) + ocxodac_saved_value = dac_value; + + LOGP(DCALIB, LOGL_INFO, + "Calibration ERR(%f PPB) ACC(%f PPB) INT(%d) DAC(%d)\n", + error_ppt / 1000., accuracy_ppq / 1000000., interval_sec, dac_value); + + /* 1 unit of correction equal about 0.5 - 1 PPB correction */ + dac_correction = (int)(-error_ppt * 0.0015); + new_dac_value = dac_value + dac_correction; + + if (new_dac_value > 4095) + new_dac_value = 4095; + else if (new_dac_value < 0) + new_dac_value = 0; + + /* We have a fix, make sure the measured error is + meaningful (10 times the accuracy) */ + if ((new_dac_value != dac_value) && ((100l * abs(error_ppt)) > accuracy_ppq)) { + + LOGP(DCALIB, LOGL_INFO, + "Going to apply %d as new clock setting.\n", + new_dac_value); + + rc = oc2gbts_clock_dac_set(new_dac_value); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to set OCXO dac value %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_OCXODAC); + return; + } + rc = oc2gbts_clock_err_reset(); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to reset clock error module %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_CLKERR); + return; + } + } + /* New conditions to store DAC value: + * - Resolution accuracy less or equal than 0.01PPB (or 10000 PPQ) + * - Error less or equal than 2PPB (or 2000PPT) + * - Solution different than the last one */ + else if (accuracy_ppq <= 10000) { + if((dac_value != ocxodac_saved_value) && (abs(error_ppt) < 2000)) { + LOGP(DCALIB, LOGL_INFO, "Saving OCXO DAC value to memory... val = %d\n", dac_value); + rc = oc2gbts_clock_dac_save(); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to save OCXO dac value %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_OCXODAC); + } else { + ocxodac_saved_value = dac_value; + } + } + + rc = oc2gbts_clock_err_reset(); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to reset clock error module %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_CLKERR); + } + } + + calib_state_reset(mgr, CALIB_SUCCESS); + return; +} + +static void calib_close(struct oc2gbts_mgr_instance *mgr) +{ + oc2gbts_clock_err_close(); + oc2gbts_clock_dac_close(); +} + +static void calib_state_reset(struct oc2gbts_mgr_instance *mgr, int outcome) +{ + if (mgr->calib.calib_from_loop) { + /* + * In case of success calibrate in two hours again + * and in case of a failure in some minutes. + * + * TODO NTQ: Select timeout based on last error and accuracy + */ + int timeout = 60; + //int timeout = 2 * 60 * 60; + //if (outcome != CALIB_SUCESS) } + // timeout = 5 * 60; + //} + + mgr->calib.calib_timeout.data = mgr; + mgr->calib.calib_timeout.cb = calib_loop_run; + osmo_timer_schedule(&mgr->calib.calib_timeout, timeout, 0); + /* TODO: do we want to notify if we got a calibration error, like no gps fix? */ + oc2gbts_swd_event(mgr, SWD_CHECK_CALIB); + } + + mgr->calib.state = CALIB_INITIAL; + calib_close(mgr); +} + +static int calib_run(struct oc2gbts_mgr_instance *mgr, int from_loop) +{ + if (mgr->calib.state != CALIB_INITIAL) { + LOGP(DCALIB, LOGL_ERROR, "Calib is already in progress.\n"); + return -1; + } + + /* Validates if we have a bts connection */ + if (mgr->oc2gbts_ctrl.is_up) { + LOGP(DCALIB, LOGL_DEBUG, "Bts connection is up.\n"); + oc2gbts_swd_event(mgr, SWD_CHECK_BTS_CONNECTION); + } + + mgr->calib.calib_from_loop = from_loop; + + /* From now on everything will be handled from the failure */ + mgr->calib.state = CALIB_IN_PROGRESS; + calib_start(mgr); + return 0; +} + +static void calib_loop_run(void *_data) +{ + int rc; + struct oc2gbts_mgr_instance *mgr = _data; + + LOGP(DCALIB, LOGL_INFO, "Going to calibrate the system.\n"); + rc = calib_run(mgr, 1); + if (rc != 0) { + calib_state_reset(mgr, CALIB_FAIL_START); + } +} + +int oc2gbts_mgr_calib_run(struct oc2gbts_mgr_instance *mgr) +{ + return calib_run(mgr, 0); +} + +static void schedule_bts_connect(struct oc2gbts_mgr_instance *mgr) +{ + DEBUGP(DLCTRL, "Scheduling BTS connect\n"); + osmo_timer_schedule(&mgr->oc2gbts_ctrl.recon_timer, 1, 0); +} + +/* link to BSC has gone up or down */ +static void bts_updown_cb(struct ipa_client_conn *link, int up) +{ + struct oc2gbts_mgr_instance *mgr = link->data; + + LOGP(DLCTRL, LOGL_INFO, "BTS connection %s\n", up ? "up" : "down"); + + if (up) { + mgr->oc2gbts_ctrl.is_up = 1; + mgr->oc2gbts_ctrl.last_seqno = 0; + /* handle any pending alarm */ + handle_alert_actions(mgr); + handle_warn_actions(mgr); + } else { + mgr->oc2gbts_ctrl.is_up = 0; + schedule_bts_connect(mgr); + } + +} + +/* BTS re-connect timer call-back */ +static void bts_recon_timer_cb(void *data) +{ + int rc; + struct oc2gbts_mgr_instance *mgr = data; + + /* update LED pattern */ + select_led_pattern(mgr); + + /* The connection failures are to be expected during boot */ + mgr->oc2gbts_ctrl.bts_conn->ofd->when |= BSC_FD_WRITE; + rc = ipa_client_conn_open(mgr->oc2gbts_ctrl.bts_conn); + if (rc < 0) { + LOGP(DLCTRL, LOGL_NOTICE, "Failed to connect to BTS.\n"); + schedule_bts_connect(mgr); + } +} + +static void oc2gbts_handle_ctrl(struct oc2gbts_mgr_instance *mgr, struct msgb *msg) +{ + struct ctrl_cmd *cmd = ctrl_cmd_parse(tall_mgr_ctx, msg); + int cause = atoi(cmd->reply); + + if (!cmd) { + LOGP(DCALIB, LOGL_ERROR, "Failed to parse command/response\n"); + return; + } + + switch (cmd->type) { + case CTRL_TYPE_GET_REPLY: + LOGP(DCALIB, LOGL_INFO, "Got GET_REPLY from BTS cause=0x%x\n", cause); + break; + case CTRL_TYPE_SET_REPLY: + LOGP(DCALIB, LOGL_INFO, "Got SET_REPLY from BTS cause=0x%x\n", cause); + break; + default: + LOGP(DCALIB, LOGL_ERROR, + "Unhandled CTRL response: %d. Resetting state\n", + cmd->type); + break; + } + + talloc_free(cmd); + return; +} + +static int bts_read_cb(struct ipa_client_conn *link, struct msgb *msg) +{ + int rc; + struct ipaccess_head *hh = (struct ipaccess_head *) msgb_l1(msg); + struct ipaccess_head_ext *hh_ext; + + LOGP(DLCTRL, LOGL_DEBUG, "Received data from BTS: %s\n", + osmo_hexdump(msgb_data(msg), msgb_length(msg))); + + /* regular message handling */ + rc = msg_verify_ipa_structure(msg); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Invalid IPA message from BTS (rc=%d)\n", rc); + goto err; + } + + switch (hh->proto) { + case IPAC_PROTO_OSMO: + hh_ext = (struct ipaccess_head_ext *) hh->data; + switch (hh_ext->proto) { + case IPAC_PROTO_EXT_CTRL: + oc2gbts_handle_ctrl(link->data, msg); + break; + default: + LOGP(DCALIB, LOGL_NOTICE, + "Unhandled osmo ID %u from BTS\n", hh_ext->proto); + }; + msgb_free(msg); + break; + default: + LOGP(DCALIB, LOGL_NOTICE, + "Unhandled stream ID %u from BTS\n", hh->proto); + msgb_free(msg); + break; + } + return 0; +err: + msgb_free(msg); + return -1; +} + +int oc2gbts_mgr_calib_init(struct oc2gbts_mgr_instance *mgr) +{ + int rc; + + /* initialize last uptime */ + mgr->gps.last_update = 0; + rc = oc2gbts_par_set_uptime(tall_mgr_ctx, mgr->gps.last_update); + if (rc < 0) + LOGP(DCALIB, LOGL_ERROR, "Failed to store uptime to storage %d\n", rc); + + /* get last GPS 3D fix timestamp */ + mgr->gps.last_gps_fix = 0; + rc = oc2gbts_par_get_gps_fix(tall_mgr_ctx, &mgr->gps.last_gps_fix); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, "Failed to get last GPS 3D fix timestamp from storage. Create it anyway %d\n", rc); + rc = oc2gbts_par_set_gps_fix(tall_mgr_ctx, mgr->gps.last_gps_fix); + if (rc < 0) + LOGP(DCALIB, LOGL_ERROR, "Failed to store initial GPS fix to storage %d\n", rc); + } + + mgr->calib.state = CALIB_INITIAL; + mgr->calib.calib_timeout.data = mgr; + mgr->calib.calib_timeout.cb = calib_loop_run; + osmo_timer_schedule(&mgr->calib.calib_timeout, 0, 0); + + return rc; +} + +int oc2gbts_mgr_control_init(struct oc2gbts_mgr_instance *mgr) +{ + mgr->oc2gbts_ctrl.bts_conn = ipa_client_conn_create(tall_mgr_ctx, NULL, 0, + "127.0.0.1", OSMO_CTRL_PORT_BTS, + bts_updown_cb, bts_read_cb, + NULL, mgr); + if (!mgr->oc2gbts_ctrl.bts_conn) { + LOGP(DLCTRL, LOGL_ERROR, "Failed to create IPA connection to BTS\n"); + return -1; + } + + mgr->oc2gbts_ctrl.recon_timer.cb = bts_recon_timer_cb; + mgr->oc2gbts_ctrl.recon_timer.data = mgr; + schedule_bts_connect(mgr); + + return 0; +} + +void oc2gbts_mgr_dispatch_alarm(struct oc2gbts_mgr_instance *mgr, const int cause, const char *key, const char *text) +{ + /* Make sure the control link is ready before sending alarm */ + if (mgr->oc2gbts_ctrl.bts_conn->state != IPA_CLIENT_LINK_STATE_CONNECTED) { + LOGP(DLCTRL, LOGL_NOTICE, "MGR losts connection to BTS.\n"); + LOGP(DLCTRL, LOGL_NOTICE, "MGR drops an alert cause=0x%x, text=%s to BTS\n", cause, text); + return; + } + + LOGP(DLCTRL, LOGL_DEBUG, "MGR sends an alert cause=0x%x, text=%s to BTS\n", cause, text); + send_set_ctrl_cmd(mgr, key, cause, text); + return; +} + + diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c new file mode 100644 index 000000000..db67caf2a --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c @@ -0,0 +1,208 @@ +/* NetworkListen for NuRAN OC-2G BTS management daemon */ + +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * sysmobts_mgr_nl.c + * (C) 2014 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include "misc/oc2gbts_mgr.h" +#include "misc/oc2gbts_misc.h" +#include "misc/oc2gbts_nl.h" +#include "misc/oc2gbts_par.h" +#include "misc/oc2gbts_bid.h" + +#include + +#include + +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +#define ETH0_ADDR_SYSFS "/var/oc2g/net/eth0/address" + +static struct osmo_fd nl_fd; + +/* + * The TLV structure in IPA messages in UDP packages is a bit + * weird. First the header appears to have an extra NULL byte + * and second the L16 of the L16TV needs to include +1 for the + * tag. The default msgb/tlv and libosmo-abis routines do not + * provide this. + */ + +static void ipaccess_prepend_header_quirk(struct msgb *msg, int proto) +{ + struct ipaccess_head *hh; + + /* prepend the ip.access header */ + hh = (struct ipaccess_head *) msgb_push(msg, sizeof(*hh) + 1); + hh->len = htons(msg->len - sizeof(*hh) - 1); + hh->proto = proto; +} + +static void quirk_l16tv_put(struct msgb *msg, uint16_t len, uint8_t tag, + const uint8_t *val) +{ + uint8_t *buf = msgb_put(msg, len + 2 + 1); + + *buf++ = (len + 1) >> 8; + *buf++ = (len + 1) & 0xff; + *buf++ = tag; + memcpy(buf, val, len); +} + +/* + * We don't look at the content of the request yet and lie + * about most of the responses. + */ +static void respond_to(struct sockaddr_in *src, struct osmo_fd *fd, + uint8_t *data, size_t len) +{ + static int fetched_info = 0; + static char mac_str[20] = {0, }; + static char model_name[64] = {0, }; + static char ser_str[20] = {0, }; + + struct sockaddr_in loc_addr; + int rc; + char loc_ip[INET_ADDRSTRLEN]; + struct msgb *msg = msgb_alloc_headroom(512, 128, "ipa get response"); + if (!msg) { + LOGP(DFIND, LOGL_ERROR, "Failed to allocate msgb\n"); + return; + } + + if (!fetched_info) { + int fd_eth; + int serno; + int model; + char rev_maj, rev_min; + + /* fetch the MAC */ + fd_eth = open(ETH0_ADDR_SYSFS, O_RDONLY); + if (fd_eth >= 0) { + read(fd_eth, mac_str, sizeof(mac_str)-1); + mac_str[sizeof(mac_str)-1] = '\0'; + close(fd_eth); + } + + /* fetch the serial number */ + oc2gbts_par_get_int(OC2GBTS_PAR_SERNR, &serno); + snprintf(ser_str, sizeof(ser_str), "%d", serno); + + /* fetch the model and trx number */ + snprintf(model_name, sizeof(model_name), "OC-2G BTS"); + + oc2gbts_rev_get(&rev_maj, &rev_min); + snprintf(model_name, sizeof(model_name), "%s Rev %c.%c", + model_name, rev_maj, rev_min); + + model = oc2gbts_model_get(); + if (model >= 0) { + snprintf(model_name, sizeof(model_name), "%s (%05X)", + model_name, model); + } + fetched_info = 1; + } + + if (source_for_dest(&src->sin_addr, &loc_addr.sin_addr) != 0) { + LOGP(DFIND, LOGL_ERROR, "Failed to determine local source\n"); + return; + } + + msgb_put_u8(msg, IPAC_MSGT_ID_RESP); + + /* append MAC addr */ + quirk_l16tv_put(msg, strlen(mac_str) + 1, IPAC_IDTAG_MACADDR, (uint8_t *) mac_str); + + /* append ip address */ + inet_ntop(AF_INET, &loc_addr.sin_addr, loc_ip, sizeof(loc_ip)); + quirk_l16tv_put(msg, strlen(loc_ip) + 1, IPAC_IDTAG_IPADDR, (uint8_t *) loc_ip); + + /* append the serial number */ + quirk_l16tv_put(msg, strlen(ser_str) + 1, IPAC_IDTAG_SERNR, (uint8_t *) ser_str); + + /* abuse some flags */ + quirk_l16tv_put(msg, strlen(model_name) + 1, IPAC_IDTAG_UNIT, (uint8_t *) model_name); + + /* ip.access nanoBTS would reply to port==3006 */ + ipaccess_prepend_header_quirk(msg, IPAC_PROTO_IPACCESS); + rc = sendto(fd->fd, msg->data, msg->len, 0, (struct sockaddr *)src, sizeof(*src)); + if (rc != msg->len) + LOGP(DFIND, LOGL_ERROR, + "Failed to send with rc(%d) errno(%d)\n", rc, errno); +} + +static int ipaccess_bcast(struct osmo_fd *fd, unsigned int what) +{ + uint8_t data[2048]; + char src[INET_ADDRSTRLEN]; + struct sockaddr_in addr = {}; + socklen_t len = sizeof(addr); + int rc; + + rc = recvfrom(fd->fd, data, sizeof(data), 0, + (struct sockaddr *) &addr, &len); + if (rc <= 0) { + LOGP(DFIND, LOGL_ERROR, + "Failed to read from socket errno(%d)\n", errno); + return -1; + } + + LOGP(DFIND, LOGL_DEBUG, + "Received request from: %s size %d\n", + inet_ntop(AF_INET, &addr.sin_addr, src, sizeof(src)), rc); + + if (rc < 6) + return 0; + + if (data[2] != IPAC_PROTO_IPACCESS || data[4] != IPAC_MSGT_ID_GET) + return 0; + + respond_to(&addr, fd, data + 6, rc - 6); + return 0; +} + +int oc2gbts_mgr_nl_init(void) +{ + int rc; + + nl_fd.cb = ipaccess_bcast; + rc = osmo_sock_init_ofd(&nl_fd, AF_INET, SOCK_DGRAM, IPPROTO_UDP, + "0.0.0.0", 3006, OSMO_SOCK_F_BIND); + if (rc < 0) { + perror("Socket creation"); + return -1; + } + + return 0; +} diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_temp.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_temp.c new file mode 100644 index 000000000..f9efd9cd5 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_temp.c @@ -0,0 +1,980 @@ +/* Temperature control for NuRAN OC-2G BTS management daemon */ + +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * sysmobts_mgr_temp.c + * (C) 2014 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include "misc/oc2gbts_mgr.h" +#include "misc/oc2gbts_misc.h" +#include "misc/oc2gbts_temp.h" +#include "misc/oc2gbts_power.h" +#include "misc/oc2gbts_led.h" +#include "misc/oc2gbts_swd.h" +#include "misc/oc2gbts_bid.h" +#include "limits.h" + +#include + +#include +#include +#include + +struct oc2gbts_mgr_instance *s_mgr; +static struct osmo_timer_list sensor_ctrl_timer; + +static const struct value_string state_names[] = { + { STATE_NORMAL, "NORMAL" }, + { STATE_WARNING_HYST, "WARNING (HYST)" }, + { STATE_WARNING, "WARNING" }, + { STATE_CRITICAL, "CRITICAL" }, + { 0, NULL } +}; + +/* private function prototype */ +static void sensor_ctrl_check(struct oc2gbts_mgr_instance *mgr); + +const char *oc2gbts_mgr_sensor_get_state(enum oc2gbts_sensor_state state) +{ + return get_value_string(state_names, state); +} + +static int next_state(enum oc2gbts_sensor_state current_state, int critical, int warning) +{ + int next_state = -1; + switch (current_state) { + case STATE_NORMAL: + if (critical) + next_state = STATE_CRITICAL; + else if (warning) + next_state = STATE_WARNING; + break; + case STATE_WARNING_HYST: + if (critical) + next_state = STATE_CRITICAL; + else if (warning) + next_state = STATE_WARNING; + else + next_state = STATE_NORMAL; + break; + case STATE_WARNING: + if (critical) + next_state = STATE_CRITICAL; + else if (!warning) + next_state = STATE_WARNING_HYST; + break; + case STATE_CRITICAL: + if (!critical && !warning) + next_state = STATE_WARNING; + break; + }; + + return next_state; +} + +static void handle_normal_actions(int actions) +{ + /* switch on the PA */ + if (actions & SENSOR_ACT_NORM_PA_ON) { + if (oc2gbts_power_set(OC2GBTS_POWER_PA, 1) != 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to switch on the PA\n"); + } else { + LOGP(DTEMP, LOGL_INFO, + "Switched on the PA as normal action.\n"); + } + } + + if (actions & SENSOR_ACT_NORM_BTS_SRV_ON) { + LOGP(DTEMP, LOGL_INFO, + "Going to switch on the BTS service\n"); + /* + * TODO: use/create something like nspawn that serializes + * and used SIGCHLD/waitpid to pick up the dead processes + * without invoking shell. + */ + system("/bin/systemctl start osmo-bts.service"); + } +} + +static void handle_actions(int actions) +{ + /* switch off the PA */ + if (actions & SENSOR_ACT_PA_OFF) { + if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) { + if (oc2gbts_power_set(OC2GBTS_POWER_PA, 0) != 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to switch off the PA. Stop BTS?\n"); + } else { + LOGP(DTEMP, LOGL_NOTICE, + "Switched off the PA due temperature.\n"); + } + } + } + + if (actions & SENSOR_ACT_BTS_SRV_OFF) { + LOGP(DTEMP, LOGL_NOTICE, + "Going to switch off the BTS service\n"); + /* + * TODO: use/create something like nspawn that serializes + * and used SIGCHLD/waitpid to pick up the dead processes + * without invoking shell. + */ + system("/bin/systemctl stop osmo-bts.service"); + } +} + +void handle_ceased_actions(struct oc2gbts_mgr_instance *mgr) +{ int i; + uint32_t cause; + + if (!mgr->oc2gbts_ctrl.is_up) + return; + + LOGP(DTEMP, LOGL_DEBUG, "handle_ceased_actions in state %s, warn_flags=0x%x, crit_flags=0x%x\n", + oc2gbts_mgr_sensor_get_state(mgr->state.state), + mgr->oc2gbts_ctrl.warn_flags, + mgr->oc2gbts_ctrl.crit_flags); + + for (i = 0; i < 32; i++) { + cause = 1 << i; + /* clear warning flag without sending ceased alarm */ + if (mgr->oc2gbts_ctrl.warn_flags & cause) + mgr->oc2gbts_ctrl.warn_flags &= ~cause; + + /* clear warning flag with sending ceased alarm */ + if (mgr->oc2gbts_ctrl.crit_flags & cause) { + /* clear associated flag */ + mgr->oc2gbts_ctrl.crit_flags &= ~cause; + /* dispatch ceased alarm */ + switch (cause) { + case S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_SUPPLY_MAX_FAIL, "oc2g-oml-ceased", "Main power supply temperature is too high"); + break; + case S_MGR_TEMP_SOC_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_SOC_MAX_FAIL, "oc2g-oml-ceased", "SoC temperature is too high"); + break; + case S_MGR_TEMP_FPGA_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_FPGA_MAX_FAIL, "oc2g-oml-ceased", "FPGA temperature is too high"); + break; + case S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_RMS_DET_MAX_FAIL, "oc2g-oml-ceased", "RMS detector temperature is too high"); + break; + case S_MGR_TEMP_OCXO_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_OCXO_MAX_FAIL, "oc2g-oml-ceased", "OCXO temperature is too high"); + break; + case S_MGR_TEMP_TRX_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_TRX_MAX_FAIL, "oc2g-oml-ceased", "TRX temperature is too high"); + break; + case S_MGR_TEMP_PA_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_PA_MAX_FAIL, "oc2g-oml-ceased", "PA temperature is too high"); + break; + case S_MGR_SUPPLY_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_SUPPLY_MAX_FAIL, "oc2g-oml-ceased", "Power supply voltage is too high"); + break; + case S_MGR_SUPPLY_CRIT_MIN_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_SUPPLY_MIN_FAIL, "oc2g-oml-ceased", "Power supply voltage is too low"); + break; + case S_MGR_VSWR_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_VSWR_MAX_FAIL, "oc2g-oml-ceased", "VSWR is too high"); + break; + case S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_PWR_SUPPLY_MAX_FAIL, "oc2g-oml-ceased", "Power supply consumption is too high"); + break; + case S_MGR_PWR_PA_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_PWR_PA_MAX_FAIL, "oc2g-oml-ceased", "PA power consumption is too high"); + break; + default: + break; + } + } + } + return; +} + +void handle_alert_actions(struct oc2gbts_mgr_instance *mgr) +{ int i; + uint32_t cause; + + if (!mgr->oc2gbts_ctrl.is_up) + return; + + LOGP(DTEMP, LOGL_DEBUG, "handle_alert_actions in state %s, crit_flags=0x%x\n", + oc2gbts_mgr_sensor_get_state(mgr->state.state), + mgr->oc2gbts_ctrl.crit_flags); + + for (i = 0; i < 32; i++) { + cause = 1 << i; + if (mgr->oc2gbts_ctrl.crit_flags & cause) { + switch(cause) { + case S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_SUPPLY_MAX_FAIL, "oc2g-oml-alert", "Main power supply temperature is too high"); + break; + case S_MGR_TEMP_SOC_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_SOC_MAX_FAIL, "oc2g-oml-alert", "SoC temperature is too high"); + break; + case S_MGR_TEMP_FPGA_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_FPGA_MAX_FAIL, "oc2g-oml-alert", "FPGA temperature is too high"); + break; + case S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_RMS_DET_MAX_FAIL, "oc2g-oml-alert", "RMS detector temperature is too high"); + break; + case S_MGR_TEMP_OCXO_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_OCXO_MAX_FAIL, "oc2g-oml-alert", "OCXO temperature is too high"); + break; + case S_MGR_TEMP_TRX_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_TRX_MAX_FAIL, "oc2g-oml-alert", "TRX temperature is too high"); + break; + case S_MGR_TEMP_PA_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_PA_MAX_FAIL, "oc2g-oml-alert", "PA temperature is too high"); + break; + case S_MGR_SUPPLY_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_SUPPLY_MAX_FAIL, "oc2g-oml-alert", "Power supply voltage is too high"); + break; + case S_MGR_SUPPLY_CRIT_MIN_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_SUPPLY_MIN_FAIL, "oc2g-oml-alert", "Power supply voltage is too low"); + break; + case S_MGR_VSWR_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_VSWR_MAX_FAIL, "oc2g-oml-alert", "VSWR is too high"); + break; + case S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_PWR_SUPPLY_MAX_FAIL, "oc2g-oml-alert", "Power supply consumption is too high"); + break; + case S_MGR_PWR_PA_CRIT_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_PWR_PA_MAX_FAIL, "oc2g-oml-alert", "PA power consumption is too high"); + break; + default: + break; + } + } + } + return; +} + +void handle_warn_actions(struct oc2gbts_mgr_instance *mgr) +{ int i; + uint32_t cause; + + if (!mgr->oc2gbts_ctrl.is_up) + return; + + LOGP(DTEMP, LOGL_DEBUG, "handle_warn_actions in state %s, warn_flags=0x%x\n", + oc2gbts_mgr_sensor_get_state(mgr->state.state), + mgr->oc2gbts_ctrl.warn_flags); + + for (i = 0; i < 32; i++) { + cause = 1 << i; + if (mgr->oc2gbts_ctrl.warn_flags & cause) { + switch(cause) { + case S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_SUPPLY_HIGH_FAIL, "oc2g-oml-alert", "Main power supply temperature is high"); + break; + case S_MGR_TEMP_SUPPLY_WARN_MIN_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_SUPPLY_LOW_FAIL, "oc2g-oml-alert", "Main power supply temperature is low"); + break; + case S_MGR_TEMP_SOC_WARN_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_SOC_HIGH_FAIL, "oc2g-oml-alert", "SoC temperature is high"); + break; + case S_MGR_TEMP_SOC_WARN_MIN_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_SOC_LOW_FAIL, "oc2g-oml-alert", "SoC temperature is low"); + break; + case S_MGR_TEMP_FPGA_WARN_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_FPGA_HIGH_FAIL, "oc2g-oml-alert", "FPGA temperature is high"); + break; + case S_MGR_TEMP_FPGA_WARN_MIN_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_FPGA_LOW_FAIL, "oc2g-oml-alert", "FPGA temperature is low"); + break; + case S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_RMS_DET_HIGH_FAIL, "oc2g-oml-alert", "RMS detector temperature is high"); + break; + case S_MGR_TEMP_RMS_DET_WARN_MIN_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_RMS_DET_LOW_FAIL, "oc2g-oml-alert", "RMS detector temperature is low"); + break; + case S_MGR_TEMP_OCXO_WARN_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_OCXO_HIGH_FAIL, "oc2g-oml-alert", "OCXO temperature is high"); + break; + case S_MGR_TEMP_OCXO_WARN_MIN_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_OCXO_LOW_FAIL, "oc2g-oml-alert", "OCXO temperature is low"); + break; + case S_MGR_TEMP_TRX_WARN_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_TRX_HIGH_FAIL, "oc2g-oml-alert", "TRX temperature is high"); + break; + case S_MGR_TEMP_TRX_WARN_MIN_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_TRX_LOW_FAIL, "oc2g-oml-alert", "TRX temperature is low"); + break; + case S_MGR_TEMP_PA_WARN_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_PA_HIGH_FAIL, "oc2g-oml-alert", "PA temperature is high"); + break; + case S_MGR_TEMP_PA_WARN_MIN_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_PA_LOW_FAIL, "oc2g-oml-alert", "PA temperature is low"); + break; + case S_MGR_SUPPLY_WARN_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_SUPPLY_HIGH_FAIL, "oc2g-oml-alert", "Power supply voltage is high"); + break; + case S_MGR_SUPPLY_WARN_MIN_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_SUPPLY_LOW_FAIL, "oc2g-oml-alert", "Power supply voltage is low"); + break; + case S_MGR_VSWR_WARN_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_VSWR_HIGH_FAIL, "oc2g-oml-alert", "VSWR is high"); + break; + case S_MGR_PWR_SUPPLY_WARN_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_PWR_SUPPLY_HIGH_FAIL, "oc2g-oml-alert", "Power supply consumption is high"); + break; + case S_MGR_PWR_PA_WARN_MAX_ALARM: + oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_PWR_PA_HIGH_FAIL, "oc2g-oml-alert", "PA power consumption is high"); + break; + default: + break; + } + } + } + return; +} + +/** + * Go back to normal! Depending on the configuration execute the normal + * actions that could (start to) undo everything we did in the other + * states. What is still missing is the power increase/decrease depending + * on the state. E.g. starting from WARNING_HYST we might want to slowly + * ramp up the output power again. + */ +static void execute_normal_act(struct oc2gbts_mgr_instance *manager) +{ + LOGP(DTEMP, LOGL_NOTICE, "System is back to normal state.\n"); + handle_ceased_actions(manager); + handle_normal_actions(manager->state.action_norm); +} + +static void execute_warning_act(struct oc2gbts_mgr_instance *manager) +{ + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning state.\n"); + handle_warn_actions(manager); + handle_actions(manager->state.action_warn); +} + +/* Preventive timer call-back */ +static void preventive_timer_cb(void *_data) +{ + struct oc2gbts_mgr_instance *mgr = _data; + + /* Delete current preventive timer if possible */ + osmo_timer_del(&mgr->alarms.preventive_timer); + + LOGP(DTEMP, LOGL_DEBUG, "Preventive timer expired in %d sec, retry=%d\n", + mgr->alarms.preventive_duration, + mgr->alarms.preventive_retry); + + /* Turn on PA and clear action flag */ + if (mgr->state.action_comb & SENSOR_ACT_PA_OFF) { + mgr->state.action_comb &= ~SENSOR_ACT_PA_OFF; + + if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) { + if (oc2gbts_power_set(OC2GBTS_POWER_PA, 1)) + LOGP(DTEMP, LOGL_ERROR, "Failed to switch on the PA\n"); + else + LOGP(DTEMP, LOGL_DEBUG, "Re-enable PA after preventive timer expired in %d sec\n", + mgr->alarms.preventive_duration); + } + } + + /* restart check sensor timer */ + osmo_timer_del(&sensor_ctrl_timer); + osmo_timer_schedule(&sensor_ctrl_timer, OC2GBTS_SENSOR_TIMER_DURATION, 0); + + return; + +} + +static void execute_preventive_act(struct oc2gbts_mgr_instance *manager) +{ + struct oc2gbts_preventive_list *prevent_list, *prevent_list2; + + /* update LED pattern */ + select_led_pattern(manager); + + /* do nothing if the preventive action list is empty */ + if (llist_empty(&manager->alarms.list)) + return; + + llist_for_each_entry_safe(prevent_list, prevent_list2, &manager->alarms.list, list) { + /* Delete the timer in list and perform action*/ + if (prevent_list) { + /* Delete current preventive timer if possible */ + osmo_timer_del(&manager->alarms.preventive_timer); + + /* Start/restart preventive timer */ + if (prevent_list->param.sleep_sec) { + manager->alarms.preventive_timer.cb = preventive_timer_cb; + manager->alarms.preventive_timer.data = manager; + osmo_timer_schedule(&manager->alarms.preventive_timer, prevent_list->param.sleep_sec, 0); + + LOGP(DTEMP, LOGL_DEBUG,"Preventive timer scheduled for %d sec, preventive flags=0x%x\n", + prevent_list->param.sleep_sec, + prevent_list->action_flag); + } + /* Update active flags */ + manager->state.action_comb |= prevent_list->action_flag; + + /* Turn off PA */ + if (manager->state.action_comb & SENSOR_ACT_PA_OFF) { + if (oc2gbts_power_set(OC2GBTS_POWER_PA, 0)) + LOGP(DTEMP, LOGL_ERROR, "Failed to switch off the PA\n"); + } + + /* Delete this preventive entry */ + llist_del(&prevent_list->list); + talloc_free(prevent_list); + LOGP(DTEMP, LOGL_DEBUG,"Deleted preventive entry from list, entries left=%d\n", + llist_count(&manager->alarms.list)); + + /* stay in last state is preventive active has exceed maximum number of retries */ + if (manager->alarms.preventive_retry > OC2GBTS_PREVENT_RETRY) + LOGP(DTEMP, LOGL_NOTICE, "Maximum number of preventive active exceed\n"); + else + /* increase retry counter */ + manager->alarms.preventive_retry++; + } + } + return; +} + +static void execute_critical_act(struct oc2gbts_mgr_instance *manager) +{ + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical warning.\n"); + handle_alert_actions(manager); + handle_actions(manager->state.action_crit); + +} + +static void oc2gbts_mgr_sensor_handle(struct oc2gbts_mgr_instance *manager, + int critical, int warning) +{ + int new_state = next_state(manager->state.state, critical, warning); + + /* run preventive action if it is possible */ + execute_preventive_act(manager); + + /* Nothing changed */ + if (new_state < 0) + return; + LOGP(DTEMP, LOGL_INFO, "Moving from state %s to %s.\n", + get_value_string(state_names, manager->state.state), + get_value_string(state_names, new_state)); + manager->state.state = new_state; + switch (manager->state.state) { + case STATE_NORMAL: + execute_normal_act(manager); + /* reset alarms */ + manager->alarms.temp_high = 0; + manager->alarms.temp_max = 0; + manager->alarms.vswr_high = 0; + manager->alarms.vswr_max = 0; + manager->alarms.supply_low = 0; + manager->alarms.supply_min = 0; + manager->alarms.supply_pwr_high = 0; + manager->alarms.supply_pwr_max = 0; + manager->alarms.pa_pwr_max = 0; + manager->alarms.pa_pwr_high = 0; + manager->state.action_comb = 0; + manager->alarms.preventive_retry = 0; + /* update LED pattern */ + select_led_pattern(manager); + break; + case STATE_WARNING_HYST: + /* do nothing? Maybe start to increase transmit power? */ + break; + case STATE_WARNING: + execute_warning_act(manager); + /* update LED pattern */ + select_led_pattern(manager); + break; + case STATE_CRITICAL: + execute_critical_act(manager); + /* update LED pattern */ + select_led_pattern(manager); + break; + }; +} + +static void schedule_preventive_action(struct oc2gbts_mgr_instance *mgr, int action, int duration) +{ + struct oc2gbts_preventive_list *prevent_list; + + /* add to pending list */ + prevent_list = talloc_zero(tall_mgr_ctx, struct oc2gbts_preventive_list); + if (prevent_list) { + prevent_list->action_flag = action; + prevent_list->param.sleep_sec = duration; + prevent_list->param.sleep_usec = 0; + llist_add_tail(&prevent_list->list, &mgr->alarms.list); + LOGP(DTEMP, LOGL_DEBUG,"Added preventive action to list, duration=%d sec, total entries=%d\n", + prevent_list->param.sleep_sec, + llist_count(&mgr->alarms.list)); + } + return; +} + +static void sensor_ctrl_check(struct oc2gbts_mgr_instance *mgr) +{ + int rc; + int temp, volt, vswr, power = 0; + int warn_thresh_passed = 0; + int crit_thresh_passed = 0; + int action = 0; + + LOGP(DTEMP, LOGL_INFO, "Going to check the temperature.\n"); + + /* Read the current supply temperature */ + rc = oc2gbts_temp_get(OC2GBTS_TEMP_SUPPLY, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_NOTICE, + "Failed to read the supply temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.supply_temp_limit.thresh_warn_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because supply temperature is over %d\n", mgr->temp.supply_temp_limit.thresh_warn_max); + warn_thresh_passed = 1; + mgr->alarms.temp_high = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + if (temp < mgr->temp.supply_temp_limit.thresh_warn_min){ + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because supply temperature is under %d\n", mgr->temp.supply_temp_limit.thresh_warn_min); + warn_thresh_passed = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_SUPPLY_WARN_MIN_ALARM; + } + if (temp > mgr->temp.supply_temp_limit.thresh_crit_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because supply temperature is over %d\n", mgr->temp.supply_temp_limit.thresh_crit_max); + crit_thresh_passed = 1; + mgr->alarms.temp_max = 1; + mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM; + mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM; + action = SENSOR_ACT_PA_OFF; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + LOGP(DTEMP, LOGL_INFO, "Supply temperature is: %d\n", temp); + } + + /* Read the current SoC temperature */ + rc = oc2gbts_temp_get(OC2GBTS_TEMP_SOC, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_NOTICE, + "Failed to read the SoC temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.soc_temp_limit.thresh_warn_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because SoC temperature is over %d\n", mgr->temp.soc_temp_limit.thresh_warn_max); + warn_thresh_passed = 1; + mgr->alarms.temp_high = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_SOC_WARN_MAX_ALARM; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + if (temp < mgr->temp.soc_temp_limit.thresh_warn_min){ + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because SoC temperature is under %d\n", mgr->temp.soc_temp_limit.thresh_warn_min); + warn_thresh_passed = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_SOC_WARN_MIN_ALARM; + } + if (temp > mgr->temp.soc_temp_limit.thresh_crit_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because SoC temperature is over %d\n", mgr->temp.soc_temp_limit.thresh_crit_max); + crit_thresh_passed = 1; + mgr->alarms.temp_max = 1; + mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_SOC_CRIT_MAX_ALARM; + mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_SOC_WARN_MAX_ALARM; + action = SENSOR_ACT_PA_OFF; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + LOGP(DTEMP, LOGL_INFO, "SoC temperature is: %d\n", temp); + } + + /* Read the current fpga temperature */ + rc = oc2gbts_temp_get(OC2GBTS_TEMP_FPGA, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_NOTICE, + "Failed to read the fpga temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.fpga_temp_limit.thresh_warn_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because fpga temperature is over %d\n", mgr->temp.fpga_temp_limit.thresh_warn_max); + warn_thresh_passed = 1; + mgr->alarms.temp_high = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_FPGA_WARN_MAX_ALARM; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + if (temp < mgr->temp.fpga_temp_limit.thresh_warn_min) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because fpga temperature is under %d\n", mgr->temp.fpga_temp_limit.thresh_warn_min); + warn_thresh_passed = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_FPGA_WARN_MIN_ALARM; + } + if (temp > mgr->temp.fpga_temp_limit.thresh_crit_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because fpga temperature is over %d\n", mgr->temp.fpga_temp_limit.thresh_crit_max); + crit_thresh_passed = 1; + mgr->alarms.temp_max = 1; + mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_FPGA_CRIT_MAX_ALARM; + mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_FPGA_WARN_MAX_ALARM; + action = SENSOR_ACT_PA_OFF; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + LOGP(DTEMP, LOGL_INFO, "FPGA temperature is: %d\n", temp); + } + + if (oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) || oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) { + /* Read the current RMS detector temperature */ + rc = oc2gbts_temp_get(OC2GBTS_TEMP_RMSDET, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_NOTICE, + "Failed to read the RMS detector temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.rmsdet_temp_limit.thresh_warn_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because RMS detector temperature is over %d\n", mgr->temp.rmsdet_temp_limit.thresh_warn_max); + warn_thresh_passed = 1; + mgr->alarms.temp_high = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + if (temp < mgr->temp.rmsdet_temp_limit.thresh_warn_min) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because RMS detector temperature is under %d\n", mgr->temp.rmsdet_temp_limit.thresh_warn_min); + warn_thresh_passed = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_RMS_DET_WARN_MIN_ALARM; + } + if (temp > mgr->temp.rmsdet_temp_limit.thresh_crit_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because RMS detector temperature is over %d\n", mgr->temp.rmsdet_temp_limit.thresh_crit_max); + crit_thresh_passed = 1; + mgr->alarms.temp_max = 1; + mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM; + mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM; + action = SENSOR_ACT_PA_OFF; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + LOGP(DTEMP, LOGL_INFO, "RMS detector temperature is: %d\n", temp); + } + } + + /* Read the current OCXO temperature */ + rc = oc2gbts_temp_get(OC2GBTS_TEMP_OCXO, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_NOTICE, + "Failed to read the OCXO temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.ocxo_temp_limit.thresh_warn_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because OCXO temperature is over %d\n", mgr->temp.ocxo_temp_limit.thresh_warn_max); + warn_thresh_passed = 1; + mgr->alarms.temp_high = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_OCXO_WARN_MAX_ALARM; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + if (temp < mgr->temp.ocxo_temp_limit.thresh_warn_min) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because OCXO temperature is under %d\n", mgr->temp.ocxo_temp_limit.thresh_warn_min); + warn_thresh_passed = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_OCXO_WARN_MIN_ALARM; + } + if (temp > mgr->temp.ocxo_temp_limit.thresh_crit_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because OCXO temperature is over %d\n", mgr->temp.ocxo_temp_limit.thresh_crit_max); + crit_thresh_passed = 1; + mgr->alarms.temp_max = 1; + mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_OCXO_CRIT_MAX_ALARM; + mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_OCXO_WARN_MAX_ALARM; + action = SENSOR_ACT_PA_OFF; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + LOGP(DTEMP, LOGL_INFO, "OCXO temperature is: %d\n", temp); + } + + /* Read the current TX temperature */ + rc = oc2gbts_temp_get(OC2GBTS_TEMP_TX, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_NOTICE, + "Failed to read the TX temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.tx_temp_limit.thresh_warn_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because TX temperature is over %d\n", mgr->temp.tx_temp_limit.thresh_warn_max); + warn_thresh_passed = 1; + mgr->alarms.temp_high = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_TRX_WARN_MAX_ALARM; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + if (temp < mgr->temp.tx_temp_limit.thresh_warn_min) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because TX temperature is under %d\n", mgr->temp.tx_temp_limit.thresh_warn_min); + warn_thresh_passed = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_TRX_WARN_MIN_ALARM; + } + if (temp > mgr->temp.tx_temp_limit.thresh_crit_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because TX temperature is over %d\n", mgr->temp.tx_temp_limit.thresh_crit_max); + crit_thresh_passed = 1; + mgr->alarms.temp_max = 1; + mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_TRX_CRIT_MAX_ALARM; + mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_TRX_WARN_MAX_ALARM; + action = SENSOR_ACT_PA_OFF; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + LOGP(DTEMP, LOGL_INFO, "TX temperature is: %d\n", temp); + } + + if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) { + /* Read the current PA temperature */ + rc = oc2gbts_temp_get(OC2GBTS_TEMP_PA, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_NOTICE, + "Failed to read the PA temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.pa_temp_limit.thresh_warn_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because PA temperature because is over %d\n", mgr->temp.pa_temp_limit.thresh_warn_max); + warn_thresh_passed = 1; + mgr->alarms.temp_high = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_PA_WARN_MAX_ALARM; + action = SENSOR_ACT_PA_OFF; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + if (temp < mgr->temp.pa_temp_limit.thresh_warn_min) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because PA temperature because is under %d\n", mgr->temp.pa_temp_limit.thresh_warn_min); + warn_thresh_passed = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_PA_WARN_MIN_ALARM; + } + if (temp > mgr->temp.pa_temp_limit.thresh_crit_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because PA temperature because is over %d\n", mgr->temp.pa_temp_limit.thresh_crit_max); + crit_thresh_passed = 1; + mgr->alarms.temp_max = 1; + mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_PA_CRIT_MAX_ALARM; + mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_PA_WARN_MAX_ALARM; + action = SENSOR_ACT_PA_OFF; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + LOGP(DTEMP, LOGL_INFO, "PA temperature is: %d\n", temp); + } + } + + /* Read the current main supply voltage */ + if (oc2gbts_power_get(OC2GBTS_POWER_SUPPLY)) { + rc = oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY, OC2GBTS_POWER_VOLTAGE, &volt); + if (rc < 0) { + LOGP(DTEMP, LOGL_NOTICE, + "Failed to read the main supply voltage. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + if (volt > mgr->volt.supply_volt_limit.thresh_warn_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because supply voltage is over %d\n", mgr->volt.supply_volt_limit.thresh_warn_max); + warn_thresh_passed = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_SUPPLY_WARN_MAX_ALARM; + } + if (volt < mgr->volt.supply_volt_limit.thresh_warn_min) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because supply voltage is under %d\n", mgr->volt.supply_volt_limit.thresh_warn_min); + warn_thresh_passed = 1; + mgr->alarms.supply_low = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_SUPPLY_WARN_MIN_ALARM; + } + if (volt > mgr->volt.supply_volt_limit.thresh_crit_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because supply voltage is over %d\n", mgr->volt.supply_volt_limit.thresh_crit_max); + crit_thresh_passed = 1; + mgr->oc2gbts_ctrl.crit_flags |= S_MGR_SUPPLY_CRIT_MAX_ALARM; + mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_SUPPLY_WARN_MAX_ALARM; + } + + if (volt < mgr->volt.supply_volt_limit.thresh_crit_min) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because supply voltage is under %d\n", mgr->volt.supply_volt_limit.thresh_crit_min); + crit_thresh_passed = 1; + mgr->alarms.supply_min = 1; + mgr->oc2gbts_ctrl.crit_flags |= S_MGR_SUPPLY_CRIT_MIN_ALARM; + mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_SUPPLY_WARN_MIN_ALARM; + action = SENSOR_ACT_PA_OFF; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_NONE); + } + LOGP(DTEMP, LOGL_INFO, "Main supply voltage is: %d\n", volt); + } + } + + /* Read the main supply power consumption */ + if (oc2gbts_power_get(OC2GBTS_POWER_SUPPLY)) { + rc = oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY, OC2GBTS_POWER_POWER, &power); + if (rc < 0) { + LOGP(DTEMP, LOGL_NOTICE, + "Failed to read the power supply current. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + power /= 1000000; + if (power > mgr->pwr.supply_pwr_limit.thresh_warn_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because main supply power consumption is over %d\n", mgr->pwr.supply_pwr_limit.thresh_warn_max); + warn_thresh_passed = 1; + mgr->alarms.supply_pwr_high = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_PWR_SUPPLY_WARN_MAX_ALARM; + } + if (power > mgr->pwr.supply_pwr_limit.thresh_crit_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because main supply power consumption is over %d\n", mgr->pwr.supply_pwr_limit.thresh_crit_max); + crit_thresh_passed = 1; + + mgr->oc2gbts_ctrl.crit_flags |= S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM; + mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_PWR_SUPPLY_WARN_MAX_ALARM; + + if (oc2gbts_power_get(OC2GBTS_POWER_PA)) { + mgr->alarms.supply_pwr_max = 1; + /* schedule to turn off PA */ + action = SENSOR_ACT_PA_OFF; + /* repeat same alarm to BSC */ + handle_alert_actions(mgr); + } + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_SHORT_DURATION); + } + LOGP(DTEMP, LOGL_INFO, "Main supply current power consumption is: %d\n", power); + } + } else { + /* keep last state */ + if (mgr->oc2gbts_ctrl.crit_flags & S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM) { + warn_thresh_passed = 1; + crit_thresh_passed = 1; + } + } + + if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) { + /* Read the current PA power consumption */ + if (oc2gbts_power_get(OC2GBTS_POWER_PA)) { + rc = oc2gbts_power_sensor_get(OC2GBTS_POWER_PA, OC2GBTS_POWER_POWER, &power); + if (rc < 0) { + LOGP(DTEMP, LOGL_NOTICE, + "Failed to read the PA power. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + power /= 1000000; + if (power > mgr->pwr.pa_pwr_limit.thresh_warn_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because PA power consumption is over %d\n", mgr->pwr.pa_pwr_limit.thresh_warn_max); + warn_thresh_passed = 1; + mgr->alarms.pa_pwr_high = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_PWR_PA_WARN_MAX_ALARM; + } + if (power > mgr->pwr.pa_pwr_limit.thresh_crit_max) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because PA power consumption is over %d\n", mgr->pwr.pa_pwr_limit.thresh_crit_max); + crit_thresh_passed = 1; + mgr->alarms.pa_pwr_max = 1; + mgr->oc2gbts_ctrl.crit_flags |= S_MGR_PWR_PA_CRIT_MAX_ALARM; + mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_PWR_PA_WARN_MAX_ALARM; + action = SENSOR_ACT_PA_OFF; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_SHORT_DURATION); + } + LOGP(DTEMP, LOGL_INFO, "PA power consumption is: %d\n", power); + } + } else { + /* keep last state */ + if (mgr->oc2gbts_ctrl.crit_flags & S_MGR_PWR_PA_CRIT_MAX_ALARM) { + warn_thresh_passed = 1; + crit_thresh_passed = 1; + } + } + } + + if (oc2gbts_option_get(OC2GBTS_OPTION_PA) && + oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) && + oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) { + /* Read the current VSWR of powered ON PA*/ + if (oc2gbts_power_get(OC2GBTS_POWER_PA)) { + rc = oc2gbts_vswr_get(OC2GBTS_VSWR, &vswr); + if (rc < 0) { + LOGP(DTEMP, LOGL_NOTICE, + "Failed to read the VSWR. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + if ((vswr > mgr->vswr.vswr_limit.thresh_warn_max) && (mgr->vswr.last_vswr > mgr->vswr.vswr_limit.thresh_warn_max)) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because VSWR is over %d\n", mgr->vswr.vswr_limit.thresh_warn_max); + warn_thresh_passed = 1; + mgr->alarms.vswr_high = 1; + mgr->oc2gbts_ctrl.warn_flags |= S_MGR_VSWR_WARN_MAX_ALARM; + } + if ((vswr > mgr->vswr.vswr_limit.thresh_crit_max) && (mgr->vswr.last_vswr > mgr->vswr.vswr_limit.thresh_crit_max)) { + LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because VSWR is over %d\n", mgr->vswr.vswr_limit.thresh_crit_max); + crit_thresh_passed = 1; + mgr->alarms.vswr_max = 1; + mgr->oc2gbts_ctrl.crit_flags |= S_MGR_VSWR_CRIT_MAX_ALARM; + mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_VSWR_WARN_MAX_ALARM; + action = SENSOR_ACT_PA_OFF; + /* add to pending list */ + schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION); + } + LOGP(DTEMP, LOGL_INFO, "VSWR is: current = %d, last = %d\n", vswr, mgr->vswr.last_vswr); + + /* update last VSWR */ + mgr->vswr.last_vswr = vswr; + } + } else { + /* keep last state */ + if (mgr->oc2gbts_ctrl.crit_flags & S_MGR_VSWR_CRIT_MAX_ALARM) { + warn_thresh_passed = 1; + crit_thresh_passed = 1; + } + } + } + + select_led_pattern(mgr); + oc2gbts_mgr_sensor_handle(mgr, crit_thresh_passed, warn_thresh_passed); +} + +static void sensor_ctrl_check_cb(void *_data) +{ + struct oc2gbts_mgr_instance *mgr = _data; + sensor_ctrl_check(mgr); + /* Check every minute? XXX make it configurable! */ + osmo_timer_schedule(&sensor_ctrl_timer, OC2GBTS_SENSOR_TIMER_DURATION, 0); + LOGP(DTEMP, LOGL_DEBUG,"Check sensors timer expired\n"); + /* TODO: do we want to notify if some sensors could not be read? */ + oc2gbts_swd_event(mgr, SWD_CHECK_TEMP_SENSOR); +} + +int oc2gbts_mgr_sensor_init(struct oc2gbts_mgr_instance *mgr) +{ + int rc = 0; + + /* always enable PA GPIO for OC-2G */ + if (!oc2gbts_power_get(OC2GBTS_POWER_PA)) { + rc = oc2gbts_power_set(OC2GBTS_POWER_PA, 1); + if (!rc) + LOGP(DTEMP, LOGL_ERROR, "Failed to set GPIO for internal PA\n"); + } + + s_mgr = mgr; + sensor_ctrl_timer.cb = sensor_ctrl_check_cb; + sensor_ctrl_timer.data = s_mgr; + sensor_ctrl_check_cb(s_mgr); + return rc; +} + diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_vty.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_vty.c new file mode 100644 index 000000000..ef527394e --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_vty.c @@ -0,0 +1,984 @@ +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * sysmobts_mgr_vty.c + * (C) 2014 by oc2gcom - s.f.m.c. GmbH + * + * All Rights Reserved + * + * Author: Alvaro Neira Ayuso + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "oc2gbts_misc.h" +#include "oc2gbts_mgr.h" +#include "oc2gbts_temp.h" +#include "oc2gbts_power.h" +#include "oc2gbts_bid.h" +#include "oc2gbts_led.h" +#include "btsconfig.h" + +static struct oc2gbts_mgr_instance *s_mgr; + +static const char copyright[] = + "(C) 2012 by Harald Welte \r\n" + "(C) 2014 by Holger Hans Peter Freyther\r\n" + "(C) 2015 by Yves Godin \r\n" + "License AGPLv3+: GNU AGPL version 2 or later \r\n" + "This is free software: you are free to change and redistribute it.\r\n" + "There is NO WARRANTY, to the extent permitted by law.\r\n"; + +static int go_to_parent(struct vty *vty) +{ + switch (vty->node) { + case MGR_NODE: + vty->node = CONFIG_NODE; + break; + case ACT_NORM_NODE: + case ACT_WARN_NODE: + case ACT_CRIT_NODE: + case LIMIT_SUPPLY_TEMP_NODE: + case LIMIT_SOC_NODE: + case LIMIT_FPGA_NODE: + case LIMIT_RMSDET_NODE: + case LIMIT_OCXO_NODE: + case LIMIT_TX_TEMP_NODE: + case LIMIT_PA_TEMP_NODE: + case LIMIT_SUPPLY_VOLT_NODE: + case LIMIT_VSWR_NODE: + case LIMIT_SUPPLY_PWR_NODE: + case LIMIT_PA_PWR_NODE: + vty->node = MGR_NODE; + break; + default: + vty->node = CONFIG_NODE; + } + return vty->node; +} + +static int is_config_node(struct vty *vty, int node) +{ + switch (node) { + case MGR_NODE: + case ACT_NORM_NODE: + case ACT_WARN_NODE: + case ACT_CRIT_NODE: + case LIMIT_SUPPLY_TEMP_NODE: + case LIMIT_SOC_NODE: + case LIMIT_FPGA_NODE: + case LIMIT_RMSDET_NODE: + case LIMIT_OCXO_NODE: + case LIMIT_TX_TEMP_NODE: + case LIMIT_PA_TEMP_NODE: + case LIMIT_SUPPLY_VOLT_NODE: + case LIMIT_VSWR_NODE: + case LIMIT_SUPPLY_PWR_NODE: + case LIMIT_PA_PWR_NODE: + return 1; + default: + return 0; + } +} + +static struct vty_app_info vty_info = { + .name = "oc2gbts-mgr", + .version = PACKAGE_VERSION, + .go_parent_cb = go_to_parent, + .is_config_node = is_config_node, + .copyright = copyright, +}; + + +#define MGR_STR "Configure oc2gbts-mgr\n" + +static struct cmd_node mgr_node = { + MGR_NODE, + "%s(oc2gbts-mgr)# ", + 1, +}; + +static struct cmd_node act_norm_node = { + ACT_NORM_NODE, + "%s(actions-normal)# ", + 1, +}; + +static struct cmd_node act_warn_node = { + ACT_WARN_NODE, + "%s(actions-warn)# ", + 1, +}; + +static struct cmd_node act_crit_node = { + ACT_CRIT_NODE, + "%s(actions-critical)# ", + 1, +}; + +static struct cmd_node limit_supply_temp_node = { + LIMIT_SUPPLY_TEMP_NODE, + "%s(limit-supply-temp)# ", + 1, +}; + +static struct cmd_node limit_soc_node = { + LIMIT_SOC_NODE, + "%s(limit-soc)# ", + 1, +}; + +static struct cmd_node limit_fpga_node = { + LIMIT_FPGA_NODE, + "%s(limit-fpga)# ", + 1, +}; + +static struct cmd_node limit_rmsdet_node = { + LIMIT_RMSDET_NODE, + "%s(limit-rmsdet)# ", + 1, +}; + +static struct cmd_node limit_ocxo_node = { + LIMIT_OCXO_NODE, + "%s(limit-ocxo)# ", + 1, +}; + +static struct cmd_node limit_tx_temp_node = { + LIMIT_TX_TEMP_NODE, + "%s(limit-tx-temp)# ", + 1, +}; +static struct cmd_node limit_pa_temp_node = { + LIMIT_PA_TEMP_NODE, + "%s(limit-pa-temp)# ", + 1, +}; +static struct cmd_node limit_supply_volt_node = { + LIMIT_SUPPLY_VOLT_NODE, + "%s(limit-supply-volt)# ", + 1, +}; +static struct cmd_node limit_vswr_node = { + LIMIT_VSWR_NODE, + "%s(limit-vswr)# ", + 1, +}; +static struct cmd_node limit_supply_pwr_node = { + LIMIT_SUPPLY_PWR_NODE, + "%s(limit-supply-pwr)# ", + 1, +}; +static struct cmd_node limit_pa_pwr_node = { + LIMIT_PA_PWR_NODE, + "%s(limit-pa-pwr)# ", + 1, +}; + +static struct cmd_node limit_gps_fix_node = { + LIMIT_GPS_FIX_NODE, + "%s(limit-gps-fix)# ", + 1, +}; + +DEFUN(cfg_mgr, cfg_mgr_cmd, + "oc2gbts-mgr", + MGR_STR) +{ + vty->node = MGR_NODE; + return CMD_SUCCESS; +} + +static void write_volt_limit(struct vty *vty, const char *name, + struct oc2gbts_volt_limit *limit) +{ + vty_out(vty, " %s%s", name, VTY_NEWLINE); + vty_out(vty, " threshold warning min %d%s", + limit->thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " threshold critical min %d%s", + limit->thresh_crit_min, VTY_NEWLINE); +} + +static void write_vswr_limit(struct vty *vty, const char *name, + struct oc2gbts_vswr_limit *limit) +{ + vty_out(vty, " %s%s", name, VTY_NEWLINE); + vty_out(vty, " threshold warning max %d%s", + limit->thresh_warn_max, VTY_NEWLINE); +} + +static void write_pwr_limit(struct vty *vty, const char *name, + struct oc2gbts_pwr_limit *limit) +{ + vty_out(vty, " %s%s", name, VTY_NEWLINE); + vty_out(vty, " threshold warning max %d%s", + limit->thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " threshold critical max %d%s", + limit->thresh_crit_max, VTY_NEWLINE); +} + +static void write_norm_action(struct vty *vty, const char *name, int actions) +{ + vty_out(vty, " %s%s", name, VTY_NEWLINE); + vty_out(vty, " %spa-on%s", + (actions & SENSOR_ACT_NORM_PA_ON) ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %sbts-service-on%s", + (actions & SENSOR_ACT_NORM_BTS_SRV_ON) ? "" : "no ", VTY_NEWLINE); +} + +static void write_action(struct vty *vty, const char *name, int actions) +{ + vty_out(vty, " %s%s", name, VTY_NEWLINE); + vty_out(vty, " %spa-off%s", + (actions & SENSOR_ACT_PA_OFF) ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %sbts-service-off%s", + (actions & SENSOR_ACT_BTS_SRV_OFF) ? "" : "no ", VTY_NEWLINE); +} + +static int config_write_mgr(struct vty *vty) +{ + vty_out(vty, "oc2gbts-mgr%s", VTY_NEWLINE); + + write_volt_limit(vty, "limits supply_volt", &s_mgr->volt.supply_volt_limit); + write_pwr_limit(vty, "limits supply_pwr", &s_mgr->pwr.supply_pwr_limit); + write_vswr_limit(vty, "limits vswr", &s_mgr->vswr.vswr_limit); + + write_norm_action(vty, "actions normal", s_mgr->state.action_norm); + write_action(vty, "actions warn", s_mgr->state.action_warn); + write_action(vty, "actions critical", s_mgr->state.action_crit); + + return CMD_SUCCESS; +} + +static int config_write_dummy(struct vty *vty) +{ + return CMD_SUCCESS; +} + +#define CFG_LIMIT_TEMP(name, expl, switch_to, variable) \ +DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \ + "limits " #name, \ + "Configure Limits\n" expl) \ +{ \ + vty->node = switch_to; \ + vty->index = &s_mgr->temp.variable; \ + return CMD_SUCCESS; \ +} + +CFG_LIMIT_TEMP(supply_temp, "SUPPLY TEMP\n", LIMIT_SUPPLY_TEMP_NODE, supply_temp_limit) +CFG_LIMIT_TEMP(soc_temp, "SOC TEMP\n", LIMIT_SOC_NODE, soc_temp_limit) +CFG_LIMIT_TEMP(fpga_temp, "FPGA TEMP\n", LIMIT_FPGA_NODE, fpga_temp_limit) +CFG_LIMIT_TEMP(rmsdet_temp, "RMSDET TEMP\n", LIMIT_RMSDET_NODE, rmsdet_temp_limit) +CFG_LIMIT_TEMP(ocxo_temp, "OCXO TEMP\n", LIMIT_OCXO_NODE, ocxo_temp_limit) +CFG_LIMIT_TEMP(tx_temp, "TX TEMP\n", LIMIT_TX_TEMP_NODE, tx_temp_limit) +CFG_LIMIT_TEMP(pa_temp, "PA TEMP\n", LIMIT_PA_TEMP_NODE, pa_temp_limit) +#undef CFG_LIMIT_TEMP + +#define CFG_LIMIT_VOLT(name, expl, switch_to, variable) \ +DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \ + "limits " #name, \ + "Configure Limits\n" expl) \ +{ \ + vty->node = switch_to; \ + vty->index = &s_mgr->volt.variable; \ + return CMD_SUCCESS; \ +} + +CFG_LIMIT_VOLT(supply_volt, "SUPPLY VOLT\n", LIMIT_SUPPLY_VOLT_NODE, supply_volt_limit) +#undef CFG_LIMIT_VOLT + +#define CFG_LIMIT_VSWR(name, expl, switch_to, variable) \ +DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \ + "limits " #name, \ + "Configure Limits\n" expl) \ +{ \ + vty->node = switch_to; \ + vty->index = &s_mgr->vswr.variable; \ + return CMD_SUCCESS; \ +} + +CFG_LIMIT_VSWR(vswr, "VSWR\n", LIMIT_VSWR_NODE, vswr_limit) +#undef CFG_LIMIT_VSWR + +#define CFG_LIMIT_PWR(name, expl, switch_to, variable) \ +DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \ + "limits " #name, \ + "Configure Limits\n" expl) \ +{ \ + vty->node = switch_to; \ + vty->index = &s_mgr->pwr.variable; \ + return CMD_SUCCESS; \ +} + +CFG_LIMIT_PWR(supply_pwr, "SUPPLY PWR\n", LIMIT_SUPPLY_PWR_NODE, supply_pwr_limit) +CFG_LIMIT_PWR(pa_pwr, "PA PWR\n", LIMIT_PA_PWR_NODE, pa_pwr_limit) +#undef CFG_LIMIT_PWR + +#define CFG_LIMIT_GPS_FIX(name, expl, switch_to, variable) \ +DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \ + "limits " #name, \ + "Configure Limits\n" expl) \ +{ \ + vty->node = switch_to; \ + vty->index = &s_mgr->gps.variable; \ + return CMD_SUCCESS; \ +} + +CFG_LIMIT_GPS_FIX(gps_fix, "GPS FIX\n", LIMIT_GPS_FIX_NODE, gps_fix_limit) +#undef CFG_LIMIT_GPS_FIX + +DEFUN(cfg_limit_volt_warn_min, cfg_thresh_volt_warn_min_cmd, + "threshold warning min <0-48000>", + "Threshold to reach\n" "Warning level\n" "Range\n") +{ + struct oc2gbts_volt_limit *limit = vty->index; + limit->thresh_warn_min = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_limit_volt_crit_min, cfg_thresh_volt_crit_min_cmd, + "threshold critical min <0-48000>", + "Threshold to reach\n" "Critical level\n" "Range\n") +{ + struct oc2gbts_volt_limit *limit = vty->index; + limit->thresh_crit_min = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_limit_vswr_warn_max, cfg_thresh_vswr_warn_max_cmd, + "threshold warning max <1000-200000>", + "Threshold to reach\n" "Warning level\n" "Range\n") +{ + struct oc2gbts_vswr_limit *limit = vty->index; + limit->thresh_warn_max = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_limit_vswr_crit_max, cfg_thresh_vswr_crit_max_cmd, + "threshold critical max <1000-200000>", + "Threshold to reach\n" "Warning level\n" "Range\n") +{ + struct oc2gbts_vswr_limit *limit = vty->index; + limit->thresh_crit_max = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_limit_pwr_warn_max, cfg_thresh_pwr_warn_max_cmd, + "threshold warning max <0-200>", + "Threshold to reach\n" "Warning level\n" "Range\n") +{ + struct oc2gbts_pwr_limit *limit = vty->index; + limit->thresh_warn_max = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_limit_pwr_crit_max, cfg_thresh_pwr_crit_max_cmd, + "threshold critical max <0-200>", + "Threshold to reach\n" "Warning level\n" "Range\n") +{ + struct oc2gbts_pwr_limit *limit = vty->index; + limit->thresh_crit_max = atoi(argv[0]); + return CMD_SUCCESS; +} + +#define CFG_ACTION(name, expl, switch_to, variable) \ +DEFUN(cfg_action_##name, cfg_action_##name##_cmd, \ + "actions " #name, \ + "Configure Actions\n" expl) \ +{ \ + vty->node = switch_to; \ + vty->index = &s_mgr->state.variable; \ + return CMD_SUCCESS; \ +} +CFG_ACTION(normal, "Normal Actions\n", ACT_NORM_NODE, action_norm) +CFG_ACTION(warn, "Warning Actions\n", ACT_WARN_NODE, action_warn) +CFG_ACTION(critical, "Critical Actions\n", ACT_CRIT_NODE, action_crit) +#undef CFG_ACTION + +DEFUN(cfg_action_pa_on, cfg_action_pa_on_cmd, + "pa-on", + "Switch the Power Amplifier on\n") +{ + int *action = vty->index; + *action |= SENSOR_ACT_NORM_PA_ON; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_action_pa_on, cfg_no_action_pa_on_cmd, + "no pa-on", + NO_STR "Switch the Power Amplifier on\n") +{ + int *action = vty->index; + *action &= ~SENSOR_ACT_NORM_PA_ON; + return CMD_SUCCESS; +} + +DEFUN(cfg_action_bts_srv_on, cfg_action_bts_srv_on_cmd, + "bts-service-on", + "Start the systemd oc2gbts.service\n") +{ + int *action = vty->index; + *action |= SENSOR_ACT_NORM_BTS_SRV_ON; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_action_bts_srv_on, cfg_no_action_bts_srv_on_cmd, + "no bts-service-on", + NO_STR "Start the systemd oc2gbts.service\n") +{ + int *action = vty->index; + *action &= ~SENSOR_ACT_NORM_BTS_SRV_ON; + return CMD_SUCCESS; +} + +DEFUN(cfg_action_pa_off, cfg_action_pa_off_cmd, + "pa-off", + "Switch the Power Amplifier off\n") +{ + int *action = vty->index; + *action |= SENSOR_ACT_PA_OFF; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_action_pa_off, cfg_no_action_pa_off_cmd, + "no pa-off", + NO_STR "Do not switch off the Power Amplifier\n") +{ + int *action = vty->index; + *action &= ~SENSOR_ACT_PA_OFF; + return CMD_SUCCESS; +} + +DEFUN(cfg_action_bts_srv_off, cfg_action_bts_srv_off_cmd, + "bts-service-off", + "Stop the systemd oc2gbts.service\n") +{ + int *action = vty->index; + *action |= SENSOR_ACT_BTS_SRV_OFF; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_action_bts_srv_off, cfg_no_action_bts_srv_off_cmd, + "no bts-service-off", + NO_STR "Stop the systemd oc2gbts.service\n") +{ + int *action = vty->index; + *action &= ~SENSOR_ACT_BTS_SRV_OFF; + return CMD_SUCCESS; +} + +DEFUN(show_mgr, show_mgr_cmd, "show manager", + SHOW_STR "Display information about the manager") +{ + int temp, volt, current, power, vswr; + vty_out(vty, "Warning alarm flags: 0x%08x%s", + s_mgr->oc2gbts_ctrl.warn_flags, VTY_NEWLINE); + vty_out(vty, "Critical alarm flags: 0x%08x%s", + s_mgr->oc2gbts_ctrl.crit_flags, VTY_NEWLINE); + vty_out(vty, "Preventive action retried: %d%s", + s_mgr->alarms.preventive_retry, VTY_NEWLINE); + vty_out(vty, "Temperature control state: %s%s", + oc2gbts_mgr_sensor_get_state(s_mgr->state.state), VTY_NEWLINE); + vty_out(vty, "Current Temperatures%s", VTY_NEWLINE); + oc2gbts_temp_get(OC2GBTS_TEMP_SUPPLY, &temp); + vty_out(vty, " Main Supply : %4.2f Celcius%s", + temp/ 1000.0f, + VTY_NEWLINE); + oc2gbts_temp_get(OC2GBTS_TEMP_SOC, &temp); + vty_out(vty, " SoC : %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + oc2gbts_temp_get(OC2GBTS_TEMP_FPGA, &temp); + vty_out(vty, " FPGA : %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + if (oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) || + oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) { + oc2gbts_temp_get(OC2GBTS_TEMP_RMSDET, &temp); + vty_out(vty, " RMSDet : %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + } + oc2gbts_temp_get(OC2GBTS_TEMP_OCXO, &temp); + vty_out(vty, " OCXO : %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + oc2gbts_temp_get(OC2GBTS_TEMP_TX, &temp); + vty_out(vty, " TX : %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) { + oc2gbts_temp_get(OC2GBTS_TEMP_PA, &temp); + vty_out(vty, " Power Amp : %4.2f Celcius%s", + temp / 1000.0f, + VTY_NEWLINE); + } + vty_out(vty, "Power Status%s", VTY_NEWLINE); + oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY, + OC2GBTS_POWER_VOLTAGE, &volt); + oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY, + OC2GBTS_POWER_CURRENT, ¤t); + oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY, + OC2GBTS_POWER_POWER, &power); + vty_out(vty, " Main Supply : ON [%6.2f Vdc, %4.2f A, %6.2f W]%s", + volt /1000.0f, + current /1000.0f, + power /1000000.0f, + VTY_NEWLINE); + if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) { + oc2gbts_power_sensor_get(OC2GBTS_POWER_PA, + OC2GBTS_POWER_VOLTAGE, &volt); + oc2gbts_power_sensor_get(OC2GBTS_POWER_PA, + OC2GBTS_POWER_CURRENT, ¤t); + oc2gbts_power_sensor_get(OC2GBTS_POWER_PA, + OC2GBTS_POWER_POWER, &power); + vty_out(vty, " Power Amp : %s [%6.2f Vdc, %4.2f A, %6.2f W]%s", + oc2gbts_power_get(OC2GBTS_POWER_PA) ? "ON " : "OFF", + volt /1000.0f, + current /1000.0f, + power /1000000.0f, + VTY_NEWLINE); + } + if (oc2gbts_option_get(OC2GBTS_OPTION_PA) && + oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) && + oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) { + vty_out(vty, "VSWR Status%s", VTY_NEWLINE); + oc2gbts_vswr_get(OC2GBTS_VSWR, &vswr); + vty_out(vty, " VSWR : %f %s", + vswr / 1000.0f, + VTY_NEWLINE); + } + return CMD_SUCCESS; +} + +DEFUN(show_thresh, show_thresh_cmd, "show thresholds", + SHOW_STR "Display information about the thresholds") +{ + vty_out(vty, "Temperature limits (Celsius)%s", VTY_NEWLINE); + vty_out(vty, " Main supply%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.supply_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.supply_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.supply_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " SoC%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.soc_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.soc_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.soc_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " FPGA%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.fpga_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.fpga_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.fpga_temp_limit.thresh_warn_min, VTY_NEWLINE); + if (oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) || + oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) { + vty_out(vty, " RMSDet%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_warn_min, VTY_NEWLINE); + } + vty_out(vty, " OCXO%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " TX%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.tx_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.tx_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.tx_temp_limit.thresh_warn_min, VTY_NEWLINE); + if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) { + vty_out(vty, " PA%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.pa_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.pa_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.pa_temp_limit.thresh_warn_min, VTY_NEWLINE); + } + vty_out(vty, "Power limits%s", VTY_NEWLINE); + vty_out(vty, " Main supply (mV)%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->volt.supply_volt_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->volt.supply_volt_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->volt.supply_volt_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " Critical min : %d%s",s_mgr->volt.supply_volt_limit.thresh_crit_min, VTY_NEWLINE); + vty_out(vty, " Main supply power (W)%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->pwr.supply_pwr_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->pwr.supply_pwr_limit.thresh_warn_max, VTY_NEWLINE); + if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) { + vty_out(vty, " PA power (W)%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->pwr.pa_pwr_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->pwr.pa_pwr_limit.thresh_warn_max, VTY_NEWLINE); + } + if (oc2gbts_option_get(OC2GBTS_OPTION_PA) && + oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) && + oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) { + vty_out(vty, "VSWR limits%s", VTY_NEWLINE); + vty_out(vty, " TX%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->vswr.vswr_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->vswr.vswr_limit.thresh_warn_max, VTY_NEWLINE); + } + vty_out(vty, "Days since last GPS 3D fix%s", VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->gps.gps_fix_limit.thresh_warn_max, VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(calibrate_clock, calibrate_clock_cmd, + "calibrate clock", + "Calibration commands\n" + "Calibrate clock against GPS PPS\n") +{ + if (oc2gbts_mgr_calib_run(s_mgr) < 0) { + vty_out(vty, "%%Failed to start calibration.%s", VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(set_led_pattern, set_led_pattern_cmd, + "set led pattern <0-255>", + "Set LED pattern\n" + "Set LED pattern for debugging purpose only. This pattern will be overridden after 60 seconds by LED pattern of actual system state\n") +{ + int pattern_id = atoi(argv[0]); + + if ((pattern_id < 0) || (pattern_id > BLINK_PATTERN_MAX_ITEM)) { + vty_out(vty, "%%Invalid LED pattern ID. It must be in range of %d..%d %s", 0, BLINK_PATTERN_MAX_ITEM - 1, VTY_NEWLINE); + return CMD_WARNING; + } + + led_set(s_mgr, pattern_id); + return CMD_SUCCESS; +} + +DEFUN(force_mgr_state, force_mgr_state_cmd, + "force manager state <0-255>", + "Force BTS manager state\n" + "Force BTS manager state for debugging purpose only\n") +{ + int state = atoi(argv[0]); + + if ((state < 0) || (state > STATE_CRITICAL)) { + vty_out(vty, "%%Invalid BTS manager state. It must be in range of %d..%d %s", 0, STATE_CRITICAL, VTY_NEWLINE); + return CMD_WARNING; + } + + s_mgr->state.state = state; + return CMD_SUCCESS; +} + +#define LIMIT_TEMP(name, limit, expl, variable, criticity, min_max) \ +DEFUN(limit_temp_##name##_##variable, limit_temp_##name##_##variable##_cmd, \ + "limit temp " #name " " #criticity " " #min_max " <-200-200>", \ + "Limit to reach\n" expl) \ +{ \ + s_mgr->temp.limit.variable = atoi(argv[0]); \ + return CMD_SUCCESS; \ +} + +LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(tx, tx_temp_limit, "TX TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(tx, tx_temp_limit, "TX TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(tx, tx_temp_limit, "TX TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(pa, pa_temp_limit, "PA TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(pa, pa_temp_limit, "PA TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(pa, pa_temp_limit, "PA TEMP\n", thresh_warn_min, warning, min) +#undef LIMIT_TEMP + +#define LIMIT_VOLT(name, limit, expl, variable, criticity, min_max) \ +DEFUN(limit_volt_##name##_##variable, limit_volt_##name##_##variable##_cmd, \ + "limit " #name " " #criticity " " #min_max " <0-48000>", \ + "Limit to reach\n" expl) \ +{ \ + s_mgr->volt.limit.variable = atoi(argv[0]); \ + return CMD_SUCCESS; \ +} + +LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_warn_max, warning, max) +LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_crit_max, critical, max) +LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_warn_min, warning, min) +LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_crit_min, critical, min) +#undef LIMIT_VOLT + +#define LIMIT_PWR(name, limit, expl, variable, criticity, min_max) \ +DEFUN(limit_pwr_##name##_##variable, limit_pwr_##name##_##variable##_cmd, \ + "limit power " #name " " #criticity " " #min_max " <0-200>", \ + "Limit to reach\n" expl) \ +{ \ + s_mgr->pwr.limit.variable = atoi(argv[0]); \ + return CMD_SUCCESS; \ +} + +LIMIT_PWR(supply, supply_pwr_limit, "SUPPLY PWR\n", thresh_warn_max, warning, max) +LIMIT_PWR(supply, supply_pwr_limit, "SUPPLY PWR\n", thresh_crit_max, critical, max) +LIMIT_PWR(pa, pa_pwr_limit, "PA PWR\n", thresh_warn_max, warning, max) +LIMIT_PWR(pa, pa_pwr_limit, "PA PWR\n", thresh_crit_max, critical, max) +#undef LIMIT_PWR + +#define LIMIT_VSWR(limit, expl, variable, criticity, min_max) \ +DEFUN(limit_vswr_##variable, limit_vswr_##variable##_cmd, \ + "limit vswr " #criticity " " #min_max " <1000-200000>", \ + "Limit to reach\n" expl) \ +{ \ + s_mgr->vswr.limit.variable = atoi(argv[0]); \ + return CMD_SUCCESS; \ +} + +LIMIT_VSWR(vswr_limit, "VSWR\n", thresh_warn_max, warning, max) +LIMIT_VSWR(vswr_limit, "VSWR\n", thresh_crit_max, critical, max) +#undef LIMIT_VSWR + +#define LIMIT_GPSFIX(limit, expl, variable, criticity, min_max) \ +DEFUN(limit_gpsfix_##variable, limit_gpsfix_##variable##_cmd, \ + "limit gpsfix " #criticity " " #min_max " <0-365>", \ + "Limit to reach\n" expl) \ +{ \ + s_mgr->gps.limit.variable = atoi(argv[0]); \ + return CMD_SUCCESS; \ +} + +LIMIT_GPSFIX(gps_fix_limit, "GPS FIX\n", thresh_warn_max, warning, max) +#undef LIMIT_GPSFIX + +static void register_limit(int limit, uint32_t unit) +{ + switch (unit) { + case MGR_LIMIT_TYPE_VOLT: + install_element(limit, &cfg_thresh_volt_warn_min_cmd); + install_element(limit, &cfg_thresh_volt_crit_min_cmd); + break; + case MGR_LIMIT_TYPE_VSWR: + install_element(limit, &cfg_thresh_vswr_warn_max_cmd); + install_element(limit, &cfg_thresh_vswr_crit_max_cmd); + break; + case MGR_LIMIT_TYPE_PWR: + install_element(limit, &cfg_thresh_pwr_warn_max_cmd); + install_element(limit, &cfg_thresh_pwr_crit_max_cmd); + break; + default: + break; + } +} + +static void register_normal_action(int act) +{ + if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) { + install_element(act, &cfg_action_pa_on_cmd); + install_element(act, &cfg_no_action_pa_on_cmd); + } + install_element(act, &cfg_action_bts_srv_on_cmd); + install_element(act, &cfg_no_action_bts_srv_on_cmd); +} + +static void register_action(int act) +{ + if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) { + install_element(act, &cfg_action_pa_off_cmd); + install_element(act, &cfg_no_action_pa_off_cmd); + } + install_element(act, &cfg_action_bts_srv_off_cmd); + install_element(act, &cfg_no_action_bts_srv_off_cmd); +} + +static void register_hidden_commands() +{ + install_element(ENABLE_NODE, &limit_temp_supply_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_supply_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_supply_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_soc_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_soc_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_soc_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_fpga_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_fpga_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_fpga_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_tx_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_tx_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_tx_thresh_warn_min_cmd); + if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) { + install_element(ENABLE_NODE, &limit_temp_pa_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_pa_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_pa_thresh_warn_min_cmd); + } + + install_element(ENABLE_NODE, &limit_volt_supply_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_volt_supply_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_volt_supply_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_volt_supply_thresh_crit_min_cmd); + + install_element(ENABLE_NODE, &limit_pwr_supply_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_pwr_supply_thresh_crit_max_cmd); + + if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) { + install_element(ENABLE_NODE, &limit_pwr_pa_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_pwr_pa_thresh_crit_max_cmd); + } + + if (oc2gbts_option_get(OC2GBTS_OPTION_PA) && + oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) && + oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) { + install_element(ENABLE_NODE, &limit_vswr_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_vswr_thresh_crit_max_cmd); + } + + install_element(ENABLE_NODE, &limit_gpsfix_thresh_warn_max_cmd); +} + +int oc2gbts_mgr_vty_init(void) +{ + vty_init(&vty_info); + + install_element_ve(&show_mgr_cmd); + install_element_ve(&show_thresh_cmd); + + install_element(ENABLE_NODE, &calibrate_clock_cmd); + + install_node(&mgr_node, config_write_mgr); + install_element(CONFIG_NODE, &cfg_mgr_cmd); + vty_install_default(MGR_NODE); + + /* install the limit nodes */ + install_node(&limit_supply_temp_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_supply_temp_cmd); + vty_install_default(LIMIT_SUPPLY_TEMP_NODE); + + install_node(&limit_soc_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_soc_temp_cmd); + vty_install_default(LIMIT_SOC_NODE); + + install_node(&limit_fpga_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_fpga_temp_cmd); + vty_install_default(LIMIT_FPGA_NODE); + + if (oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) || + oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) { + install_node(&limit_rmsdet_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_rmsdet_temp_cmd); + vty_install_default(LIMIT_RMSDET_NODE); + } + + install_node(&limit_ocxo_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_ocxo_temp_cmd); + vty_install_default(LIMIT_OCXO_NODE); + + install_node(&limit_tx_temp_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_tx_temp_cmd); + vty_install_default(LIMIT_TX_TEMP_NODE); + + if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) { + install_node(&limit_pa_temp_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_pa_temp_cmd); + vty_install_default(LIMIT_PA_TEMP_NODE); + } + + install_node(&limit_supply_volt_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_supply_volt_cmd); + register_limit(LIMIT_SUPPLY_VOLT_NODE, MGR_LIMIT_TYPE_VOLT); + vty_install_default(LIMIT_SUPPLY_VOLT_NODE); + + if (oc2gbts_option_get(OC2GBTS_OPTION_PA) && + oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) && + oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) { + install_node(&limit_vswr_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_vswr_cmd); + register_limit(LIMIT_VSWR_NODE, MGR_LIMIT_TYPE_VSWR); + vty_install_default(LIMIT_VSWR_NODE); + } + + install_node(&limit_supply_pwr_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_supply_pwr_cmd); + register_limit(LIMIT_SUPPLY_PWR_NODE, MGR_LIMIT_TYPE_PWR); + vty_install_default(LIMIT_SUPPLY_PWR_NODE); + + if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) { + install_node(&limit_pa_pwr_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_pa_pwr_cmd); + vty_install_default(LIMIT_PA_PWR_NODE); + } + + install_node(&limit_gps_fix_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_gps_fix_cmd); + vty_install_default(LIMIT_GPS_FIX_NODE); + + /* install the normal node */ + install_node(&act_norm_node, config_write_dummy); + install_element(MGR_NODE, &cfg_action_normal_cmd); + register_normal_action(ACT_NORM_NODE); + + /* install the warning and critical node */ + install_node(&act_warn_node, config_write_dummy); + install_element(MGR_NODE, &cfg_action_warn_cmd); + register_action(ACT_WARN_NODE); + vty_install_default(ACT_WARN_NODE); + + install_node(&act_crit_node, config_write_dummy); + install_element(MGR_NODE, &cfg_action_critical_cmd); + register_action(ACT_CRIT_NODE); + vty_install_default(ACT_CRIT_NODE); + + /* install LED pattern command for debugging purpose */ + install_element_ve(&set_led_pattern_cmd); + install_element_ve(&force_mgr_state_cmd); + + register_hidden_commands(); + + return 0; +} + +int oc2gbts_mgr_parse_config(struct oc2gbts_mgr_instance *manager) +{ + int rc; + + s_mgr = manager; + rc = vty_read_config_file(s_mgr->config_file, NULL); + if (rc < 0) { + fprintf(stderr, "Failed to parse the config file: '%s'\n", + s_mgr->config_file); + return rc; + } + + return 0; +} diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_misc.c b/src/osmo-bts-oc2g/misc/oc2gbts_misc.c new file mode 100644 index 000000000..bacf07bd7 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_misc.c @@ -0,0 +1,381 @@ +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * sysmobts_misc.c + * (C) 2012 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "oc2gbts_mgr.h" +#include "btsconfig.h" +#include "oc2gbts_misc.h" +#include "oc2gbts_par.h" +#include "oc2gbts_temp.h" +#include "oc2gbts_power.h" +#include "oc2gbts_bid.h" + +/********************************************************************* + * Temperature handling + *********************************************************************/ + +static const struct { + const char *name; + int has_max; + enum oc2gbts_temp_sensor sensor; + enum oc2gbts_par ee_par; +} temp_data[] = { + { + .name = "supply_temp", + .has_max = 1, + .sensor = OC2GBTS_TEMP_SUPPLY, + .ee_par = OC2GBTS_PAR_TEMP_SUPPLY_MAX, + }, { + .name = "soc_temp", + .has_max = 0, + .sensor = OC2GBTS_TEMP_SOC, + .ee_par = OC2GBTS_PAR_TEMP_SOC_MAX, + }, { + .name = "fpga_temp", + .has_max = 0, + .sensor = OC2GBTS_TEMP_FPGA, + .ee_par = OC2GBTS_PAR_TEMP_FPGA_MAX, + + }, { + .name = "rmsdet_temp", + .has_max = 1, + .sensor = OC2GBTS_TEMP_RMSDET, + .ee_par = OC2GBTS_PAR_TEMP_RMSDET_MAX, + }, { + .name = "ocxo_temp", + .has_max = 1, + .sensor = OC2GBTS_TEMP_OCXO, + .ee_par = OC2GBTS_PAR_TEMP_OCXO_MAX, + }, { + .name = "tx_temp", + .has_max = 0, + .sensor = OC2GBTS_TEMP_TX, + .ee_par = OC2GBTS_PAR_TEMP_TX_MAX, + }, { + .name = "pa_temp", + .has_max = 1, + .sensor = OC2GBTS_TEMP_PA, + .ee_par = OC2GBTS_PAR_TEMP_PA_MAX, + } +}; + +static const struct { + const char *name; + int has_max; + enum oc2gbts_power_source sensor_source; + enum oc2gbts_power_type sensor_type; + enum oc2gbts_par ee_par; +} power_data[] = { + { + .name = "supply_volt", + .has_max = 1, + .sensor_source = OC2GBTS_POWER_SUPPLY, + .sensor_type = OC2GBTS_POWER_VOLTAGE, + .ee_par = OC2GBTS_PAR_VOLT_SUPPLY_MAX, + }, { + .name = "supply_pwr", + .has_max = 1, + .sensor_source = OC2GBTS_POWER_SUPPLY, + .sensor_type = OC2GBTS_POWER_POWER, + .ee_par = OC2GBTS_PAR_PWR_SUPPLY_MAX, + }, { + .name = "pa_pwr", + .has_max = 1, + .sensor_source = OC2GBTS_POWER_PA, + .sensor_type = OC2GBTS_POWER_POWER, + .ee_par = OC2GBTS_PAR_PWR_PA_MAX, + } +}; + +static const struct { + const char *name; + int has_max; + enum oc2gbts_vswr_sensor sensor; + enum oc2gbts_par ee_par; +} vswr_data[] = { + { + .name = "vswr", + .has_max = 0, + .sensor = OC2GBTS_VSWR, + .ee_par = OC2GBTS_PAR_VSWR_MAX, + } +}; + +static const struct value_string power_unit_strs[] = { + { OC2GBTS_POWER_POWER, "W" }, + { OC2GBTS_POWER_VOLTAGE, "V" }, + { 0, NULL } +}; + +void oc2gbts_check_temp(int no_rom_write) +{ + int temp_old[ARRAY_SIZE(temp_data)]; + int temp_cur[ARRAY_SIZE(temp_data)]; + int i, rc; + + for (i = 0; i < ARRAY_SIZE(temp_data); i++) { + int ret = -99; + + if (temp_data[i].sensor == OC2GBTS_TEMP_PA && + !oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) + continue; + + rc = oc2gbts_par_get_int(temp_data[i].ee_par, &ret); + temp_old[i] = ret * 1000; + + oc2gbts_temp_get(temp_data[i].sensor, &temp_cur[i]); + if (temp_cur[i] < 0 && temp_cur[i] > -1000) { + LOGP(DTEMP, LOGL_ERROR, "Error reading temperature (%d)\n", temp_data[i].sensor); + continue; + } + + LOGP(DTEMP, LOGL_DEBUG, "Current %s temperature: %d.%d C\n", + temp_data[i].name, temp_cur[i]/1000, temp_cur[i]%1000); + + if (temp_cur[i] > temp_old[i]) { + LOGP(DTEMP, LOGL_NOTICE, "New maximum %s " + "temperature: %d.%d C\n", temp_data[i].name, + temp_cur[i]/1000, temp_old[i]%1000); + + if (!no_rom_write) { + rc = oc2gbts_par_set_int(temp_data[i].ee_par, + temp_cur[i]/1000); + if (rc < 0) + LOGP(DTEMP, LOGL_ERROR, "error writing new %s " + "max temp %d (%s)\n", temp_data[i].name, + rc, strerror(errno)); + } + } + } +} + +void oc2gbts_check_power(int no_rom_write) +{ + int power_old[ARRAY_SIZE(power_data)]; + int power_cur[ARRAY_SIZE(power_data)]; + int i, rc; + int div_ratio; + + for (i = 0; i < ARRAY_SIZE(power_data); i++) { + int ret = 0; + + if (power_data[i].sensor_source == OC2GBTS_POWER_PA && + !oc2gbts_option_get(OC2GBTS_OPTION_PA)) + continue; + + rc = oc2gbts_par_get_int(power_data[i].ee_par, &ret); + switch(power_data[i].sensor_type) { + case OC2GBTS_POWER_VOLTAGE: + div_ratio = 1000; + break; + case OC2GBTS_POWER_POWER: + div_ratio = 1000000; + break; + default: + div_ratio = 1000; + } + power_old[i] = ret * div_ratio; + + oc2gbts_power_sensor_get(power_data[i].sensor_source, power_data[i].sensor_type, &power_cur[i]); + if (power_cur[i] < 0 && power_cur[i] > -1000) { + LOGP(DTEMP, LOGL_ERROR, "Error reading power (%d) (%d)\n", power_data[i].sensor_source, power_data[i].sensor_type); + continue; + } + LOGP(DTEMP, LOGL_DEBUG, "Current %s power: %d.%d %s\n", + power_data[i].name, power_cur[i]/div_ratio, power_cur[i]%div_ratio, + get_value_string(power_unit_strs, power_data[i].sensor_type)); + + if (power_cur[i] > power_old[i]) { + LOGP(DTEMP, LOGL_NOTICE, "New maximum %s " + "power: %d.%d %s\n", power_data[i].name, + power_cur[i]/div_ratio, power_cur[i]%div_ratio, + get_value_string(power_unit_strs, power_data[i].sensor_type)); + + if (!no_rom_write) { + rc = oc2gbts_par_set_int(power_data[i].ee_par, + power_cur[i]/div_ratio); + if (rc < 0) + LOGP(DTEMP, LOGL_ERROR, "error writing new %s " + "max power %d (%s)\n", power_data[i].name, + rc, strerror(errno)); + } + } + } +} + +void oc2gbts_check_vswr(int no_rom_write) +{ + int vswr_old[ARRAY_SIZE(vswr_data)]; + int vswr_cur[ARRAY_SIZE(vswr_data)]; + int i, rc; + + for (i = 0; i < ARRAY_SIZE(vswr_data); i++) { + int ret = 0; + + if (vswr_data[i].sensor == OC2GBTS_VSWR && + (!oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) || + !oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL))) + continue; + + rc = oc2gbts_par_get_int(vswr_data[i].ee_par, &ret); + vswr_old[i] = ret * 1000; + + oc2gbts_vswr_get(vswr_data[i].sensor, &vswr_cur[i]); + if (vswr_cur[i] < 0 && vswr_cur[i] > -1000) { + LOGP(DTEMP, LOGL_ERROR, "Error reading vswr (%d)\n", vswr_data[i].sensor); + continue; + } + + LOGP(DTEMP, LOGL_DEBUG, "Current %s vswr: %d.%d\n", + vswr_data[i].name, vswr_cur[i]/1000, vswr_cur[i]%1000); + + if (vswr_cur[i] > vswr_old[i]) { + LOGP(DTEMP, LOGL_NOTICE, "New maximum %s " + "vswr: %d.%d C\n", vswr_data[i].name, + vswr_cur[i]/1000, vswr_old[i]%1000); + + if (!no_rom_write) { + rc = oc2gbts_par_set_int(vswr_data[i].ee_par, + vswr_cur[i]/1000); + if (rc < 0) + LOGP(DTEMP, LOGL_ERROR, "error writing new %s " + "max vswr %d (%s)\n", vswr_data[i].name, + rc, strerror(errno)); + } + } + } +} + +/********************************************************************* + * Hours handling + *********************************************************************/ +static time_t last_update; + +int oc2gbts_update_hours(int no_rom_write) +{ + time_t now = time(NULL); + int rc, op_hrs = 0; + + /* first time after start of manager program */ + if (last_update == 0) { + last_update = now; + + rc = oc2gbts_par_get_int(OC2GBTS_PAR_HOURS, &op_hrs); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, "Unable to read " + "operational hours: %d (%s)\n", rc, + strerror(errno)); + /* create a new file anyway */ + if (!no_rom_write) + rc = oc2gbts_par_set_int(OC2GBTS_PAR_HOURS, op_hrs); + + return rc; + } + + LOGP(DTEMP, LOGL_INFO, "Total hours of Operation: %u\n", + op_hrs); + + return 0; + } + + if (now >= last_update + 3600) { + rc = oc2gbts_par_get_int(OC2GBTS_PAR_HOURS, &op_hrs); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, "Unable to read " + "operational hours: %d (%s)\n", rc, + strerror(errno)); + return rc; + } + + /* number of hours to increase */ + op_hrs += (now-last_update)/3600; + + LOGP(DTEMP, LOGL_INFO, "Total hours of Operation: %u\n", + op_hrs); + + if (!no_rom_write) { + rc = oc2gbts_par_set_int(OC2GBTS_PAR_HOURS, op_hrs); + if (rc < 0) + return rc; + } + + last_update = now; + } + + return 0; +} + +/********************************************************************* + * Firmware reloading + *********************************************************************/ + +static const char *fw_sysfs[_NUM_FW] = { + [OC2GBTS_FW_DSP] = "/sys/kernel/debug/remoteproc/remoteproc0/recovery", +}; + +int oc2gbts_firmware_reload(enum oc2gbts_firmware_type type) +{ + int fd; + int rc; + + switch (type) { + case OC2GBTS_FW_DSP: + fd = open(fw_sysfs[type], O_WRONLY); + if (fd < 0) { + LOGP(DFW, LOGL_ERROR, "unable ot open firmware device %s: %s\n", + fw_sysfs[type], strerror(errno)); + close(fd); + return fd; + } + rc = write(fd, "restart", 8); + if (rc < 8) { + LOGP(DFW, LOGL_ERROR, "short write during " + "fw write to %s\n", fw_sysfs[type]); + close(fd); + return -EIO; + } + close(fd); + default: + return -EINVAL; + } + return 0; +} diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_misc.h b/src/osmo-bts-oc2g/misc/oc2gbts_misc.h new file mode 100644 index 000000000..783156798 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_misc.h @@ -0,0 +1,17 @@ +#ifndef _OC2GBTS_MISC_H +#define _OC2GBTS_MISC_H + +#include + +void oc2gbts_check_temp(int no_rom_write); +void oc2gbts_check_power(int no_rom_write); +void oc2gbts_check_vswr(int no_rom_write); + +int oc2gbts_update_hours(int no_rom_write); + +enum oc2gbts_firmware_type { + OC2GBTS_FW_DSP, + _NUM_FW +}; + +#endif diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_nl.c b/src/osmo-bts-oc2g/misc/oc2gbts_nl.c new file mode 100644 index 000000000..39f64aaec --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_nl.c @@ -0,0 +1,123 @@ +/* Helper for netlink */ + +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * sysmobts_nl.c + * (C) 2014 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +#define NLMSG_TAIL(nmsg) \ + ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) + +/** + * In case one binds to 0.0.0.0/INADDR_ANY and wants to know which source + * address will be used when sending a message this function can be used. + * It will ask the routing code of the kernel for the PREFSRC + */ +int source_for_dest(const struct in_addr *dest, struct in_addr *loc_source) +{ + int fd, rc; + struct rtmsg *r; + struct rtattr *rta; + struct { + struct nlmsghdr n; + struct rtmsg r; + char buf[1024]; + } req; + + memset(&req, 0, sizeof(req)); + + fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_ROUTE); + if (fd < 0) { + perror("nl socket"); + return -1; + } + + /* Send a rtmsg and ask for a response */ + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + req.n.nlmsg_type = RTM_GETROUTE; + req.n.nlmsg_seq = 1; + + /* Prepare the routing request */ + req.r.rtm_family = AF_INET; + + /* set the dest */ + rta = NLMSG_TAIL(&req.n); + rta->rta_type = RTA_DST; + rta->rta_len = RTA_LENGTH(sizeof(*dest)); + memcpy(RTA_DATA(rta), dest, sizeof(*dest)); + + /* update sizes for dest */ + req.r.rtm_dst_len = sizeof(*dest) * 8; + req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + RTA_ALIGN(rta->rta_len); + + rc = send(fd, &req, req.n.nlmsg_len, 0); + if (rc != req.n.nlmsg_len) { + perror("short write"); + close(fd); + return -2; + } + + + /* now receive a response and parse it */ + rc = recv(fd, &req, sizeof(req), 0); + if (rc <= 0) { + perror("short read"); + close(fd); + return -3; + } + + if (!NLMSG_OK(&req.n, rc) || req.n.nlmsg_type != RTM_NEWROUTE) { + close(fd); + return -4; + } + + r = NLMSG_DATA(&req.n); + rc -= NLMSG_LENGTH(sizeof(*r)); + rta = RTM_RTA(r); + while (RTA_OK(rta, rc)) { + if (rta->rta_type != RTA_PREFSRC) { + rta = RTA_NEXT(rta, rc); + continue; + } + + /* we are done */ + memcpy(loc_source, RTA_DATA(rta), RTA_PAYLOAD(rta)); + close(fd); + return 0; + } + + close(fd); + return -5; +} diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_nl.h b/src/osmo-bts-oc2g/misc/oc2gbts_nl.h new file mode 100644 index 000000000..340cf1170 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_nl.h @@ -0,0 +1,27 @@ +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * sysmobts_nl.h + * (C) 2014 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#pragma once + +struct in_addr; + +int source_for_dest(const struct in_addr *dest, struct in_addr *loc_source); diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_par.c b/src/osmo-bts-oc2g/misc/oc2gbts_par.c new file mode 100644 index 000000000..f35502437 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_par.c @@ -0,0 +1,249 @@ +/* oc2gbts - access to hardware related parameters */ + +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * sysmobts_par.c + * (C) 2012 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "oc2gbts_par.h" + +const struct value_string oc2gbts_par_names[_NUM_OC2GBTS_PAR+1] = { + { OC2GBTS_PAR_TEMP_SUPPLY_MAX, "temp-supply-max" }, + { OC2GBTS_PAR_TEMP_SOC_MAX, "temp-soc-max" }, + { OC2GBTS_PAR_TEMP_FPGA_MAX, "temp-fpga-max" }, + { OC2GBTS_PAR_TEMP_RMSDET_MAX, "temp-rmsdet-max" }, + { OC2GBTS_PAR_TEMP_OCXO_MAX, "temp-ocxo-max" }, + { OC2GBTS_PAR_TEMP_TX_MAX, "temp-tx-max" }, + { OC2GBTS_PAR_TEMP_PA_MAX, "temp-pa-max" }, + { OC2GBTS_PAR_VOLT_SUPPLY_MAX, "volt-supply-max" }, + { OC2GBTS_PAR_PWR_SUPPLY_MAX, "pwr-supply-max" }, + { OC2GBTS_PAR_PWR_PA_MAX, "pwr-pa-max" }, + { OC2GBTS_PAR_VSWR_MAX, "vswr-max" }, + { OC2GBTS_PAR_GPS_FIX, "gps-fix" }, + { OC2GBTS_PAR_SERNR, "serial-nr" }, + { OC2GBTS_PAR_HOURS, "hours-running" }, + { OC2GBTS_PAR_BOOTS, "boot-count" }, + { OC2GBTS_PAR_KEY, "key" }, + { 0, NULL } +}; + +int oc2gbts_par_is_int(enum oc2gbts_par par) +{ + switch (par) { + case OC2GBTS_PAR_TEMP_SUPPLY_MAX: + case OC2GBTS_PAR_TEMP_SOC_MAX: + case OC2GBTS_PAR_TEMP_FPGA_MAX: + case OC2GBTS_PAR_TEMP_RMSDET_MAX: + case OC2GBTS_PAR_TEMP_OCXO_MAX: + case OC2GBTS_PAR_TEMP_TX_MAX: + case OC2GBTS_PAR_TEMP_PA_MAX: + case OC2GBTS_PAR_VOLT_SUPPLY_MAX: + case OC2GBTS_PAR_VSWR_MAX: + case OC2GBTS_PAR_SERNR: + case OC2GBTS_PAR_HOURS: + case OC2GBTS_PAR_BOOTS: + case OC2GBTS_PAR_PWR_SUPPLY_MAX: + case OC2GBTS_PAR_PWR_PA_MAX: + return 1; + default: + return 0; + } +} + +FILE *oc2gbts_par_get_path(void *ctx, enum oc2gbts_par par, const char* mode) +{ + char *fpath; + FILE *fp; + + if (par >= _NUM_OC2GBTS_PAR) + return NULL; + + fpath = talloc_asprintf(ctx, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par)); + if (!fpath) + return NULL; + + fp = fopen(fpath, mode); + if (!fp) + fprintf(stderr, "Failed to open %s due to '%s' error\n", fpath, strerror(errno)); + + talloc_free(fpath); + + return fp; +} + +int oc2gbts_par_get_int(enum oc2gbts_par par, int *ret) +{ + char fpath[PATH_MAX]; + FILE *fp; + int rc; + + if (par >= _NUM_OC2GBTS_PAR) + return -ENODEV; + + snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par)); + fpath[sizeof(fpath)-1] = '\0'; + + fp = fopen(fpath, "r"); + if (fp == NULL) { + return -errno; + } + + rc = fscanf(fp, "%d", ret); + if (rc != 1) { + fclose(fp); + return -EIO; + } + fclose(fp); + return 0; +} + +int oc2gbts_par_set_int(enum oc2gbts_par par, int val) +{ + char fpath[PATH_MAX]; + FILE *fp; + int rc; + + if (par >= _NUM_OC2GBTS_PAR) + return -ENODEV; + + snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par)); + fpath[sizeof(fpath)-1] = '\0'; + + fp = fopen(fpath, "w"); + if (fp == NULL) { + return -errno; + } + + rc = fprintf(fp, "%d", val); + if (rc < 0) { + fclose(fp); + return -EIO; + } + fsync(fp); + fclose(fp); + return 0; +} + +int oc2gbts_par_get_buf(enum oc2gbts_par par, uint8_t *buf, + unsigned int size) +{ + char fpath[PATH_MAX]; + FILE *fp; + int rc; + + if (par >= _NUM_OC2GBTS_PAR) + return -ENODEV; + + snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par)); + fpath[sizeof(fpath)-1] = '\0'; + + fp = fopen(fpath, "rb"); + if (fp == NULL) { + return -errno; + } + + rc = fread(buf, 1, size, fp); + + fclose(fp); + + return rc; +} + +int oc2gbts_par_set_buf(enum oc2gbts_par par, const uint8_t *buf, + unsigned int size) +{ + char fpath[PATH_MAX]; + FILE *fp; + int rc; + + if (par >= _NUM_OC2GBTS_PAR) + return -ENODEV; + + snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par)); + fpath[sizeof(fpath)-1] = '\0'; + + fp = fopen(fpath, "wb"); + if (fp == NULL) { + return -errno; + } + + rc = fwrite(buf, 1, size, fp); + + fsync(fp); + fclose(fp); + + return rc; +} + +int oc2gbts_par_get_gps_fix(void *ctx, time_t *ret) +{ + FILE *fp; + int rc; + + fp = oc2gbts_par_get_path(ctx, OC2GBTS_PAR_GPS_FIX, "r"); + if (fp == NULL) { + return -errno; + } + + rc = fscanf(fp, "%ld", ret); + if (rc != 1) { + fclose(fp); + return -EIO; + } + fclose(fp); + + return 0; +} + +int oc2gbts_par_set_gps_fix(void *ctx, time_t val) +{ + FILE *fp; + int rc; + + fp = oc2gbts_par_get_path(ctx, OC2GBTS_PAR_GPS_FIX, "w"); + if (fp == NULL) { + return -errno; + } + + rc = fprintf(fp, "%ld", val); + if (rc < 0) { + fclose(fp); + return -EIO; + } + fsync(fp); + fclose(fp); + + return 0; +} diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_par.h b/src/osmo-bts-oc2g/misc/oc2gbts_par.h new file mode 100644 index 000000000..588a3c322 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_par.h @@ -0,0 +1,43 @@ +#ifndef _OC2GBTS_PAR_H +#define _OC2GBTS_PAR_H + +#include + +#define FACTORY_ROM_PATH "/mnt/rom/factory" +#define USER_ROM_PATH "/var/run/oc2gbts-mgr" +#define UPTIME_TMP_PATH "/tmp/uptime" + +enum oc2gbts_par { + OC2GBTS_PAR_TEMP_SUPPLY_MAX, + OC2GBTS_PAR_TEMP_SOC_MAX, + OC2GBTS_PAR_TEMP_FPGA_MAX, + OC2GBTS_PAR_TEMP_RMSDET_MAX, + OC2GBTS_PAR_TEMP_OCXO_MAX, + OC2GBTS_PAR_TEMP_TX_MAX, + OC2GBTS_PAR_TEMP_PA_MAX, + OC2GBTS_PAR_VOLT_SUPPLY_MAX, + OC2GBTS_PAR_PWR_SUPPLY_MAX, + OC2GBTS_PAR_PWR_PA_MAX, + OC2GBTS_PAR_VSWR_MAX, + OC2GBTS_PAR_GPS_FIX, + OC2GBTS_PAR_SERNR, + OC2GBTS_PAR_HOURS, + OC2GBTS_PAR_BOOTS, + OC2GBTS_PAR_KEY, + _NUM_OC2GBTS_PAR +}; + +extern const struct value_string oc2gbts_par_names[_NUM_OC2GBTS_PAR+1]; + +int oc2gbts_par_get_int(enum oc2gbts_par par, int *ret); +int oc2gbts_par_set_int(enum oc2gbts_par par, int val); +int oc2gbts_par_get_buf(enum oc2gbts_par par, uint8_t *buf, + unsigned int size); +int oc2gbts_par_set_buf(enum oc2gbts_par par, const uint8_t *buf, + unsigned int size); + +int oc2gbts_par_is_int(enum oc2gbts_par par); +int oc2gbts_par_get_gps_fix(void *ctx, time_t *ret); +int oc2gbts_par_set_gps_fix(void *ctx, time_t val); + +#endif diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_power.c b/src/osmo-bts-oc2g/misc/oc2gbts_power.c new file mode 100644 index 000000000..4e2fc95a9 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_power.c @@ -0,0 +1,177 @@ +/* Copyright (C) 2015 by Yves Godin + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "oc2gbts_power.h" + +static const char *power_enable_devs[_NUM_POWER_SOURCES] = { + [OC2GBTS_POWER_PA] = "/var/oc2g/pa-state/pa0/state", +}; + +static const char *power_sensor_devs[_NUM_POWER_SOURCES] = { + [OC2GBTS_POWER_SUPPLY] = "/var/oc2g/pwr-sense/main-supply/", + [OC2GBTS_POWER_PA] = "/var/oc2g/pwr-sense/pa0/", +}; + +static const char *power_sensor_type_str[_NUM_POWER_TYPES] = { + [OC2GBTS_POWER_POWER] = "power", + [OC2GBTS_POWER_VOLTAGE] = "voltage", + [OC2GBTS_POWER_CURRENT] = "current", +}; + +int oc2gbts_power_sensor_get( + enum oc2gbts_power_source source, + enum oc2gbts_power_type type, + int *power) +{ + char buf[PATH_MAX]; + char pwrstr[10]; + int fd, rc; + + if (source >= _NUM_POWER_SOURCES) + return -EINVAL; + + if (type >= _NUM_POWER_TYPES) + return -EINVAL; + + snprintf(buf, sizeof(buf)-1, "%s%s", power_sensor_devs[source], power_sensor_type_str[type]); + buf[sizeof(buf)-1] = '\0'; + + fd = open(buf, O_RDONLY); + if (fd < 0) + return fd; + + rc = read(fd, pwrstr, sizeof(pwrstr)); + pwrstr[sizeof(pwrstr)-1] = '\0'; + if (rc < 0) { + close(fd); + return rc; + } + if (rc == 0) { + close(fd); + return -EIO; + } + close(fd); + *power = atoi(pwrstr); + return 0; +} + + +int oc2gbts_power_set( + enum oc2gbts_power_source source, + int en) +{ + int fd; + int rc; + + if (source != OC2GBTS_POWER_PA) { + return -EINVAL; + } + + fd = open(power_enable_devs[source], O_WRONLY); + if (fd < 0) { + return fd; + } + rc = write(fd, en?"1":"0", 2); + close( fd ); + + if (rc != 2) { + return -1; + } + + if (en) usleep(50*1000); + + return 0; +} + +int oc2gbts_power_get( + enum oc2gbts_power_source source) +{ + int fd; + int rc; + int retVal = 0; + char enstr[10]; + + fd = open(power_enable_devs[source], O_RDONLY); + if (fd < 0) { + return fd; + } + + rc = read(fd, enstr, sizeof(enstr)); + enstr[rc-1] = '\0'; + + close(fd); + + if (rc < 0) { + return rc; + } + if (rc == 0) { + return -EIO; + } + + rc = strcmp(enstr, "enabled"); + if(rc == 0) { + retVal = 1; + } + + return retVal; +} + +static const char *vswr_devs[_NUM_VSWR_SENSORS] = { + [OC2GBTS_VSWR] = "/var/oc2g/vswr/tx0/vswr", +}; + +int oc2gbts_vswr_get(enum oc2gbts_vswr_sensor sensor, int *vswr) +{ + char buf[PATH_MAX]; + char vswrstr[8]; + int fd, rc; + + if (sensor < 0 || sensor >= _NUM_VSWR_SENSORS) + return -EINVAL; + + snprintf(buf, sizeof(buf)-1, "%s", vswr_devs[sensor]); + buf[sizeof(buf)-1] = '\0'; + + fd = open(buf, O_RDONLY); + if (fd < 0) + return fd; + + rc = read(fd, vswrstr, sizeof(vswrstr)); + vswrstr[sizeof(vswrstr)-1] = '\0'; + if (rc < 0) { + close(fd); + return rc; + } + if (rc == 0) { + close(fd); + return -EIO; + } + close(fd); + *vswr = atoi(vswrstr); + return 0; +} diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_power.h b/src/osmo-bts-oc2g/misc/oc2gbts_power.h new file mode 100644 index 000000000..3229f2435 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_power.h @@ -0,0 +1,36 @@ +#ifndef _OC2GBTS_POWER_H +#define _OC2GBTS_POWER_H + +enum oc2gbts_power_source { + OC2GBTS_POWER_SUPPLY, + OC2GBTS_POWER_PA, + _NUM_POWER_SOURCES +}; + +enum oc2gbts_power_type { + OC2GBTS_POWER_POWER, + OC2GBTS_POWER_VOLTAGE, + OC2GBTS_POWER_CURRENT, + _NUM_POWER_TYPES +}; + +int oc2gbts_power_sensor_get( + enum oc2gbts_power_source source, + enum oc2gbts_power_type type, + int *volt); + +int oc2gbts_power_set( + enum oc2gbts_power_source source, + int en); + +int oc2gbts_power_get( + enum oc2gbts_power_source source); + +enum oc2gbts_vswr_sensor { + OC2GBTS_VSWR, + _NUM_VSWR_SENSORS +}; + +int oc2gbts_vswr_get(enum oc2gbts_vswr_sensor sensor, int *vswr); + +#endif diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_swd.c b/src/osmo-bts-oc2g/misc/oc2gbts_swd.c new file mode 100644 index 000000000..59b795ac0 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_swd.c @@ -0,0 +1,178 @@ +/* Systemd service wd notification for OC-2G BTS management daemon */ + +/* Copyright (C) 2015 by Yves Godin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include "misc/oc2gbts_mgr.h" +#include "misc/oc2gbts_swd.h" +#include + +/* Needed for service watchdog notification */ +#include + +/* This is the period used to verify if all events have been registered to be allowed + to notify the systemd service watchdog +*/ +#define SWD_PERIOD 30 + +static void swd_start(struct oc2gbts_mgr_instance *mgr); +static void swd_process(struct oc2gbts_mgr_instance *mgr); +static void swd_close(struct oc2gbts_mgr_instance *mgr); +static void swd_state_reset(struct oc2gbts_mgr_instance *mgr, int reason); +static int swd_run(struct oc2gbts_mgr_instance *mgr, int from_loop); +static void swd_loop_run(void *_data); + +enum swd_state { + SWD_INITIAL, + SWD_IN_PROGRESS, +}; + +enum swd_result { + SWD_FAIL_START, + SWD_FAIL_NOTIFY, + SWD_SUCCESS, +}; + +static void swd_start(struct oc2gbts_mgr_instance *mgr) +{ + swd_process(mgr); +} + +static void swd_process(struct oc2gbts_mgr_instance *mgr) +{ + int rc = 0, notify = 0; + + /* Did we get all needed conditions ? */ + if (mgr->swd.swd_eventmasks == mgr->swd.swd_events) { + /* Ping systemd service wd if enabled */ + rc = sd_notify(0, "WATCHDOG=1"); + LOGP(DSWD, LOGL_INFO, "Watchdog notification attempt\n"); + notify = 1; + } + else { + LOGP(DSWD, LOGL_INFO, "Missing watchdog events: e:0x%016llx,m:0x%016llx\n",mgr->swd.swd_events,mgr->swd.swd_eventmasks); + } + + if (rc < 0) { + LOGP(DSWD, LOGL_ERROR, + "Failed to notify system service watchdog: %d\n", rc); + swd_state_reset(mgr, SWD_FAIL_NOTIFY); + return; + } + else { + /* Did we notified the watchdog? */ + if (notify) { + mgr->swd.swd_events = 0; + /* Makes sure we really cleared it in case any event was notified at this same moment (it would be lost) */ + if (mgr->swd.swd_events != 0) + mgr->swd.swd_events = 0; + } + } + + swd_state_reset(mgr, SWD_SUCCESS); + return; +} + +static void swd_close(struct oc2gbts_mgr_instance *mgr) +{ +} + +static void swd_state_reset(struct oc2gbts_mgr_instance *mgr, int outcome) +{ + if (mgr->swd.swd_from_loop) { + mgr->swd.swd_timeout.data = mgr; + mgr->swd.swd_timeout.cb = swd_loop_run; + osmo_timer_schedule(&mgr->swd.swd_timeout, SWD_PERIOD, 0); + } + + mgr->swd.state = SWD_INITIAL; + swd_close(mgr); +} + +static int swd_run(struct oc2gbts_mgr_instance *mgr, int from_loop) +{ + if (mgr->swd.state != SWD_INITIAL) { + LOGP(DSWD, LOGL_ERROR, "Swd is already in progress.\n"); + return -1; + } + + mgr->swd.swd_from_loop = from_loop; + + /* From now on everything will be handled from the failure */ + mgr->swd.state = SWD_IN_PROGRESS; + swd_start(mgr); + return 0; +} + +static void swd_loop_run(void *_data) +{ + int rc; + struct oc2gbts_mgr_instance *mgr = _data; + + LOGP(DSWD, LOGL_INFO, "Going to check for watchdog notification.\n"); + rc = swd_run(mgr, 1); + if (rc != 0) { + swd_state_reset(mgr, SWD_FAIL_START); + } +} + +/* 'swd_num_events' configures the number of events to be monitored before notifying the + systemd service watchdog. It must be in the range of [1,64]. Events are notified + through the function 'oc2gbts_swd_event' +*/ +int oc2gbts_swd_init(struct oc2gbts_mgr_instance *mgr, int swd_num_events) +{ + /* Checks for a valid number of events to validate */ + if (swd_num_events < 1 || swd_num_events > 64) + return(-1); + + mgr->swd.state = SWD_INITIAL; + mgr->swd.swd_timeout.data = mgr; + mgr->swd.swd_timeout.cb = swd_loop_run; + osmo_timer_schedule(&mgr->swd.swd_timeout, 0, 0); + + if (swd_num_events == 64){ + mgr->swd.swd_eventmasks = 0xffffffffffffffffULL; + } + else { + mgr->swd.swd_eventmasks = ((1ULL << swd_num_events) - 1); + } + mgr->swd.swd_events = 0; + mgr->swd.num_events = swd_num_events; + + return 0; +} + +/* Notifies that the specified event 'swd_event' happened correctly; + the value must be in the range of [0,'swd_num_events'[ (see oc2gbts_swd_init). + For example, if 'swd_num_events' was 64, 'swd_event' events are numbered 0 to 63. + WARNING: if this function can be used from multiple threads at the same time, + it must be protected with a kind of mutex to avoid loosing event notification. +*/ +int oc2gbts_swd_event(struct oc2gbts_mgr_instance *mgr, enum mgr_swd_events swd_event) +{ + /* Checks for a valid specified event (smaller than max possible) */ + if ((int)(swd_event) < 0 || (int)(swd_event) >= mgr->swd.num_events) + return(-1); + + mgr->swd.swd_events = mgr->swd.swd_events | ((unsigned long long int)(1) << (int)(swd_event)); + + /* !!! Uncomment following line to debug events notification */ + LOGP(DSWD, LOGL_DEBUG,"Swd event notified: %d\n", (int)(swd_event)); + + return 0; +} diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_swd.h b/src/osmo-bts-oc2g/misc/oc2gbts_swd.h new file mode 100644 index 000000000..313712ac0 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_swd.h @@ -0,0 +1,7 @@ +#ifndef _OC2GBTS_SWD_H +#define _OC2GBTS_SWD_H + +int oc2gbts_swd_init(struct oc2gbts_mgr_instance *mgr, int swd_num_events); +int oc2gbts_swd_event(struct oc2gbts_mgr_instance *mgr, enum mgr_swd_events swd_event); + +#endif diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_temp.c b/src/osmo-bts-oc2g/misc/oc2gbts_temp.c new file mode 100644 index 000000000..8425dda34 --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_temp.c @@ -0,0 +1,71 @@ +/* Copyright (C) 2015 by Yves Godin + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "oc2gbts_temp.h" + +static const char *temp_devs[_NUM_TEMP_SENSORS] = { + [OC2GBTS_TEMP_SUPPLY] = "/var/oc2g/temp/main-supply/temp", + [OC2GBTS_TEMP_SOC] = "/var/oc2g/temp/cpu/temp", + [OC2GBTS_TEMP_FPGA] = "/var/oc2g/temp/fpga/temp", + [OC2GBTS_TEMP_RMSDET] = "/var/oc2g/temp/rmsdet/temp", + [OC2GBTS_TEMP_OCXO] = "/var/oc2g/temp/ocxo/temp", + [OC2GBTS_TEMP_TX] = "/var/oc2g/temp/tx0/temp", + [OC2GBTS_TEMP_PA] = "/var/oc2g/temp/pa0/temp", +}; + +int oc2gbts_temp_get(enum oc2gbts_temp_sensor sensor, int *temp) +{ + char buf[PATH_MAX]; + char tempstr[8]; + int fd, rc; + + if (sensor < 0 || sensor >= _NUM_TEMP_SENSORS) + return -EINVAL; + + snprintf(buf, sizeof(buf)-1, "%s", temp_devs[sensor]); + buf[sizeof(buf)-1] = '\0'; + + fd = open(buf, O_RDONLY); + if (fd < 0) + return fd; + + rc = read(fd, tempstr, sizeof(tempstr)); + tempstr[sizeof(tempstr)-1] = '\0'; + if (rc < 0) { + close(fd); + return rc; + } + if (rc == 0) { + close(fd); + return -EIO; + } + close(fd); + *temp = atoi(tempstr); + return 0; +} diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_temp.h b/src/osmo-bts-oc2g/misc/oc2gbts_temp.h new file mode 100644 index 000000000..6d5dfca8b --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_temp.h @@ -0,0 +1,26 @@ +#ifndef _OC2GBTS_TEMP_H +#define _OC2GBTS_TEMP_H + +enum oc2gbts_temp_sensor { + OC2GBTS_TEMP_SUPPLY, + OC2GBTS_TEMP_SOC, + OC2GBTS_TEMP_FPGA, + OC2GBTS_TEMP_RMSDET, + OC2GBTS_TEMP_OCXO, + OC2GBTS_TEMP_TX, + OC2GBTS_TEMP_PA, + _NUM_TEMP_SENSORS +}; + +enum oc2gbts_temp_type { + OC2GBTS_TEMP_INPUT, + OC2GBTS_TEMP_LOWEST, + OC2GBTS_TEMP_HIGHEST, + OC2GBTS_TEMP_FAULT, + _NUM_TEMP_TYPES +}; + +int oc2gbts_temp_get(enum oc2gbts_temp_sensor sensor, int *temp); + + +#endif diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_util.c b/src/osmo-bts-oc2g/misc/oc2gbts_util.c new file mode 100644 index 000000000..b71f0383a --- /dev/null +++ b/src/osmo-bts-oc2g/misc/oc2gbts_util.c @@ -0,0 +1,158 @@ +/* oc2gbts-util - access to hardware related parameters */ + +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * sysmobts_misc.c + * (C) 2012-2013 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +#include +#include +#include +#include +#include +#include + + +#include "oc2gbts_par.h" + +enum act { + ACT_GET, + ACT_SET, +}; + +static enum act action; +static char *write_arg; +static int void_warranty; + +static void print_help() +{ + const struct value_string *par = oc2gbts_par_names; + + printf("oc2gbts-util [--void-warranty -r | -w value] param_name\n"); + printf("Possible param names:\n"); + + for (; par->str != NULL; par += 1) { + if (!oc2gbts_par_is_int(par->value)) + continue; + printf(" %s\n", par->str); + } +} + +static int parse_options(int argc, char **argv) +{ + while (1) { + int option_idx = 0, c; + static const struct option long_options[] = { + { "help", 0, 0, 'h' }, + { "read", 0, 0, 'r' }, + { "void-warranty", 0, 0, 1000}, + { "write", 1, 0, 'w' }, + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "rw:h", + long_options, &option_idx); + if (c == -1) + break; + switch (c) { + case 'r': + action = ACT_GET; + break; + case 'w': + action = ACT_SET; + write_arg = optarg; + break; + case 'h': + print_help(); + return -1; + break; + case 1000: + printf("Will void warranty on write.\n"); + void_warranty = 1; + break; + default: + return -1; + } + } + + return 0; +} + +int main(int argc, char **argv) +{ + const char *parname; + enum oc2gbts_par par; + int rc, val; + + rc = parse_options(argc, argv); + if (rc < 0) + exit(2); + + if (optind >= argc) { + fprintf(stderr, "You must specify the parameter name\n"); + exit(2); + } + parname = argv[optind]; + + rc = get_string_value(oc2gbts_par_names, parname); + if (rc < 0) { + fprintf(stderr, "`%s' is not a valid parameter\n", parname); + exit(2); + } else + par = rc; + + switch (action) { + case ACT_GET: + rc = oc2gbts_par_get_int(par, &val); + if (rc < 0) { + fprintf(stderr, "Error %d\n", rc); + goto err; + } + printf("%d\n", val); + break; + case ACT_SET: + rc = oc2gbts_par_get_int(par, &val); + if (rc < 0) { + fprintf(stderr, "Error %d\n", rc); + goto err; + } + if (val != 0xFFFF && val != 0xFF && val != 0xFFFFFFFF && !void_warranty) { + fprintf(stderr, "Parameter is already set!\r\n"); + goto err; + } + rc = oc2gbts_par_set_int(par, atoi(write_arg)); + if (rc < 0) { + fprintf(stderr, "Error %d\n", rc); + goto err; + } + printf("Success setting %s=%d\n", parname, + atoi(write_arg)); + break; + default: + fprintf(stderr, "Unsupported action\n"); + goto err; + } + + exit(0); + +err: + exit(1); +} + diff --git a/src/osmo-bts-oc2g/oc2gbts.c b/src/osmo-bts-oc2g/oc2gbts.c new file mode 100644 index 000000000..012d705c0 --- /dev/null +++ b/src/osmo-bts-oc2g/oc2gbts.c @@ -0,0 +1,361 @@ +/* NuRAN Wireless OC-2G L1 API related definitions */ + +/* Copyright (C) 2015 by Yves Godin + * based on: + * sysmobts.c + * (C) 2011 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +#include "oc2gbts.h" + +enum l1prim_type oc2gbts_get_l1prim_type(GsmL1_PrimId_t id) +{ + switch (id) { + case GsmL1_PrimId_MphInitReq: return L1P_T_REQ; + case GsmL1_PrimId_MphCloseReq: return L1P_T_REQ; + case GsmL1_PrimId_MphConnectReq: return L1P_T_REQ; + case GsmL1_PrimId_MphDisconnectReq: return L1P_T_REQ; + case GsmL1_PrimId_MphActivateReq: return L1P_T_REQ; + case GsmL1_PrimId_MphDeactivateReq: return L1P_T_REQ; + case GsmL1_PrimId_MphConfigReq: return L1P_T_REQ; + case GsmL1_PrimId_MphMeasureReq: return L1P_T_REQ; + case GsmL1_PrimId_MphInitCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphCloseCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphConnectCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphDisconnectCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphActivateCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphDeactivateCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphConfigCnf: return L1P_T_CONF; + case GsmL1_PrimId_MphMeasureCnf: return L1P_T_CONF; + case GsmL1_PrimId_PhEmptyFrameReq: return L1P_T_REQ; + case GsmL1_PrimId_PhDataReq: return L1P_T_REQ; + case GsmL1_PrimId_MphTimeInd: return L1P_T_IND; + case GsmL1_PrimId_MphSyncInd: return L1P_T_IND; + case GsmL1_PrimId_PhConnectInd: return L1P_T_IND; + case GsmL1_PrimId_PhReadyToSendInd: return L1P_T_IND; + case GsmL1_PrimId_PhDataInd: return L1P_T_IND; + case GsmL1_PrimId_PhRaInd: return L1P_T_IND; + default: return L1P_T_INVALID; + } +} + +const struct value_string oc2gbts_l1prim_names[GsmL1_PrimId_NUM+1] = { + { GsmL1_PrimId_MphInitReq, "MPH-INIT.req" }, + { GsmL1_PrimId_MphCloseReq, "MPH-CLOSE.req" }, + { GsmL1_PrimId_MphConnectReq, "MPH-CONNECT.req" }, + { GsmL1_PrimId_MphDisconnectReq,"MPH-DISCONNECT.req" }, + { GsmL1_PrimId_MphActivateReq, "MPH-ACTIVATE.req" }, + { GsmL1_PrimId_MphDeactivateReq,"MPH-DEACTIVATE.req" }, + { GsmL1_PrimId_MphConfigReq, "MPH-CONFIG.req" }, + { GsmL1_PrimId_MphMeasureReq, "MPH-MEASURE.req" }, + { GsmL1_PrimId_MphInitCnf, "MPH-INIT.conf" }, + { GsmL1_PrimId_MphCloseCnf, "MPH-CLOSE.conf" }, + { GsmL1_PrimId_MphConnectCnf, "MPH-CONNECT.conf" }, + { GsmL1_PrimId_MphDisconnectCnf,"MPH-DISCONNECT.conf" }, + { GsmL1_PrimId_MphActivateCnf, "MPH-ACTIVATE.conf" }, + { GsmL1_PrimId_MphDeactivateCnf,"MPH-DEACTIVATE.conf" }, + { GsmL1_PrimId_MphConfigCnf, "MPH-CONFIG.conf" }, + { GsmL1_PrimId_MphMeasureCnf, "MPH-MEASURE.conf" }, + { GsmL1_PrimId_MphTimeInd, "MPH-TIME.ind" }, + { GsmL1_PrimId_MphSyncInd, "MPH-SYNC.ind" }, + { GsmL1_PrimId_PhEmptyFrameReq, "PH-EMPTY_FRAME.req" }, + { GsmL1_PrimId_PhDataReq, "PH-DATA.req" }, + { GsmL1_PrimId_PhConnectInd, "PH-CONNECT.ind" }, + { GsmL1_PrimId_PhReadyToSendInd,"PH-READY_TO_SEND.ind" }, + { GsmL1_PrimId_PhDataInd, "PH-DATA.ind" }, + { GsmL1_PrimId_PhRaInd, "PH-RA.ind" }, + { 0, NULL } +}; + +GsmL1_PrimId_t oc2gbts_get_l1prim_conf(GsmL1_PrimId_t id) +{ + switch (id) { + case GsmL1_PrimId_MphInitReq: return GsmL1_PrimId_MphInitCnf; + case GsmL1_PrimId_MphCloseReq: return GsmL1_PrimId_MphCloseCnf; + case GsmL1_PrimId_MphConnectReq: return GsmL1_PrimId_MphConnectCnf; + case GsmL1_PrimId_MphDisconnectReq: return GsmL1_PrimId_MphDisconnectCnf; + case GsmL1_PrimId_MphActivateReq: return GsmL1_PrimId_MphActivateCnf; + case GsmL1_PrimId_MphDeactivateReq: return GsmL1_PrimId_MphDeactivateCnf; + case GsmL1_PrimId_MphConfigReq: return GsmL1_PrimId_MphConfigCnf; + case GsmL1_PrimId_MphMeasureReq: return GsmL1_PrimId_MphMeasureCnf; + default: return -1; // Weak + } +} + +enum l1prim_type oc2gbts_get_sysprim_type(Oc2g_PrimId_t id) +{ + switch (id) { + case Oc2g_PrimId_SystemInfoReq: return L1P_T_REQ; + case Oc2g_PrimId_SystemInfoCnf: return L1P_T_CONF; + case Oc2g_PrimId_SystemFailureInd: return L1P_T_IND; + case Oc2g_PrimId_ActivateRfReq: return L1P_T_REQ; + case Oc2g_PrimId_ActivateRfCnf: return L1P_T_CONF; + case Oc2g_PrimId_DeactivateRfReq: return L1P_T_REQ; + case Oc2g_PrimId_DeactivateRfCnf: return L1P_T_CONF; + case Oc2g_PrimId_SetTraceFlagsReq: return L1P_T_REQ; + case Oc2g_PrimId_Layer1ResetReq: return L1P_T_REQ; + case Oc2g_PrimId_Layer1ResetCnf: return L1P_T_CONF; + case Oc2g_PrimId_SetCalibTblReq: return L1P_T_REQ; + case Oc2g_PrimId_SetCalibTblCnf: return L1P_T_CONF; + case Oc2g_PrimId_MuteRfReq: return L1P_T_REQ; + case Oc2g_PrimId_MuteRfCnf: return L1P_T_CONF; + case Oc2g_PrimId_SetRxAttenReq: return L1P_T_REQ; + case Oc2g_PrimId_SetRxAttenCnf: return L1P_T_CONF; + case Oc2g_PrimId_IsAliveReq: return L1P_T_REQ; + case Oc2g_PrimId_IsAliveCnf: return L1P_T_CONF; + case Oc2g_PrimId_SetMaxCellSizeReq: return L1P_T_REQ; + case Oc2g_PrimId_SetMaxCellSizeCnf: return L1P_T_CONF; + case Oc2g_PrimId_SetC0IdleSlotPowerReductionReq: return L1P_T_REQ; + case Oc2g_PrimId_SetC0IdleSlotPowerReductionCnf: return L1P_T_CONF; + default: return L1P_T_INVALID; + } +} + +const struct value_string oc2gbts_sysprim_names[Oc2g_PrimId_NUM+1] = { + { Oc2g_PrimId_SystemInfoReq, "SYSTEM-INFO.req" }, + { Oc2g_PrimId_SystemInfoCnf, "SYSTEM-INFO.conf" }, + { Oc2g_PrimId_SystemFailureInd, "SYSTEM-FAILURE.ind" }, + { Oc2g_PrimId_ActivateRfReq, "ACTIVATE-RF.req" }, + { Oc2g_PrimId_ActivateRfCnf, "ACTIVATE-RF.conf" }, + { Oc2g_PrimId_DeactivateRfReq, "DEACTIVATE-RF.req" }, + { Oc2g_PrimId_DeactivateRfCnf, "DEACTIVATE-RF.conf" }, + { Oc2g_PrimId_SetTraceFlagsReq, "SET-TRACE-FLAGS.req" }, + { Oc2g_PrimId_Layer1ResetReq, "LAYER1-RESET.req" }, + { Oc2g_PrimId_Layer1ResetCnf, "LAYER1-RESET.conf" }, + { Oc2g_PrimId_SetCalibTblReq, "SET-CALIB.req" }, + { Oc2g_PrimId_SetCalibTblCnf, "SET-CALIB.cnf" }, + { Oc2g_PrimId_MuteRfReq, "MUTE-RF.req" }, + { Oc2g_PrimId_MuteRfCnf, "MUTE-RF.cnf" }, + { Oc2g_PrimId_SetRxAttenReq, "SET-RX-ATTEN.req" }, + { Oc2g_PrimId_SetRxAttenCnf, "SET-RX-ATTEN-CNF.cnf" }, + { Oc2g_PrimId_IsAliveReq, "IS-ALIVE.req" }, + { Oc2g_PrimId_IsAliveCnf, "IS-ALIVE-CNF.cnf" }, + { Oc2g_PrimId_SetMaxCellSizeReq, "SET-MAX-CELL-SIZE.req" }, + { Oc2g_PrimId_SetMaxCellSizeCnf, "SET-MAX-CELL-SIZE.cnf" }, + { Oc2g_PrimId_SetC0IdleSlotPowerReductionReq, "SET-C0-IDLE-PWR-RED.req" }, + { Oc2g_PrimId_SetC0IdleSlotPowerReductionCnf, "SET-C0-IDLE-PWR-RED.cnf" }, + { 0, NULL } +}; + +Oc2g_PrimId_t oc2gbts_get_sysprim_conf(Oc2g_PrimId_t id) +{ + switch (id) { + case Oc2g_PrimId_SystemInfoReq: return Oc2g_PrimId_SystemInfoCnf; + case Oc2g_PrimId_ActivateRfReq: return Oc2g_PrimId_ActivateRfCnf; + case Oc2g_PrimId_DeactivateRfReq: return Oc2g_PrimId_DeactivateRfCnf; + case Oc2g_PrimId_Layer1ResetReq: return Oc2g_PrimId_Layer1ResetCnf; + case Oc2g_PrimId_SetCalibTblReq: return Oc2g_PrimId_SetCalibTblCnf; + case Oc2g_PrimId_MuteRfReq: return Oc2g_PrimId_MuteRfCnf; + case Oc2g_PrimId_SetRxAttenReq: return Oc2g_PrimId_SetRxAttenCnf; + case Oc2g_PrimId_IsAliveReq: return Oc2g_PrimId_IsAliveCnf; + case Oc2g_PrimId_SetMaxCellSizeReq: return Oc2g_PrimId_SetMaxCellSizeCnf; + case Oc2g_PrimId_SetC0IdleSlotPowerReductionReq: return Oc2g_PrimId_SetC0IdleSlotPowerReductionCnf; + default: return -1; // Weak + } +} + +const struct value_string oc2gbts_l1sapi_names[GsmL1_Sapi_NUM+1] = { + { GsmL1_Sapi_Idle, "IDLE" }, + { GsmL1_Sapi_Fcch, "FCCH" }, + { GsmL1_Sapi_Sch, "SCH" }, + { GsmL1_Sapi_Sacch, "SACCH" }, + { GsmL1_Sapi_Sdcch, "SDCCH" }, + { GsmL1_Sapi_Bcch, "BCCH" }, + { GsmL1_Sapi_Pch, "PCH" }, + { GsmL1_Sapi_Agch, "AGCH" }, + { GsmL1_Sapi_Cbch, "CBCH" }, + { GsmL1_Sapi_Rach, "RACH" }, + { GsmL1_Sapi_TchF, "TCH/F" }, + { GsmL1_Sapi_FacchF, "FACCH/F" }, + { GsmL1_Sapi_TchH, "TCH/H" }, + { GsmL1_Sapi_FacchH, "FACCH/H" }, + { GsmL1_Sapi_Nch, "NCH" }, + { GsmL1_Sapi_Pdtch, "PDTCH" }, + { GsmL1_Sapi_Pacch, "PACCH" }, + { GsmL1_Sapi_Pbcch, "PBCCH" }, + { GsmL1_Sapi_Pagch, "PAGCH" }, + { GsmL1_Sapi_Ppch, "PPCH" }, + { GsmL1_Sapi_Pnch, "PNCH" }, + { GsmL1_Sapi_Ptcch, "PTCCH" }, + { GsmL1_Sapi_Prach, "PRACH" }, + { 0, NULL } +}; + +const struct value_string oc2gbts_l1status_names[GSML1_STATUS_NUM+1] = { + { GsmL1_Status_Success, "Success" }, + { GsmL1_Status_Generic, "Generic error" }, + { GsmL1_Status_NoMemory, "Not enough memory" }, + { GsmL1_Status_Timeout, "Timeout" }, + { GsmL1_Status_InvalidParam, "Invalid parameter" }, + { GsmL1_Status_Busy, "Resource busy" }, + { GsmL1_Status_NoRessource, "No more resources" }, + { GsmL1_Status_Uninitialized, "Trying to use uninitialized resource" }, + { GsmL1_Status_NullInterface, "Trying to call a NULL interface" }, + { GsmL1_Status_NullFctnPtr, "Trying to call a NULL function ptr" }, + { GsmL1_Status_BadCrc, "Bad CRC" }, + { GsmL1_Status_BadUsf, "Bad USF" }, + { GsmL1_Status_InvalidCPS, "Invalid CPS field" }, + { GsmL1_Status_UnexpectedBurst, "Unexpected burst" }, + { GsmL1_Status_UnavailCodec, "AMR codec is unavailable" }, + { GsmL1_Status_CriticalError, "Critical error" }, + { GsmL1_Status_OverheatError, "Overheat error" }, + { GsmL1_Status_DeviceError, "Device error" }, + { GsmL1_Status_FacchError, "FACCH / TCH order error" }, + { GsmL1_Status_AlreadyDeactivated, "Lchan already deactivated" }, + { GsmL1_Status_TxBurstFifoOvrn, "FIFO overrun" }, + { GsmL1_Status_TxBurstFifoUndr, "FIFO underrun" }, + { GsmL1_Status_NotSynchronized, "Not synchronized" }, + { GsmL1_Status_Unsupported, "Unsupported feature" }, + { GsmL1_Status_ClockError, "System clock error" }, + { 0, NULL } +}; + +const struct value_string oc2gbts_tracef_names[29] = { + { DBG_DEBUG, "DEBUG" }, + { DBG_L1WARNING, "L1_WARNING" }, + { DBG_ERROR, "ERROR" }, + { DBG_L1RXMSG, "L1_RX_MSG" }, + { DBG_L1RXMSGBYTE, "L1_RX_MSG_BYTE" }, + { DBG_L1TXMSG, "L1_TX_MSG" }, + { DBG_L1TXMSGBYTE, "L1_TX_MSG_BYTE" }, + { DBG_MPHCNF, "MPH_CNF" }, + { DBG_MPHIND, "MPH_IND" }, + { DBG_MPHREQ, "MPH_REQ" }, + { DBG_PHIND, "PH_IND" }, + { DBG_PHREQ, "PH_REQ" }, + { DBG_PHYRF, "PHY_RF" }, + { DBG_PHYRFMSGBYTE, "PHY_MSG_BYTE" }, + { DBG_MODE, "MODE" }, + { DBG_TDMAINFO, "TDMA_INFO" }, + { DBG_BADCRC, "BAD_CRC" }, + { DBG_PHINDBYTE, "PH_IND_BYTE" }, + { DBG_PHREQBYTE, "PH_REQ_BYTE" }, + { DBG_DEVICEMSG, "DEVICE_MSG" }, + { DBG_RACHINFO, "RACH_INFO" }, + { DBG_LOGCHINFO, "LOG_CH_INFO" }, + { DBG_MEMORY, "MEMORY" }, + { DBG_PROFILING, "PROFILING" }, + { DBG_TESTCOMMENT, "TEST_COMMENT" }, + { DBG_TEST, "TEST" }, + { DBG_STATUS, "STATUS" }, + { 0, NULL } +}; + +const struct value_string oc2gbts_tracef_docs[29] = { + { DBG_DEBUG, "Debug Region" }, + { DBG_L1WARNING, "L1 Warning Region" }, + { DBG_ERROR, "Error Region" }, + { DBG_L1RXMSG, "L1_RX_MSG Region" }, + { DBG_L1RXMSGBYTE, "L1_RX_MSG_BYTE Region" }, + { DBG_L1TXMSG, "L1_TX_MSG Region" }, + { DBG_L1TXMSGBYTE, "L1_TX_MSG_BYTE Region" }, + { DBG_MPHCNF, "MphConfirmation Region" }, + { DBG_MPHIND, "MphIndication Region" }, + { DBG_MPHREQ, "MphRequest Region" }, + { DBG_PHIND, "PhIndication Region" }, + { DBG_PHREQ, "PhRequest Region" }, + { DBG_PHYRF, "PhyRF Region" }, + { DBG_PHYRFMSGBYTE, "PhyRF Message Region" }, + { DBG_MODE, "Mode Region" }, + { DBG_TDMAINFO, "TDMA Info Region" }, + { DBG_BADCRC, "Bad CRC Region" }, + { DBG_PHINDBYTE, "PH_IND_BYTE" }, + { DBG_PHREQBYTE, "PH_REQ_BYTE" }, + { DBG_DEVICEMSG, "Device Message Region" }, + { DBG_RACHINFO, "RACH Info" }, + { DBG_LOGCHINFO, "LOG_CH_INFO" }, + { DBG_MEMORY, "Memory Region" }, + { DBG_PROFILING, "Profiling Region" }, + { DBG_TESTCOMMENT, "Test Comments" }, + { DBG_TEST, "Test Region" }, + { DBG_STATUS, "Status Region" }, + { 0, NULL } +}; + +const struct value_string oc2gbts_tch_pl_names[] = { + { GsmL1_TchPlType_NA, "N/A" }, + { GsmL1_TchPlType_Fr, "FR" }, + { GsmL1_TchPlType_Hr, "HR" }, + { GsmL1_TchPlType_Efr, "EFR" }, + { GsmL1_TchPlType_Amr, "AMR(IF2)" }, + { GsmL1_TchPlType_Amr_SidBad, "AMR(SID BAD)" }, + { GsmL1_TchPlType_Amr_Onset, "AMR(ONSET)" }, + { GsmL1_TchPlType_Amr_Ratscch, "AMR(RATSCCH)" }, + { GsmL1_TchPlType_Amr_SidUpdateInH, "AMR(SID_UPDATE INH)" }, + { GsmL1_TchPlType_Amr_SidFirstP1, "AMR(SID_FIRST P1)" }, + { GsmL1_TchPlType_Amr_SidFirstP2, "AMR(SID_FIRST P2)" }, + { GsmL1_TchPlType_Amr_SidFirstInH, "AMR(SID_FIRST INH)" }, + { GsmL1_TchPlType_Amr_RatscchMarker, "AMR(RATSCCH MARK)" }, + { GsmL1_TchPlType_Amr_RatscchData, "AMR(RATSCCH DATA)" }, + { 0, NULL } +}; + +const struct value_string oc2gbts_dir_names[] = { + { GsmL1_Dir_TxDownlink, "TxDL" }, + { GsmL1_Dir_TxUplink, "TxUL" }, + { GsmL1_Dir_RxUplink, "RxUL" }, + { GsmL1_Dir_RxDownlink, "RxDL" }, + { GsmL1_Dir_TxDownlink|GsmL1_Dir_RxUplink, "BOTH" }, + { 0, NULL } +}; + +const struct value_string oc2gbts_chcomb_names[] = { + { GsmL1_LogChComb_0, "dummy" }, + { GsmL1_LogChComb_I, "tch_f" }, + { GsmL1_LogChComb_II, "tch_h" }, + { GsmL1_LogChComb_IV, "ccch" }, + { GsmL1_LogChComb_V, "ccch_sdcch4" }, + { GsmL1_LogChComb_VII, "sdcch8" }, + { GsmL1_LogChComb_XIII, "pdtch" }, + { 0, NULL } +}; + +const uint8_t pdch_msu_size[_NUM_PDCH_CS] = { + [PDCH_CS_1] = 23, + [PDCH_CS_2] = 34, + [PDCH_CS_3] = 40, + [PDCH_CS_4] = 54, + [PDCH_MCS_1] = 27, + [PDCH_MCS_2] = 33, + [PDCH_MCS_3] = 42, + [PDCH_MCS_4] = 49, + [PDCH_MCS_5] = 60, + [PDCH_MCS_6] = 78, + [PDCH_MCS_7] = 118, + [PDCH_MCS_8] = 142, + [PDCH_MCS_9] = 154 +}; + +const struct value_string oc2gbts_rsl_ho_causes[] = { + { IPAC_HO_RQD_CAUSE_L_RXLEV_UL_H, "L_RXLEV_UL_H" }, + { IPAC_HO_RQD_CAUSE_L_RXLEV_DL_H, "L_RXLEV_DL_H" }, + { IPAC_HO_RQD_CAUSE_L_RXQUAL_UL_H, "L_RXQUAL_UL_H" }, + { IPAC_HO_RQD_CAUSE_L_RXQUAL_DL_H, "L_RXQUAL_DL_H" }, + { IPAC_HO_RQD_CAUSE_RXLEV_UL_IH, "RXLEV_UL_IH" }, + { IPAC_HO_RQD_CAUSE_RXLEV_DL_IH, "RXLEV_DL_IH" }, + { IPAC_HO_RQD_CAUSE_MAX_MS_RANGE, "MAX_MS_RANGE" }, + { IPAC_HO_RQD_CAUSE_POWER_BUDGET, "POWER_BUDGET" }, + { IPAC_HO_RQD_CAUSE_ENQUIRY, "ENQUIRY" }, + { IPAC_HO_RQD_CAUSE_ENQUIRY_FAILED, "ENQUIRY_FAILED" }, + { 0, NULL } +}; diff --git a/src/osmo-bts-oc2g/oc2gbts.h b/src/osmo-bts-oc2g/oc2gbts.h new file mode 100644 index 000000000..9eb87452a --- /dev/null +++ b/src/osmo-bts-oc2g/oc2gbts.h @@ -0,0 +1,92 @@ +#ifndef OC2GBTS_H +#define OC2GBTS_H + +#include +#include +#include + +#include +#include + +/* + * Depending on the firmware version either GsmL1_Prim_t or Oc2g_Prim_t + * is the bigger struct. For earlier firmware versions the GsmL1_Prim_t was the + * bigger struct. + */ +#define OC2GBTS_PRIM_SIZE \ + (OSMO_MAX(sizeof(Oc2g_Prim_t), sizeof(GsmL1_Prim_t)) + 128) + +enum l1prim_type { + L1P_T_INVALID, /* this must be 0 to detect uninitialized elements */ + L1P_T_REQ, + L1P_T_CONF, + L1P_T_IND, +}; + +enum oc2g_pedestal_mode{ + OC2G_PEDESTAL_OFF = 0, + OC2G_PEDESTAL_ON, +}; + +enum oc2g_led_control_mode{ + OC2G_LED_CONTROL_BTS = 0, + OC2G_LED_CONTROL_EXT, +}; + +enum oc2g_auto_pwr_adjust_mode{ + OC2G_TX_PWR_ADJ_NONE = 0, + OC2G_TX_PWR_ADJ_AUTO, +}; + +enum l1prim_type oc2gbts_get_l1prim_type(GsmL1_PrimId_t id); +const struct value_string oc2gbts_l1prim_names[GsmL1_PrimId_NUM+1]; +GsmL1_PrimId_t oc2gbts_get_l1prim_conf(GsmL1_PrimId_t id); + +enum l1prim_type oc2gbts_get_sysprim_type(Oc2g_PrimId_t id); +const struct value_string oc2gbts_sysprim_names[Oc2g_PrimId_NUM+1]; +Oc2g_PrimId_t oc2gbts_get_sysprim_conf(Oc2g_PrimId_t id); + +const struct value_string oc2gbts_l1sapi_names[GsmL1_Sapi_NUM+1]; +const struct value_string oc2gbts_l1status_names[GSML1_STATUS_NUM+1]; + +const struct value_string oc2gbts_tracef_names[29]; +const struct value_string oc2gbts_tracef_docs[29]; + +const struct value_string oc2gbts_tch_pl_names[15]; + +const struct value_string oc2gbts_clksrc_names[10]; + +const struct value_string oc2gbts_dir_names[6]; + +const struct value_string oc2gbts_rsl_ho_causes[IPAC_HO_RQD_CAUSE_MAX]; + +enum pdch_cs { + PDCH_CS_1, + PDCH_CS_2, + PDCH_CS_3, + PDCH_CS_4, + PDCH_MCS_1, + PDCH_MCS_2, + PDCH_MCS_3, + PDCH_MCS_4, + PDCH_MCS_5, + PDCH_MCS_6, + PDCH_MCS_7, + PDCH_MCS_8, + PDCH_MCS_9, + _NUM_PDCH_CS +}; + +const uint8_t pdch_msu_size[_NUM_PDCH_CS]; + +/* OC2G default parameters */ +#define OC2G_BTS_MAX_CELL_SIZE_DEFAULT 166 /* 166 qbits is default value */ +#define OC2G_BTS_PEDESTAL_MODE_DEFAULT 0 /* Unused TS is off by default */ +#define OC2G_BTS_LED_CTRL_MODE_DEFAULT 0 /* LED is controlled by BTS by default */ +#define OC2G_BTS_DSP_ALIVE_TMR_DEFAULT 5 /* Default DSP alive timer is 5 seconds */ +#define OC2G_BTS_TX_PWR_ADJ_DEFAULT 0 /* Default Tx power auto adjustment is none */ +#define OC2G_BTS_TX_RED_PWR_8PSK_DEFAULT 0 /* Default 8-PSK maximum power level is 0 dB */ +#define OC2G_BTS_RTP_DRIFT_THRES_DEFAULT 0 /* Default RTP drift threshold is 0 ms (disabled) */ +#define OC2G_BTS_TX_C0_IDLE_RED_PWR_DEFAULT 0 /* Default C0 idle slot reduction power level is 0 dB */ + +#endif /* OC2GBTS_H */ diff --git a/src/osmo-bts-oc2g/oc2gbts_vty.c b/src/osmo-bts-oc2g/oc2gbts_vty.c new file mode 100644 index 000000000..4f7c45a12 --- /dev/null +++ b/src/osmo-bts-oc2g/oc2gbts_vty.c @@ -0,0 +1,733 @@ +/* VTY interface for NuRAN Wireless OC-2G */ + +/* Copyright (C) 2015 by Yves Godin + * Copyright (C) 2016 by Harald Welte + * + * Based on sysmoBTS: + * (C) 2011 by Harald Welte + * (C) 2012,2013 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "oc2gbts.h" +#include "l1_if.h" +#include "utils.h" + +extern int lchan_activate(struct gsm_lchan *lchan); +extern int rsl_tx_preproc_meas_res(struct gsm_lchan *lchan); + +#define TRX_STR "Transceiver related commands\n" "TRX number\n" + +#define SHOW_TRX_STR \ + SHOW_STR \ + TRX_STR +#define DSP_TRACE_F_STR "DSP Trace Flag\n" + +static struct gsm_bts *vty_bts; + +static const struct value_string oc2g_pedestal_mode_strs[] = { + { OC2G_PEDESTAL_OFF, "off" }, + { OC2G_PEDESTAL_ON, "on" }, + { 0, NULL } +}; + +static const struct value_string oc2g_led_mode_strs[] = { + { OC2G_LED_CONTROL_BTS, "bts" }, + { OC2G_LED_CONTROL_EXT, "external" }, + { 0, NULL } +}; + +static const struct value_string oc2g_auto_adj_pwr_strs[] = { + { OC2G_TX_PWR_ADJ_NONE, "none" }, + { OC2G_TX_PWR_ADJ_AUTO, "auto" }, + { 0, NULL } +}; + +/* configuration */ + +DEFUN(cfg_phy_cal_path, cfg_phy_cal_path_cmd, + "trx-calibration-path PATH", + "Set the path name to TRX calibration data\n" "Path name\n") +{ + struct phy_instance *pinst = vty->index; + + if (pinst->u.oc2g.calib_path) + talloc_free(pinst->u.oc2g.calib_path); + + pinst->u.oc2g.calib_path = talloc_strdup(pinst, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_dsp_trace_f, cfg_phy_dsp_trace_f_cmd, + "HIDDEN", TRX_STR) +{ + struct phy_instance *pinst = vty->index; + unsigned int flag; + + flag = get_string_value(oc2gbts_tracef_names, argv[1]); + pinst->u.oc2g.dsp_trace_f |= ~flag; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_no_dsp_trace_f, cfg_phy_no_dsp_trace_f_cmd, + "HIDDEN", NO_STR TRX_STR) +{ + struct phy_instance *pinst = vty->index; + unsigned int flag; + + flag = get_string_value(oc2gbts_tracef_names, argv[1]); + pinst->u.oc2g.dsp_trace_f &= ~flag; + + return CMD_SUCCESS; +} + + +/* runtime */ + +DEFUN(show_dsp_trace_f, show_dsp_trace_f_cmd, + "show trx <0-0> dsp-trace-flags", + SHOW_TRX_STR "Display the current setting of the DSP trace flags") +{ + int trx_nr = atoi(argv[0]); + struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct oc2gl1_hdl *fl1h; + int i; + + if (!trx) + return CMD_WARNING; + + fl1h = trx_oc2gl1_hdl(trx); + + vty_out(vty, "Oc2g L1 DSP trace flags:%s", VTY_NEWLINE); + for (i = 0; i < ARRAY_SIZE(oc2gbts_tracef_names); i++) { + const char *endis; + + if (oc2gbts_tracef_names[i].value == 0 && + oc2gbts_tracef_names[i].str == NULL) + break; + + if (fl1h->dsp_trace_f & oc2gbts_tracef_names[i].value) + endis = "enabled"; + else + endis = "disabled"; + + vty_out(vty, "DSP Trace %-15s %s%s", + oc2gbts_tracef_names[i].str, endis, + VTY_NEWLINE); + } + + return CMD_SUCCESS; + +} + +DEFUN(dsp_trace_f, dsp_trace_f_cmd, "HIDDEN", TRX_STR) +{ + int phy_nr = atoi(argv[0]); + struct phy_instance *pinst; + struct oc2gl1_hdl *fl1h; + unsigned int flag ; + + pinst = vty_get_phy_instance(vty, phy_nr, 0); + if (!pinst) + return CMD_WARNING; + + fl1h = pinst->u.oc2g.hdl; + flag = get_string_value(oc2gbts_tracef_names, argv[1]); + l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f | flag); + + return CMD_SUCCESS; +} + +DEFUN(no_dsp_trace_f, no_dsp_trace_f_cmd, "HIDDEN", NO_STR TRX_STR) +{ + int phy_nr = atoi(argv[0]); + struct phy_instance *pinst; + struct oc2gl1_hdl *fl1h; + unsigned int flag ; + + pinst = vty_get_phy_instance(vty, phy_nr, 0); + if (!pinst) + return CMD_WARNING; + + fl1h = pinst->u.oc2g.hdl; + flag = get_string_value(oc2gbts_tracef_names, argv[1]); + l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f & ~flag); + + return CMD_SUCCESS; +} + +DEFUN(show_sys_info, show_sys_info_cmd, + "show phy <0-0> instance <0-0> system-information", + SHOW_TRX_STR "Display information about system\n") +{ + int phy_nr = atoi(argv[0]); + int inst_nr = atoi(argv[1]); + struct phy_link *plink = phy_link_by_num(phy_nr); + struct phy_instance *pinst; + struct oc2gl1_hdl *fl1h; + int i; + + if (!plink) { + vty_out(vty, "Cannot find PHY link %u%s", + phy_nr, VTY_NEWLINE); + return CMD_WARNING; + } + pinst = phy_instance_by_num(plink, inst_nr); + if (!plink) { + vty_out(vty, "Cannot find PHY instance %u%s", + phy_nr, VTY_NEWLINE); + return CMD_WARNING; + } + fl1h = pinst->u.oc2g.hdl; + + vty_out(vty, "DSP Version: %u.%u.%u, FPGA Version: %u.%u.%u%s", + fl1h->hw_info.dsp_version[0], + fl1h->hw_info.dsp_version[1], + fl1h->hw_info.dsp_version[2], + fl1h->hw_info.fpga_version[0], + fl1h->hw_info.fpga_version[1], + fl1h->hw_info.fpga_version[2], VTY_NEWLINE); + + vty_out(vty, "GSM Band Support: "); + for (i = 0; i < sizeof(fl1h->hw_info.band_support); i++) { + if (fl1h->hw_info.band_support & (1 << i)) + vty_out(vty, "%s ", gsm_band_name(1 << i)); + } + vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, "Min Tx Power: %d dBm%s", fl1h->phy_inst->u.oc2g.minTxPower, VTY_NEWLINE); + vty_out(vty, "Max Tx Power: %d dBm%s", fl1h->phy_inst->u.oc2g.maxTxPower, VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(activate_lchan, activate_lchan_cmd, + "trx <0-0> <0-7> (activate|deactivate) <0-7>", + TRX_STR + "Timeslot number\n" + "Activate Logical Channel\n" + "Deactivate Logical Channel\n" + "Logical Channel Number\n" ) +{ + int trx_nr = atoi(argv[0]); + int ts_nr = atoi(argv[1]); + int lchan_nr = atoi(argv[3]); + struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; + struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; + + if (!strcmp(argv[2], "activate")) + lchan_activate(lchan); + else + lchan_deactivate(lchan); + + return CMD_SUCCESS; +} + +DEFUN(set_tx_power, set_tx_power_cmd, + "trx nr <0-1> tx-power <-110-100>", + TRX_STR + "TRX number \n" + "Set transmit power (override BSC)\n" + "Transmit power in dBm\n") +{ + int trx_nr = atoi(argv[0]); + int power = atoi(argv[1]); + struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + + power_ramp_start(trx, to_mdB(power), 1); + + return CMD_SUCCESS; +} + +DEFUN(loopback, loopback_cmd, + "trx <0-0> <0-7> loopback <0-1>", + TRX_STR + "Timeslot number\n" + "Set TCH loopback\n" + "Logical Channel Number\n") +{ + int trx_nr = atoi(argv[0]); + int ts_nr = atoi(argv[1]); + int lchan_nr = atoi(argv[2]); + struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; + struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; + + lchan->loopback = 1; + + return CMD_SUCCESS; +} + +DEFUN(no_loopback, no_loopback_cmd, + "no trx <0-0> <0-7> loopback <0-1>", + NO_STR TRX_STR + "Timeslot number\n" + "Set TCH loopback\n" + "Logical Channel Number\n") +{ + int trx_nr = atoi(argv[0]); + int ts_nr = atoi(argv[1]); + int lchan_nr = atoi(argv[2]); + struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr); + struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; + struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; + + lchan->loopback = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_nominal_power, cfg_trx_nominal_power_cmd, + "nominal-tx-power <0-40>", + "Set the nominal transmit output power in dBm\n" + "Nominal transmit output power level in dBm\n") +{ + int nominal_power = atoi(argv[0]); + struct gsm_bts_trx *trx = vty->index; + + if (( nominal_power > 40 ) || ( nominal_power < 0 )) { + vty_out(vty, "Nominal Tx power level must be between 0 and 40 dBm (%d) %s", + nominal_power, VTY_NEWLINE); + return CMD_WARNING; + } + + trx->nominal_power = nominal_power; + trx->power_params.trx_p_max_out_mdBm = to_mdB(nominal_power); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_max_cell_size, cfg_phy_max_cell_size_cmd, + "max-cell-size <0-166>", + "Set the maximum cell size in qbits\n") +{ + struct phy_instance *pinst = vty->index; + int cell_size = (uint8_t)atoi(argv[0]); + + if (( cell_size > 166 ) || ( cell_size < 0 )) { + vty_out(vty, "Max cell size must be between 0 and 166 qbits (%d) %s", + cell_size, VTY_NEWLINE); + return CMD_WARNING; + } + + pinst->u.oc2g.max_cell_size = (uint8_t)cell_size; + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_pedestal_mode, cfg_phy_pedestal_mode_cmd, + "pedestal-mode (on|off)", + "Set unused time-slot transmission in pedestal mode\n" + "Transmission pedestal mode can be (off, on)\n") +{ + struct phy_instance *pinst = vty->index; + int val = get_string_value(oc2g_pedestal_mode_strs, argv[0]); + + if((val < OC2G_PEDESTAL_OFF) || (val > OC2G_PEDESTAL_ON)) { + vty_out(vty, "Invalid unused time-slot transmission mode %d%s", val, VTY_NEWLINE); + return CMD_WARNING; + } + + pinst->u.oc2g.pedestal_mode = (uint8_t)val; + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_dsp_alive_timer, cfg_phy_dsp_alive_timer_cmd, + "dsp-alive-period <0-60>", + "Set DSP alive timer period in second\n") +{ + struct phy_instance *pinst = vty->index; + uint8_t period = (uint8_t)atoi(argv[0]); + + if (( period > 60 ) || ( period < 0 )) { + vty_out(vty, "DSP heart beat alive timer period must be between 0 and 60 seconds (%d) %s", + period, VTY_NEWLINE); + return CMD_WARNING; + } + + pinst->u.oc2g.dsp_alive_period = period; + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_auto_tx_pwr_adj, cfg_phy_auto_tx_pwr_adj_cmd, + "pwr-adj-mode (none|auto)", + "Set output power adjustment mode\n") +{ + struct phy_instance *pinst = vty->index; + int val = get_string_value(oc2g_auto_adj_pwr_strs, argv[0]); + + if((val < OC2G_TX_PWR_ADJ_NONE) || (val > OC2G_TX_PWR_ADJ_AUTO)) { + vty_out(vty, "Invalid output power adjustment mode %d%s", val, VTY_NEWLINE); + return CMD_WARNING; + } + + pinst->u.oc2g.tx_pwr_adj_mode = (uint8_t)val; + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_tx_red_pwr_8psk, cfg_phy_tx_red_pwr_8psk_cmd, + "tx-red-pwr-8psk <0-40>", + "Set reduction output power for 8-PSK scheme in dB unit\n") +{ + struct phy_instance *pinst = vty->index; + int val = atoi(argv[0]); + + if ((val > 40) || (val < 0)) { + vty_out(vty, "Reduction Tx power level must be between 0 and 40 dB (%d) %s", + val, VTY_NEWLINE); + return CMD_WARNING; + } + + pinst->u.oc2g.tx_pwr_red_8psk = (uint8_t)val; + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_c0_idle_red_pwr, cfg_phy_c0_idle_red_pwr_cmd, + "c0-idle-red-pwr <0-40>", + "Set reduction output power for C0 idle slot in dB unit\n") +{ + struct phy_instance *pinst = vty->index; + int val = atoi(argv[0]); + + if ((val > 40) || (val < 0)) { + vty_out(vty, "Reduction Tx power level must be between 0 and 40 dB (%d) %s", + val, VTY_NEWLINE); + return CMD_WARNING; + } + + pinst->u.oc2g.tx_c0_idle_pwr_red = (uint8_t)val; + return CMD_SUCCESS; +} + +DEFUN(trigger_ho_cause, trigger_ho_cause_cmd, "HIDDEN", TRX_STR) +{ + struct gsm_network *net = gsmnet_from_vty(vty); + struct gsm_bts *bts; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + struct gsm_lchan *lchan; + int trx_nr, ts_nr, lchan_nr; + uint8_t ho_cause; + uint8_t old_ho_cause; + + /* get BTS pointer */ + bts = gsm_bts_num(net, 0); + if (!bts) { + vty_out(vty, "Can not get BTS node %s", VTY_NEWLINE); + return CMD_WARNING; + } + /* get TRX pointer */ + if (argc >= 1) { + trx_nr = atoi(argv[0]); + if (trx_nr >= bts->num_trx) { + vty_out(vty, "%% can't find TRX %s%s", argv[0], + VTY_NEWLINE); + return CMD_WARNING; + } + trx = gsm_bts_trx_num(bts, trx_nr); + } + /* get TS pointer */ + if (argc >= 2) { + ts_nr = atoi(argv[1]); + if (ts_nr >= TRX_NR_TS) { + vty_out(vty, "%% can't find TS %s%s", argv[1], + VTY_NEWLINE); + return CMD_WARNING; + } + ts = &trx->ts[ts_nr]; + } + /* get lchan pointer */ + if (argc >= 3) { + lchan_nr = atoi(argv[2]); + if (lchan_nr >= TS_MAX_LCHAN) { + vty_out(vty, "%% can't find LCHAN %s%s", argv[2], + VTY_NEWLINE); + return CMD_WARNING; + } + lchan = &ts->lchan[lchan_nr]; + } + + /* get HO cause */ + if (argc >= 4) { + ho_cause = get_string_value(oc2gbts_rsl_ho_causes, argv[3]); + if (ho_cause >= IPAC_HO_RQD_CAUSE_MAX) { + vty_out(vty, "%% can't find valid HO cause %s%s", argv[3], + VTY_NEWLINE); + return CMD_WARNING; + } + } else { + vty_out(vty, "%% HO cause is not provided %s", VTY_NEWLINE); + return CMD_WARNING; + } + /* TODO(oramadan) MERGE + /* store recorded HO causes * / + old_ho_cause = lchan->meas_preproc.rec_ho_causes; + + /* Apply new HO causes * / + lchan->meas_preproc.rec_ho_causes = 1 << (ho_cause - 1); + + /* Send measuremnt report to BSC * / + rsl_tx_preproc_meas_res(lchan); + + /* restore HO cause * / + lchan->meas_preproc.rec_ho_causes = old_ho_cause; + */ + + return CMD_SUCCESS; +} + +/* TODO(oramadan) MERGE +DEFUN(cfg_bts_rtp_drift_threshold, cfg_bts_rtp_drift_threshold_cmd, + "rtp-drift-threshold <0-10000>", + "RTP parameters\n" + "RTP timestamp drift threshold in ms\n") +{ + struct gsm_bts *bts = vty->index; + struct gsm_bts_role_bts *btsb = bts_role_bts(bts); + + btsb->oc2g.rtp_drift_thres_ms = (unsigned int) atoi(argv[0]); + + return CMD_SUCCESS; +} +*/ + +void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts) +{ + /* TODO(oramadan) MERGE + struct gsm_bts_role_bts *btsb = bts_role_bts(bts); + + vty_out(vty, " led-control-mode %s%s", + get_value_string(oc2g_led_mode_strs, btsb->oc2g.led_ctrl_mode), VTY_NEWLINE); + + vty_out(vty, " rtp-drift-threshold %d%s", + btsb->oc2g.rtp_drift_thres_ms, VTY_NEWLINE); + */ + +} + +void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx) +{ + vty_out(vty, " nominal-tx-power %d%s", trx->nominal_power,VTY_NEWLINE); +} + +void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink) +{ +} + +void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst) +{ + int i; + + for (i = 0; i < 32; i++) { + if (pinst->u.oc2g.dsp_trace_f & (1 << i)) { + const char *name; + name = get_value_string(oc2gbts_tracef_names, (1 << i)); + vty_out(vty, " dsp-trace-flag %s%s", name, + VTY_NEWLINE); + } + } + if (pinst->u.oc2g.calib_path) + vty_out(vty, " trx-calibration-path %s%s", + pinst->u.oc2g.calib_path, VTY_NEWLINE); + + vty_out(vty, " max-cell-size %d%s", + pinst->u.oc2g.max_cell_size, VTY_NEWLINE); + + vty_out(vty, " pedestal-mode %s%s", + get_value_string(oc2g_pedestal_mode_strs, pinst->u.oc2g.pedestal_mode) , VTY_NEWLINE); + + vty_out(vty, " dsp-alive-period %d%s", + pinst->u.oc2g.dsp_alive_period, VTY_NEWLINE); + + vty_out(vty, " pwr-adj-mode %s%s", + get_value_string(oc2g_auto_adj_pwr_strs, pinst->u.oc2g.tx_pwr_adj_mode), VTY_NEWLINE); + + vty_out(vty, " tx-red-pwr-8psk %d%s", + pinst->u.oc2g.tx_pwr_red_8psk, VTY_NEWLINE); + + vty_out(vty, " c0-idle-red-pwr %d%s", + pinst->u.oc2g.tx_c0_idle_pwr_red, VTY_NEWLINE); +} + +int bts_model_vty_init(struct gsm_bts *bts) +{ + vty_bts = bts; + + /* runtime-patch the command strings with debug levels */ + dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, oc2gbts_tracef_names, + "phy <0-1> dsp-trace-flag (", + "|",")", VTY_DO_LOWER); + dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, oc2gbts_tracef_docs, + TRX_STR DSP_TRACE_F_STR, + "\n", "", 0); + + no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, oc2gbts_tracef_names, + "no phy <0-1> dsp-trace-flag (", + "|",")", VTY_DO_LOWER); + no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, oc2gbts_tracef_docs, + NO_STR TRX_STR DSP_TRACE_F_STR, + "\n", "", 0); + + cfg_phy_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, + oc2gbts_tracef_names, + "dsp-trace-flag (", + "|",")", VTY_DO_LOWER); + cfg_phy_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, + oc2gbts_tracef_docs, + DSP_TRACE_F_STR, + "\n", "", 0); + + cfg_phy_no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, + oc2gbts_tracef_names, + "no dsp-trace-flag (", + "|",")", VTY_DO_LOWER); + cfg_phy_no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, + oc2gbts_tracef_docs, + NO_STR DSP_TRACE_F_STR, + "\n", "", 0); + + trigger_ho_cause_cmd.string = vty_cmd_string_from_valstr(bts, + oc2gbts_rsl_ho_causes, + "trigger-ho-cause trx <0-1> ts <0-7> lchan <0-1> cause (", + "|",")", VTY_DO_LOWER); + + install_element_ve(&show_dsp_trace_f_cmd); + install_element_ve(&show_sys_info_cmd); + install_element_ve(&dsp_trace_f_cmd); + install_element_ve(&no_dsp_trace_f_cmd); + + install_element(ENABLE_NODE, &activate_lchan_cmd); + install_element(ENABLE_NODE, &set_tx_power_cmd); + + install_element(ENABLE_NODE, &loopback_cmd); + install_element(ENABLE_NODE, &no_loopback_cmd); + + install_element(ENABLE_NODE, &trigger_ho_cause_cmd); + + install_element(BTS_NODE, &cfg_bts_auto_band_cmd); + install_element(BTS_NODE, &cfg_bts_no_auto_band_cmd); + /* TODO(oramadan) MERGE + install_element(BTS_NODE, &cfg_bts_rtp_drift_threshold_cmd); + */ + + install_element(TRX_NODE, &cfg_trx_nominal_power_cmd); + + install_element(PHY_INST_NODE, &cfg_phy_dsp_trace_f_cmd); + install_element(PHY_INST_NODE, &cfg_phy_no_dsp_trace_f_cmd); + install_element(PHY_INST_NODE, &cfg_phy_cal_path_cmd); + install_element(PHY_INST_NODE, &cfg_phy_pedestal_mode_cmd); + install_element(PHY_INST_NODE, &cfg_phy_max_cell_size_cmd); + install_element(PHY_INST_NODE, &cfg_phy_dsp_alive_timer_cmd); + install_element(PHY_INST_NODE, &cfg_phy_auto_tx_pwr_adj_cmd); + install_element(PHY_INST_NODE, &cfg_phy_tx_red_pwr_8psk_cmd); + install_element(PHY_INST_NODE, &cfg_phy_c0_idle_red_pwr_cmd); + + return 0; +} + +/* TODO(oramadan) MERGE +/* OC2G BTS control interface * / +CTRL_CMD_DEFINE_WO_NOVRF(oc2g_oml_alert, "oc2g-oml-alert"); +static int set_oc2g_oml_alert(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + int cause = atoi(cmd->value); + char *saveptr = NULL; + + alarm_sig_data.mo = &bts->mo; + cause = atoi(strtok_r(cmd->value, ",", &saveptr)); + alarm_sig_data.event_serverity = (cause >> 8) & 0x0F; + alarm_sig_data.add_text = strtok_r(NULL, "\n", &saveptr); + memcpy(alarm_sig_data.spare, &cause, sizeof(int)); + LOGP(DLCTRL, LOGL_NOTICE, "BTS received MGR alarm cause=%d, text=%s\n", cause, alarm_sig_data.add_text); + + /* dispatch OML alarm signal * / + osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_MGR_ALARM, &alarm_sig_data); + + /* return with same alarm cause to MGR rather than OK string* / + cmd->reply = talloc_asprintf(cmd, "%d", cause); + return CTRL_CMD_REPLY; +} + +CTRL_CMD_DEFINE_WO_NOVRF(oc2g_oml_ceased, "oc2g-oml-ceased"); +static int set_oc2g_oml_ceased(struct ctrl_cmd *cmd, void *data) +{ + struct gsm_bts *bts = cmd->node; + int cause = atoi(cmd->value); + char *saveptr = NULL; + + alarm_sig_data.mo = &bts->mo; + cause = atoi(strtok_r(cmd->value, ",", &saveptr)); + alarm_sig_data.add_text = strtok_r(NULL, "\n", &saveptr); + memcpy(alarm_sig_data.spare, &cause, sizeof(int)); + LOGP(DLCTRL, LOGL_NOTICE, "BTS received MGR ceased alarm cause=%d, text=%s\n", cause, alarm_sig_data.add_text); + + /* dispatch OML alarm signal * / + osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_MGR_CEASED_ALARM, &alarm_sig_data); + */ + + /* return with same alarm cause to MGR rather than OK string* / + cmd->reply = talloc_asprintf(cmd, "%d", cause); + return CTRL_CMD_REPLY; +} +*/ + +int bts_model_ctrl_cmds_install(struct gsm_bts *bts) +{ + int rc = 0; + + + /* TODO(oramadan) MERGE + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_oc2g_oml_alert); + rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_oc2g_oml_ceased); + */ + + return 0; +} diff --git a/src/osmo-bts-oc2g/oml.c b/src/osmo-bts-oc2g/oml.c new file mode 100644 index 000000000..65ebc1c9a --- /dev/null +++ b/src/osmo-bts-oc2g/oml.c @@ -0,0 +1,2078 @@ +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * (C) 2011 by Harald Welte + * (C) 2013-2014 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "l1_if.h" +#include "oc2gbts.h" +#include "utils.h" + +static int mph_info_chan_confirm(struct gsm_lchan *lchan, + enum osmo_mph_info_type type, uint8_t cause) +{ + struct osmo_phsap_prim l1sap; + + memset(&l1sap, 0, sizeof(l1sap)); + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_CONFIRM, + NULL); + l1sap.u.info.type = type; + l1sap.u.info.u.act_cnf.chan_nr = gsm_lchan2chan_nr(lchan); + l1sap.u.info.u.act_cnf.cause = cause; + + return l1sap_up(lchan->ts->trx, &l1sap); +} + +enum sapi_cmd_type { + SAPI_CMD_ACTIVATE, + SAPI_CMD_CONFIG_CIPHERING, + SAPI_CMD_CONFIG_LOGCH_PARAM, + SAPI_CMD_SACCH_REL_MARKER, + SAPI_CMD_REL_MARKER, + SAPI_CMD_DEACTIVATE, +}; + +struct sapi_cmd { + struct llist_head entry; + GsmL1_Sapi_t sapi; + GsmL1_Dir_t dir; + enum sapi_cmd_type type; + int (*callback)(struct gsm_lchan *lchan, int status); +}; + +static const enum GsmL1_LogChComb_t pchan_to_logChComb[_GSM_PCHAN_MAX] = { + [GSM_PCHAN_NONE] = GsmL1_LogChComb_0, + [GSM_PCHAN_CCCH] = GsmL1_LogChComb_IV, + [GSM_PCHAN_CCCH_SDCCH4] = GsmL1_LogChComb_V, + [GSM_PCHAN_CCCH_SDCCH4_CBCH] = GsmL1_LogChComb_V, + [GSM_PCHAN_TCH_F] = GsmL1_LogChComb_I, + [GSM_PCHAN_TCH_H] = GsmL1_LogChComb_II, + [GSM_PCHAN_SDCCH8_SACCH8C] = GsmL1_LogChComb_VII, + [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = GsmL1_LogChComb_VII, + [GSM_PCHAN_PDCH] = GsmL1_LogChComb_XIII, + [GSM_PCHAN_UNKNOWN] = GsmL1_LogChComb_0, + /* + * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_TCH_F_TCH_H_PDCH should not be + * part of this, only "real" pchan values will be looked up here. + * See the callers of ts_connect_as(). + */ +}; + +static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb); + +static void *prim_init(GsmL1_Prim_t *prim, GsmL1_PrimId_t id, struct oc2gl1_hdl *gl1, + uint32_t hLayer3_uint32) +{ + HANDLE hLayer3; + prim->id = id; + + osmo_static_assert(sizeof(HANDLE) >= 4, l1p_handle_is_at_least_32bit); + hLayer3 = (void*)hLayer3_uint32; + + switch (id) { + case GsmL1_PrimId_MphInitReq: + //prim->u.mphInitReq.hLayer1 = (HANDLE)gl1->hLayer1; + prim->u.mphInitReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphCloseReq: + prim->u.mphCloseReq.hLayer1 = gl1->hLayer1; + prim->u.mphCloseReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConnectReq: + prim->u.mphConnectReq.hLayer1 = gl1->hLayer1; + prim->u.mphConnectReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDisconnectReq: + prim->u.mphDisconnectReq.hLayer1 = gl1->hLayer1; + prim->u.mphDisconnectReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphActivateReq: + prim->u.mphActivateReq.hLayer1 = gl1->hLayer1; + prim->u.mphActivateReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDeactivateReq: + prim->u.mphDeactivateReq.hLayer1 = gl1->hLayer1; + prim->u.mphDeactivateReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConfigReq: + prim->u.mphConfigReq.hLayer1 = gl1->hLayer1; + prim->u.mphConfigReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphMeasureReq: + prim->u.mphMeasureReq.hLayer1 = gl1->hLayer1; + prim->u.mphMeasureReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphInitCnf: + prim->u.mphInitCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphCloseCnf: + prim->u.mphCloseCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConnectCnf: + prim->u.mphConnectCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDisconnectCnf: + prim->u.mphDisconnectCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphActivateCnf: + prim->u.mphActivateCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDeactivateCnf: + prim->u.mphDeactivateCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConfigCnf: + prim->u.mphConfigCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphMeasureCnf: + prim->u.mphMeasureCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphTimeInd: + break; + case GsmL1_PrimId_MphSyncInd: + break; + case GsmL1_PrimId_PhEmptyFrameReq: + prim->u.phEmptyFrameReq.hLayer1 = gl1->hLayer1; + break; + case GsmL1_PrimId_PhDataReq: + prim->u.phDataReq.hLayer1 = gl1->hLayer1; + break; + case GsmL1_PrimId_PhConnectInd: + break; + case GsmL1_PrimId_PhReadyToSendInd: + break; + case GsmL1_PrimId_PhDataInd: + break; + case GsmL1_PrimId_PhRaInd: + break; + default: + LOGP(DL1C, LOGL_ERROR, "unknown L1 primitive %u\n", id); + break; + } + return &prim->u; +} + +static uint32_t l1p_handle_for_trx(struct gsm_bts_trx *trx) +{ + struct gsm_bts *bts = trx->bts; + + osmo_static_assert(sizeof(trx->nr) == 1, trx_nr_is_8bit); + osmo_static_assert(sizeof(bts->nr) == 1, bts_nr_is_8bit); + + return bts->nr << 24 + | trx->nr << 16; +} + +static uint32_t l1p_handle_for_ts(struct gsm_bts_trx_ts *ts) +{ + osmo_static_assert(sizeof(ts->nr) == 1, ts_nr_is_8bit); + + return l1p_handle_for_trx(ts->trx) + | ts->nr << 8; +} + + +static uint32_t l1p_handle_for_lchan(struct gsm_lchan *lchan) +{ + osmo_static_assert(sizeof(lchan->nr) == 1, lchan_nr_is_8bit); + + return l1p_handle_for_ts(lchan->ts) + | lchan->nr; +} + +GsmL1_Status_t prim_status(GsmL1_Prim_t *prim) +{ + switch (prim->id) { + case GsmL1_PrimId_MphInitCnf: + return prim->u.mphInitCnf.status; + case GsmL1_PrimId_MphCloseCnf: + return prim->u.mphCloseCnf.status; + case GsmL1_PrimId_MphConnectCnf: + return prim->u.mphConnectCnf.status; + case GsmL1_PrimId_MphDisconnectCnf: + return prim->u.mphDisconnectCnf.status; + case GsmL1_PrimId_MphActivateCnf: + return prim->u.mphActivateCnf.status; + case GsmL1_PrimId_MphDeactivateCnf: + return prim->u.mphDeactivateCnf.status; + case GsmL1_PrimId_MphConfigCnf: + return prim->u.mphConfigCnf.status; + case GsmL1_PrimId_MphMeasureCnf: + return prim->u.mphMeasureCnf.status; + default: + break; + } + return GsmL1_Status_Success; +} + +#if 0 +static int compl_cb_send_oml_msg(struct msgb *l1_msg, void *data) +{ + struct msgb *resp_msg = data; + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + + if (prim_status(l1p) != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_ERROR, "Rx %s, status: %s\n", + get_value_string(oc2gbts_l1prim_names, l1p->id), + get_value_string(oc2gbts_l1status_names, cc->status)); + return 0; + } + + msgb_free(l1_msg); + + return abis_nm_sendmsg(msg); +} +#endif + +int lchan_activate(struct gsm_lchan *lchan); + +static int opstart_compl(struct gsm_abis_mo *mo, struct msgb *l1_msg) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_Status_t status = prim_status(l1p); + + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_ERROR, "Rx %s, status: %s\n", + get_value_string(oc2gbts_l1prim_names, l1p->id), + get_value_string(oc2gbts_l1status_names, status)); + msgb_free(l1_msg); + return oml_mo_opstart_nack(mo, NM_NACK_CANT_PERFORM); + } + + msgb_free(l1_msg); + + /* Set to Operational State: Enabled */ + oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, NM_AVSTATE_OK); + + /* ugly hack to auto-activate all SAPIs for the BCCH/CCCH on TS0 */ + if (mo->obj_class == NM_OC_CHANNEL && mo->obj_inst.trx_nr == 0 && + mo->obj_inst.ts_nr == 0) { + struct gsm_lchan *cbch = gsm_bts_get_cbch(mo->bts); + DEBUGP(DL1C, "====> trying to activate lchans of BCCH\n"); + mo->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind = + LCHAN_REL_ACT_OML; + lchan_activate(&mo->bts->c0->ts[0].lchan[CCCH_LCHAN]); + if (cbch) { + cbch->rel_act_kind = LCHAN_REL_ACT_OML; + lchan_activate(cbch); + } + } + + /* Send OPSTART ack */ + return oml_mo_opstart_ack(mo); +} + +static int opstart_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + struct gsm_abis_mo *mo; + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphConnectCnf_t *cnf = &l1p->u.mphConnectCnf; + + mo = &trx->ts[cnf->u8Tn].mo; + return opstart_compl(mo, l1_msg); +} + +static int trx_mute_on_init_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + Oc2g_Prim_t *sysp = msgb_sysprim(resp); + GsmL1_Status_t status; + + status = sysp->u.muteRfCnf.status; + + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_FATAL, "Rx RF-MUTE.conf status=%s\n", + get_value_string(oc2gbts_l1status_names, status)); + bts_shutdown(trx->bts, "RF-MUTE failure"); + } + + msgb_free(resp); + + return 0; +} + +static int trx_init_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx); + + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphInitCnf_t *ic = &l1p->u.mphInitCnf; + + LOGP(DL1C, LOGL_INFO, "Rx MPH-INIT.conf (status=%s)\n", + get_value_string(oc2gbts_l1status_names, ic->status)); + + /* store layer1 handle */ + if (ic->status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_FATAL, "Rx MPH-INIT.conf status=%s\n", + get_value_string(oc2gbts_l1status_names, ic->status)); + bts_shutdown(trx->bts, "MPH-INIT failure"); + } + + fl1h->hLayer1 = ic->hLayer1; + + /* If the TRX was already locked the MphInit would have undone it */ + if (trx->mo.nm_state.administrative == NM_STATE_LOCKED) + trx_rf_lock(trx, 1, trx_mute_on_init_cb); + + /* apply initial values for Tx power backoff for 8-PSK * / + trx->max_power_backoff_8psk = fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk; + l1if_set_txpower_backoff_8psk(fl1h, fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk); + LOGP(DL1C, LOGL_INFO, "%s Applied initial 8-PSK Tx power backoff of %d dB\n", + gsm_trx_name(fl1h->phy_inst->trx), + fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk); + + /* apply initial values for Tx C0 idle slot power reduction * / + trx->c0_idle_power_red = fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red; + l1if_set_txpower_c0_idle_pwr_red(fl1h, fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red); + LOGP(DL1C, LOGL_INFO, "%s Applied initial C0 idle slot power reduction of %d dB\n", + gsm_trx_name(fl1h->phy_inst->trx), + fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red); */ + + /* Begin to ramp up the power */ + power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0); + + return opstart_compl(&trx->mo, l1_msg); +} + +int gsm_abis_mo_check_attr(const struct gsm_abis_mo *mo, const uint8_t *attr_ids, + unsigned int num_attr_ids) +{ + unsigned int i; + + if (!mo->nm_attr) + return 0; + + for (i = 0; i < num_attr_ids; i++) { + if (!TLVP_PRESENT(mo->nm_attr, attr_ids[i])) + return 0; + } + return 1; +} + +static const uint8_t trx_rqd_attr[] = { NM_ATT_RF_MAXPOWR_R }; + +/* initialize the layer1 */ +static int trx_init(struct gsm_bts_trx *trx) +{ + struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx); + struct msgb *msg; + GsmL1_MphInitReq_t *mi_req; + GsmL1_DeviceParam_t *dev_par; + int oc2g_band; + + if (!gsm_abis_mo_check_attr(&trx->mo, trx_rqd_attr, + ARRAY_SIZE(trx_rqd_attr))) { + /* HACK: spec says we need to decline, but openbsc + * doesn't deal with this very well */ + return oml_mo_opstart_ack(&trx->mo); + //return oml_mo_opstart_nack(&trx->mo, NM_NACK_CANT_PERFORM); + } + + /* Update TRX band */ + trx->bts->band = gsm_arfcn2band(trx->arfcn); + + oc2g_band = oc2gbts_select_oc2g_band(trx, trx->arfcn); + if (oc2g_band < 0) { + LOGP(DL1C, LOGL_ERROR, "Unsupported GSM band %s\n", + gsm_band_name(trx->bts->band)); + } + + msg = l1p_msgb_alloc(); + mi_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphInitReq, fl1h, + l1p_handle_for_trx(trx)); + dev_par = &mi_req->deviceParam; + dev_par->devType = GsmL1_DevType_TxdRxu; + dev_par->freqBand = oc2g_band; + dev_par->u16Arfcn = trx->arfcn; + dev_par->u16BcchArfcn = trx->bts->c0->arfcn; + dev_par->u8NbTsc = trx->bts->bsic & 7; + dev_par->fRxPowerLevel = trx_ms_pwr_ctrl_is_osmo(trx) + ? 0.0 : trx->bts->ul_power_target; + + dev_par->fTxPowerLevel = 0.0; + LOGP(DL1C, LOGL_NOTICE, "Init TRX (Band %d, ARFCN %u, TSC %u, RxPower % 2f dBm, " + "TxPower % 2.2f dBm\n", dev_par->freqBand, dev_par->u16Arfcn, dev_par->u8NbTsc, + dev_par->fRxPowerLevel, dev_par->fTxPowerLevel); + + /* send MPH-INIT-REQ, wait for MPH-INIT-CNF */ + return l1if_gsm_req_compl(fl1h, msg, trx_init_compl_cb, NULL); +} + +uint32_t trx_get_hlayer1(struct gsm_bts_trx *trx) +{ + struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx); + + return fl1h->hLayer1; +} + +static int trx_close_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + msgb_free(l1_msg); + return 0; +} + +int bts_model_trx_close(struct gsm_bts_trx *trx) +{ + struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx); + struct msgb *msg; + + msg = l1p_msgb_alloc(); + prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphCloseReq, fl1h, + l1p_handle_for_trx(trx)); + LOGP(DL1C, LOGL_NOTICE, "Close TRX %u\n", trx->nr); + + return l1if_gsm_req_compl(fl1h, msg, trx_close_compl_cb, NULL); +} + +static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb) +{ + struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx); + uint8_t mute[8]; + int i; + + for (i = 0; i < ARRAY_SIZE(mute); ++i) + mute[i] = locked ? 1 : 0; + + return l1if_mute_rf(fl1h, mute, cb); +} + +int oml_mo_rf_lock_chg(struct gsm_abis_mo *mo, uint8_t mute_state[8], + int success) +{ + if (success) { + int i; + int is_locked = 1; + + for (i = 0; i < 8; ++i) + if (!mute_state[i]) + is_locked = 0; + + mo->nm_state.administrative = + is_locked ? NM_STATE_LOCKED : NM_STATE_UNLOCKED; + mo->procedure_pending = 0; + return oml_mo_statechg_ack(mo); + } else { + mo->procedure_pending = 0; + return oml_mo_statechg_nack(mo, NM_NACK_REQ_NOT_GRANT); + } +} + +static int ts_connect_as(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config pchan, + l1if_compl_cb *cb, void *data) +{ + struct msgb *msg = l1p_msgb_alloc(); + struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(ts->trx); + GsmL1_MphConnectReq_t *cr; + + if (pchan == GSM_PCHAN_TCH_F_PDCH + || pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + LOGP(DL1C, LOGL_ERROR, + "%s Requested TS connect as %s," + " expected a specific pchan instead\n", + gsm_ts_and_pchan_name(ts), gsm_pchan_name(pchan)); + return -EINVAL; + } + + cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConnectReq, fl1h, + l1p_handle_for_ts(ts)); + cr->u8Tn = ts->nr; + cr->logChComb = pchan_to_logChComb[pchan]; + + return l1if_gsm_req_compl(fl1h, msg, cb, NULL); +} + +static int ts_opstart(struct gsm_bts_trx_ts *ts) +{ + enum gsm_phys_chan_config pchan = ts->pchan; + switch (pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + ts->dyn.pchan_is = ts->dyn.pchan_want = GSM_PCHAN_NONE; + /* First connect as NONE, until first RSL CHAN ACT. */ + pchan = GSM_PCHAN_NONE; + break; + case GSM_PCHAN_TCH_F_PDCH: + /* First connect as TCH/F, expecting PDCH ACT. */ + pchan = GSM_PCHAN_TCH_F; + break; + default: + /* simply use ts->pchan */ + break; + } + return ts_connect_as(ts, pchan, opstart_compl_cb, NULL); +} + +GsmL1_Sapi_t lchan_to_GsmL1_Sapi_t(const struct gsm_lchan *lchan) +{ + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + return GsmL1_Sapi_TchF; + case GSM_LCHAN_TCH_H: + return GsmL1_Sapi_TchH; + default: + LOGP(DL1C, LOGL_NOTICE, "%s cannot determine L1 SAPI\n", + gsm_lchan_name(lchan)); + break; + } + return GsmL1_Sapi_Idle; +} + +GsmL1_SubCh_t lchan_to_GsmL1_SubCh_t(const struct gsm_lchan *lchan) +{ + enum gsm_phys_chan_config pchan = lchan->ts->pchan; + + if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) + pchan = lchan->ts->dyn.pchan_want; + + switch (pchan) { + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + if (lchan->type == GSM_LCHAN_CCCH) + return GsmL1_SubCh_NA; + /* fall-through */ + case GSM_PCHAN_TCH_H: + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + return lchan->nr; + case GSM_PCHAN_NONE: + case GSM_PCHAN_CCCH: + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_PDCH: + case GSM_PCHAN_UNKNOWN: + default: + /* case GSM_PCHAN_TCH_F_TCH_H_PDCH: is caught above */ + return GsmL1_SubCh_NA; + } + + return GsmL1_SubCh_NA; +} + +struct sapi_dir { + GsmL1_Sapi_t sapi; + GsmL1_Dir_t dir; +}; + +static const struct sapi_dir ccch_sapis[] = { + { GsmL1_Sapi_Fcch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Sch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Bcch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Agch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Pch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink }, +}; + +static const struct sapi_dir tchf_sapis[] = { + { GsmL1_Sapi_TchF, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_TchF, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_FacchF, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_FacchF, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink }, +}; + +static const struct sapi_dir tchh_sapis[] = { + { GsmL1_Sapi_TchH, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_TchH, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_FacchH, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_FacchH, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink }, +}; + +static const struct sapi_dir sdcch_sapis[] = { + { GsmL1_Sapi_Sdcch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Sdcch, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink }, +}; + +static const struct sapi_dir cbch_sapis[] = { + { GsmL1_Sapi_Cbch, GsmL1_Dir_TxDownlink }, + /* Does the CBCH really have a SACCH in Downlink? */ + { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink }, +}; + +static const struct sapi_dir pdtch_sapis[] = { + { GsmL1_Sapi_Pdtch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Pdtch, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_Ptcch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Prach, GsmL1_Dir_RxUplink }, +#if 0 + { GsmL1_Sapi_Ptcch, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_Pacch, GsmL1_Dir_TxDownlink }, +#endif +}; + +static const struct sapi_dir ho_sapis[] = { + { GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink }, +}; + +struct lchan_sapis { + const struct sapi_dir *sapis; + unsigned int num_sapis; +}; + +static const struct lchan_sapis sapis_for_lchan[_GSM_LCHAN_MAX] = { + [GSM_LCHAN_SDCCH] = { + .sapis = sdcch_sapis, + .num_sapis = ARRAY_SIZE(sdcch_sapis), + }, + [GSM_LCHAN_TCH_F] = { + .sapis = tchf_sapis, + .num_sapis = ARRAY_SIZE(tchf_sapis), + }, + [GSM_LCHAN_TCH_H] = { + .sapis = tchh_sapis, + .num_sapis = ARRAY_SIZE(tchh_sapis), + }, + [GSM_LCHAN_CCCH] = { + .sapis = ccch_sapis, + .num_sapis = ARRAY_SIZE(ccch_sapis), + }, + [GSM_LCHAN_PDTCH] = { + .sapis = pdtch_sapis, + .num_sapis = ARRAY_SIZE(pdtch_sapis), + }, + [GSM_LCHAN_CBCH] = { + .sapis = cbch_sapis, + .num_sapis = ARRAY_SIZE(cbch_sapis), + }, +}; + +static const struct lchan_sapis sapis_for_ho = { + .sapis = ho_sapis, + .num_sapis = ARRAY_SIZE(ho_sapis), +}; + +static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd); +static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd); +static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd); +static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cmd); + +static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir); +static int lchan_deactivate_sapis(struct gsm_lchan *lchan); + +/** + * Execute the first SAPI command of the queue. In case of the markers + * this method is re-entrant so we need to make sure to remove a command + * from the list before calling a function that will queue a command. + * + * \return 0 in case no Gsm Request was sent, 1 otherwise + */ +static int sapi_queue_exeute(struct gsm_lchan *lchan) +{ + int res; + struct sapi_cmd *cmd; + + cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry); + + switch (cmd->type) { + case SAPI_CMD_ACTIVATE: + mph_send_activate_req(lchan, cmd); + res = 1; + break; + case SAPI_CMD_CONFIG_CIPHERING: + mph_send_config_ciphering(lchan, cmd); + res = 1; + break; + case SAPI_CMD_CONFIG_LOGCH_PARAM: + mph_send_config_logchpar(lchan, cmd); + res = 1; + break; + case SAPI_CMD_SACCH_REL_MARKER: + llist_del(&cmd->entry); + talloc_free(cmd); + res = check_sapi_release(lchan, GsmL1_Sapi_Sacch, + GsmL1_Dir_TxDownlink); + res |= check_sapi_release(lchan, GsmL1_Sapi_Sacch, + GsmL1_Dir_RxUplink); + break; + case SAPI_CMD_REL_MARKER: + llist_del(&cmd->entry); + talloc_free(cmd); + res = lchan_deactivate_sapis(lchan); + break; + case SAPI_CMD_DEACTIVATE: + mph_send_deactivate_req(lchan, cmd); + res = 1; + break; + default: + LOGP(DL1C, LOGL_NOTICE, + "Unimplemented command type %d\n", cmd->type); + llist_del(&cmd->entry); + talloc_free(cmd); + res = 0; + abort(); + break; + } + + return res; +} + +static void sapi_queue_send(struct gsm_lchan *lchan) +{ + int res; + + do { + res = sapi_queue_exeute(lchan); + } while (res == 0 && !llist_empty(&lchan->sapi_cmds)); +} + +static void sapi_queue_dispatch(struct gsm_lchan *lchan, int status) +{ + int end; + struct sapi_cmd *cmd = llist_entry(lchan->sapi_cmds.next, + struct sapi_cmd, entry); + llist_del(&cmd->entry); + end = llist_empty(&lchan->sapi_cmds); + + if (cmd->callback) + cmd->callback(lchan, status); + talloc_free(cmd); + + if (end || llist_empty(&lchan->sapi_cmds)) { + LOGP(DL1C, LOGL_DEBUG, + "%s End of SAPI cmd queue encountered.%s\n", + gsm_lchan_name(lchan), + llist_empty(&lchan->sapi_cmds) + ? " Queue is now empty." + : " More pending."); + return; + } + + sapi_queue_send(lchan); +} + +/** + * Queue and possible execute a SAPI command. Return 1 in case the command was + * already executed and 0 in case if it was only put into the queue + */ +static int queue_sapi_command(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + int start = llist_empty(&lchan->sapi_cmds); + llist_add_tail(&cmd->entry, &lchan->sapi_cmds); + + if (!start) + return 0; + + sapi_queue_send(lchan); + return 1; +} + +static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + enum lchan_sapi_state status; + struct sapi_cmd *cmd; + struct gsm_lchan *lchan; + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphActivateCnf_t *ic = &l1p->u.mphActivateCnf; + + /* get the lchan from the information we supplied */ + lchan = l1if_hLayer_to_lchan(trx, (uint32_t)ic->hLayer3); + if (!lchan) { + LOGP(DL1C, LOGL_ERROR, + "Failed to find lchan for hLayer3=0x%x\n", (uint32_t)ic->hLayer3); + goto err; + } + + LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.conf (%s ", + gsm_lchan_name(lchan), + get_value_string(oc2gbts_l1sapi_names, ic->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(oc2gbts_dir_names, ic->dir)); + + if (ic->status == GsmL1_Status_Success) { + DEBUGP(DL1C, "Successful activation of L1 SAPI %s on TS %u\n", + get_value_string(oc2gbts_l1sapi_names, ic->sapi), ic->u8Tn); + status = LCHAN_SAPI_S_ASSIGNED; + } else { + LOGP(DL1C, LOGL_ERROR, "Error activating L1 SAPI %s on TS %u: %s\n", + get_value_string(oc2gbts_l1sapi_names, ic->sapi), ic->u8Tn, + get_value_string(oc2gbts_l1status_names, ic->status)); + status = LCHAN_SAPI_S_ERROR; + } + + if (ic->dir & GsmL1_Dir_TxDownlink) + lchan->sapis_dl[ic->sapi] = status; + if (ic->dir & GsmL1_Dir_RxUplink) + lchan->sapis_ul[ic->sapi] = status; + + if (llist_empty(&lchan->sapi_cmds)) { + LOGP(DL1C, LOGL_ERROR, + "%s Got activation confirmation with empty queue\n", + gsm_lchan_name(lchan)); + goto err; + } + + cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry); + if (cmd->sapi != ic->sapi || cmd->dir != ic->dir || + cmd->type != SAPI_CMD_ACTIVATE) { + LOGP(DL1C, LOGL_ERROR, + "%s Confirmation mismatch (%d, %d) (%d, %d)\n", + gsm_lchan_name(lchan), cmd->sapi, cmd->dir, + ic->sapi, ic->dir); + goto err; + } + + sapi_queue_dispatch(lchan, ic->status); + +err: + msgb_free(l1_msg); + + return 0; +} + +uint32_t l1if_lchan_to_hLayer(struct gsm_lchan *lchan) +{ + return 0xBB + | (lchan->nr << 8) + | (lchan->ts->nr << 16) + | (lchan->ts->trx->nr << 24); +} + +/* obtain a ptr to the lapdm_channel for a given hLayer */ +struct gsm_lchan * +l1if_hLayer_to_lchan(struct gsm_bts_trx *trx, uint32_t hLayer2) +{ + uint8_t magic = hLayer2 & 0xff; + uint8_t ts_nr = (hLayer2 >> 16) & 0xff; + uint8_t lchan_nr = (hLayer2 >> 8)& 0xff; + struct gsm_bts_trx_ts *ts; + + if (magic != 0xBB) + return NULL; + + /* FIXME: if we actually run on the BTS, the 32bit field is large + * enough to simply put a pointer inside. */ + if (ts_nr >= ARRAY_SIZE(trx->ts)) + return NULL; + + ts = &trx->ts[ts_nr]; + + if (lchan_nr >= ARRAY_SIZE(ts->lchan)) + return NULL; + + return &ts->lchan[lchan_nr]; +} + +/* we regularly check if the DSP L1 is still sending us primitives. + * if not, we simply stop the BTS program (and be re-spawned) */ +static void alive_timer_cb(void *data) +{ + struct oc2gl1_hdl *fl1h = data; + + if (fl1h->alive_prim_cnt == 0) { + LOGP(DL1C, LOGL_FATAL, "DSP L1 is no longer sending primitives!\n"); + exit(23); + } + fl1h->alive_prim_cnt = 0; + osmo_timer_schedule(&fl1h->alive_timer, 5, 0); +} + +static void clear_amr_params(GsmL1_LogChParam_t *lch_par) +{ + int j; + /* common for the SIGN, V1 and EFR: */ + lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_NA; + lch_par->tch.amrInitCodecMode = GsmL1_AmrCodecMode_Unset; + for (j = 0; j < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); j++) + lch_par->tch.amrActiveCodecSet[j] = GsmL1_AmrCodec_Unset; +} + +static void set_payload_format(GsmL1_LogChParam_t *lch_par) +{ + lch_par->tch.tchPlFmt = GsmL1_TchPlFmt_Rtp; +} + +static void lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan) +{ + struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr; + struct gsm48_multi_rate_conf *mr_conf = + (struct gsm48_multi_rate_conf *) amr_mrc->gsm48_ie; + int j; + + LOGP(DL1C, LOGL_INFO, "%s: %s tch_mode=0x%02x\n", + gsm_lchan_name(lchan), __FUNCTION__, lchan->tch_mode); + + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: + /* we have to set some TCH payload type even if we don't + * know yet what codec we will use later on */ + if (lchan->type == GSM_LCHAN_TCH_F) + lch_par->tch.tchPlType = GsmL1_TchPlType_Fr; + else + lch_par->tch.tchPlType = GsmL1_TchPlType_Hr; + clear_amr_params(lch_par); + break; + case GSM48_CMODE_SPEECH_V1: + if (lchan->type == GSM_LCHAN_TCH_F) + lch_par->tch.tchPlType = GsmL1_TchPlType_Fr; + else + lch_par->tch.tchPlType = GsmL1_TchPlType_Hr; + set_payload_format(lch_par); + clear_amr_params(lch_par); + break; + case GSM48_CMODE_SPEECH_EFR: + lch_par->tch.tchPlType = GsmL1_TchPlType_Efr; + set_payload_format(lch_par); + clear_amr_params(lch_par); + break; + case GSM48_CMODE_SPEECH_AMR: + lch_par->tch.tchPlType = GsmL1_TchPlType_Amr; + set_payload_format(lch_par); + lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_Odd; /* FIXME? */ + lch_par->tch.amrInitCodecMode = amr_get_initial_mode(lchan); + + /* initialize to clean state */ + for (j = 0; j < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); j++) + lch_par->tch.amrActiveCodecSet[j] = GsmL1_AmrCodec_Unset; + + j = 0; + if (mr_conf->m4_75) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_4_75; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + + if (mr_conf->m5_15) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_5_15; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + + if (mr_conf->m5_90) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_5_9; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + + if (mr_conf->m6_70) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_6_7; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + + if (mr_conf->m7_40) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_7_4; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + + if (mr_conf->m7_95) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_7_95; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + + if (mr_conf->m10_2) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_10_2; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + if (mr_conf->m12_2) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_12_2; + break; + case GSM48_CMODE_DATA_14k5: + case GSM48_CMODE_DATA_12k0: + case GSM48_CMODE_DATA_6k0: + case GSM48_CMODE_DATA_3k6: + LOGP(DL1C, LOGL_ERROR, "%s: CSD not supported!\n", + gsm_lchan_name(lchan)); + break; + } +} + +static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(lchan->ts->trx); + struct msgb *msg = l1p_msgb_alloc(); + int sapi = cmd->sapi; + int dir = cmd->dir; + GsmL1_MphActivateReq_t *act_req; + GsmL1_LogChParam_t *lch_par; + + act_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphActivateReq, + fl1h, l1p_handle_for_lchan(lchan)); + lch_par = &act_req->logChPrm; + act_req->u8Tn = lchan->ts->nr; + act_req->subCh = lchan_to_GsmL1_SubCh_t(lchan); + act_req->dir = dir; + act_req->sapi = sapi; + act_req->hLayer2 = (HANDLE *)l1if_lchan_to_hLayer(lchan); + act_req->hLayer3 = act_req->hLayer2; + + switch (act_req->sapi) { + case GsmL1_Sapi_Rach: + lch_par->rach.u8Bsic = lchan->ts->trx->bts->bsic; + break; + case GsmL1_Sapi_Agch: + lch_par->agch.u8NbrOfAgch = num_agch(lchan->ts->trx, lchan->name); + break; + case GsmL1_Sapi_TchH: + case GsmL1_Sapi_TchF: + lchan2lch_par(lch_par, lchan); + /* + * Be sure that every packet is received, even if it + * fails. In this case the length might be lower or 0. + */ + act_req->fBFILevel = -200.0f; + break; + case GsmL1_Sapi_Ptcch: + lch_par->ptcch.u8Bsic = lchan->ts->trx->bts->bsic; + break; + case GsmL1_Sapi_Prach: + lch_par->prach.u8Bsic = lchan->ts->trx->bts->bsic; + break; + case GsmL1_Sapi_Sacch: + /* + * For the SACCH we need to set the u8MsPowerLevel when + * doing manual MS power control. + */ + if (trx_ms_pwr_ctrl_is_osmo(lchan->ts->trx)) + lch_par->sacch.u8MsPowerLevel = lchan->ms_power_ctrl.current; + /* fall through */ + case GsmL1_Sapi_Pdtch: + case GsmL1_Sapi_Pacch: + /* + * Be sure that every packet is received, even if it + * fails. In this case the length might be lower or 0. + */ + act_req->fBFILevel = -200.0f; + break; + default: + break; + } + + LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.req (hL2=0x%08x, %s ", + gsm_lchan_name(lchan), (uint32_t)act_req->hLayer2, + get_value_string(oc2gbts_l1sapi_names, act_req->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(oc2gbts_dir_names, act_req->dir)); + + /* send the primitive for all GsmL1_Sapi_* that match the LCHAN */ + return l1if_gsm_req_compl(fl1h, msg, lchan_act_compl_cb, NULL); +} + +static void sapi_clear_queue(struct llist_head *queue) +{ + struct sapi_cmd *next, *tmp; + + llist_for_each_entry_safe(next, tmp, queue, entry) { + llist_del(&next->entry); + talloc_free(next); + } +} + +static int sapi_activate_cb(struct gsm_lchan *lchan, int status) +{ + struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(lchan->ts->trx); + + /* FIXME: Error handling */ + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_ERROR, + "%s act failed mark broken due status: %d\n", + gsm_lchan_name(lchan), status); + lchan_set_state(lchan, LCHAN_S_BROKEN); + sapi_clear_queue(&lchan->sapi_cmds); + mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, RSL_ERR_PROCESSOR_OVERLOAD); + return -1; + } + + if (!llist_empty(&lchan->sapi_cmds)) + return 0; + + if (lchan->state != LCHAN_S_ACT_REQ) + return 0; + + lchan_set_state(lchan, LCHAN_S_ACTIVE); + mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, 0); + + /* set the initial ciphering parameters for both directions */ + l1if_set_ciphering(fl1h, lchan, 1); + l1if_set_ciphering(fl1h, lchan, 0); + if (lchan->encr.alg_id) + lchan->ciph_state = LCHAN_CIPH_RXTX_REQ; + else + lchan->ciph_state = LCHAN_CIPH_NONE; + + return 0; +} + +static void enqueue_sapi_act_cmd(struct gsm_lchan *lchan, int sapi, int dir) +{ + struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + + cmd->sapi = sapi; + cmd->dir = dir; + cmd->type = SAPI_CMD_ACTIVATE; + cmd->callback = sapi_activate_cb; + queue_sapi_command(lchan, cmd); +} + +int lchan_activate(struct gsm_lchan *lchan) +{ + struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(lchan->ts->trx); + const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type]; + unsigned int i; + + lchan_set_state(lchan, LCHAN_S_ACT_REQ); + + if (!llist_empty(&lchan->sapi_cmds)) + LOGP(DL1C, LOGL_ERROR, + "%s Trying to activate lchan, but commands in queue\n", + gsm_lchan_name(lchan)); + + /* override the regular SAPIs if this is the first hand-over + * related activation of the LCHAN */ + if (lchan->ho.active == HANDOVER_ENABLED) + s4l = &sapis_for_ho; + + for (i = 0; i < s4l->num_sapis; i++) { + int sapi = s4l->sapis[i].sapi; + int dir = s4l->sapis[i].dir; + + if (sapi == GsmL1_Sapi_Sch) { + /* once we activate the SCH, we should get MPH-TIME.ind */ + fl1h->alive_timer.cb = alive_timer_cb; + fl1h->alive_timer.data = fl1h; + fl1h->alive_prim_cnt = 0; + osmo_timer_schedule(&fl1h->alive_timer, 5, 0); + } + enqueue_sapi_act_cmd(lchan, sapi, dir); + } + +#warning "FIXME: Should this be in sapi_activate_cb?" + lchan_init_lapdm(lchan); + + return 0; +} + +const struct value_string oc2gbts_l1cfgt_names[] = { + { GsmL1_ConfigParamId_SetNbTsc, "Set NB TSC" }, + { GsmL1_ConfigParamId_SetTxPowerLevel, "Set Tx power level" }, + { GsmL1_ConfigParamId_SetLogChParams, "Set logical channel params" }, + { GsmL1_ConfigParamId_SetCipheringParams,"Configure ciphering params" }, + { GsmL1_ConfigParamId_Set8pskPowerReduction, "Set 8PSK Tx power reduction" }, + { 0, NULL } +}; + +static void dump_lch_par(int logl, GsmL1_LogChParam_t *lch_par, GsmL1_Sapi_t sapi) +{ + int i; + + switch (sapi) { + case GsmL1_Sapi_Rach: + LOGPC(DL1C, logl, "BSIC=0x%08x", lch_par->rach.u8Bsic); + break; + case GsmL1_Sapi_Agch: + LOGPC(DL1C, logl, "BS_AG_BLKS_RES=%u ", + lch_par->agch.u8NbrOfAgch); + break; + case GsmL1_Sapi_Sacch: + LOGPC(DL1C, logl, "MS Power Level 0x%02x", + lch_par->sacch.u8MsPowerLevel); + break; + case GsmL1_Sapi_TchF: + case GsmL1_Sapi_TchH: + LOGPC(DL1C, logl, "amrCmiPhase=0x%02x amrInitCodec=0x%02x (", + lch_par->tch.amrCmiPhase, + lch_par->tch.amrInitCodecMode); + for (i = 0; i < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); i++) { + LOGPC(DL1C, logl, "%x ", + lch_par->tch.amrActiveCodecSet[i]); + } + break; + /* FIXME: PRACH / PTCCH */ + default: + break; + } + LOGPC(DL1C, logl, ")\n"); +} + +static int chmod_txpower_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf; + + LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ", + gsm_trx_name(trx), + get_value_string(oc2gbts_l1cfgt_names, cc->cfgParamId)); + + LOGPC(DL1C, LOGL_INFO, "setTxPower %f dBm\n", + cc->cfgParams.setTxPowerLevel.fTxPowerLevel); + + power_trx_change_compl(trx, + (int) (cc->cfgParams.setTxPowerLevel.fTxPowerLevel * 1000)); + + msgb_free(l1_msg); + + return 0; +} + +static int chmod_txpower_backoff_8psk_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf; + + LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ", + gsm_trx_name(trx), + get_value_string(oc2gbts_l1cfgt_names, cc->cfgParamId)); + + LOGPC(DL1C, LOGL_INFO, "Backoff %u dB\n", + cc->cfgParams.set8pskPowerReduction.u8PowerReduction); + + msgb_free(l1_msg); + + return 0; +} + +static int chmod_max_cell_size_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + Oc2g_Prim_t *sysp = msgb_sysprim(resp); + Oc2g_SetMaxCellSizeCnf_t *sac = &sysp->u.setMaxCellSizeCnf; + + LOGP(DL1C, LOGL_INFO, "%s Rx SYS prim %s -> %s\n", + gsm_trx_name(trx), + get_value_string(oc2gbts_sysprim_names, sysp->id), + get_value_string(oc2gbts_l1status_names, sac->status)); + + msgb_free(resp); + + return 0; +} + +static int chmod_c0_idle_pwr_red_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + Oc2g_Prim_t *sysp = msgb_sysprim(resp); + Oc2g_SetC0IdleSlotPowerReductionCnf_t *sac = &sysp->u.setC0IdleSlotPowerReductionCnf; + + LOGP(DL1C, LOGL_INFO, "%s Rx SYS prim %s -> %s\n", + gsm_trx_name(trx), + get_value_string(oc2gbts_sysprim_names, sysp->id), + get_value_string(oc2gbts_l1status_names, sac->status)); + + msgb_free(resp); + + return 0; +} + +static int chmod_modif_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + struct gsm_lchan *lchan; + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf; + + /* get the lchan from the information we supplied */ + lchan = l1if_hLayer_to_lchan(trx, (uint32_t)cc->hLayer3); + if (!lchan) { + LOGP(DL1C, LOGL_ERROR, + "Failed to find lchan for hLayer3=0x%x\n", (uint32_t)cc->hLayer3); + goto err; + } + + LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ", + gsm_lchan_name(lchan), + get_value_string(oc2gbts_l1cfgt_names, cc->cfgParamId)); + + switch (cc->cfgParamId) { + case GsmL1_ConfigParamId_SetLogChParams: + dump_lch_par(LOGL_INFO, + &cc->cfgParams.setLogChParams.logChParams, + cc->cfgParams.setLogChParams.sapi); + + sapi_queue_dispatch(lchan, cc->status); + break; + case GsmL1_ConfigParamId_SetCipheringParams: + switch (lchan->ciph_state) { + case LCHAN_CIPH_RX_REQ: + LOGPC(DL1C, LOGL_INFO, "RX_REQ -> RX_CONF\n"); + lchan->ciph_state = LCHAN_CIPH_RX_CONF; + break; + case LCHAN_CIPH_RX_CONF_TX_REQ: + LOGPC(DL1C, LOGL_INFO, "RX_CONF_TX_REQ -> RXTX_CONF\n"); + lchan->ciph_state = LCHAN_CIPH_RXTX_CONF; + break; + case LCHAN_CIPH_RXTX_REQ: + LOGPC(DL1C, LOGL_INFO, "RXTX_REQ -> RX_CONF_TX_REQ\n"); + lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ; + break; + case LCHAN_CIPH_NONE: + LOGPC(DL1C, LOGL_INFO, "\n"); + break; + default: + LOGPC(DL1C, LOGL_INFO, "unhandled state %u\n", lchan->ciph_state); + break; + } + if (llist_empty(&lchan->sapi_cmds)) { + LOGP(DL1C, LOGL_ERROR, + "%s Got ciphering conf with empty queue\n", + gsm_lchan_name(lchan)); + goto err; + } + + sapi_queue_dispatch(lchan, cc->status); + break; + case GsmL1_ConfigParamId_SetNbTsc: + default: + LOGPC(DL1C, LOGL_INFO, "\n"); + break; + } + +err: + msgb_free(l1_msg); + + return 0; +} + +static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + struct gsm_bts_trx *trx = lchan->ts->trx; + struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx); + struct msgb *msg = l1p_msgb_alloc(); + GsmL1_MphConfigReq_t *conf_req; + GsmL1_LogChParam_t *lch_par; + + /* channel mode, encryption and/or multirate have changed */ + + /* update multi-rate config */ + conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h, + l1p_handle_for_lchan(lchan)); + conf_req->cfgParamId = GsmL1_ConfigParamId_SetLogChParams; + conf_req->cfgParams.setLogChParams.sapi = cmd->sapi; + conf_req->cfgParams.setLogChParams.u8Tn = lchan->ts->nr; + conf_req->cfgParams.setLogChParams.subCh = lchan_to_GsmL1_SubCh_t(lchan); + conf_req->cfgParams.setLogChParams.dir = cmd->dir; + conf_req->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan); + + lch_par = &conf_req->cfgParams.setLogChParams.logChParams; + lchan2lch_par(lch_par, lchan); + + /* Update the MS Power Level */ + if (cmd->sapi == GsmL1_Sapi_Sacch && trx_ms_pwr_ctrl_is_osmo(trx)) + lch_par->sacch.u8MsPowerLevel = lchan->ms_power_ctrl.current; + + /* FIXME: update encryption */ + + LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.req (%s) ", + gsm_lchan_name(lchan), + get_value_string(oc2gbts_l1sapi_names, + conf_req->cfgParams.setLogChParams.sapi)); + LOGPC(DL1C, LOGL_INFO, "cfgParams Tn=%u, subCh=%u, dir=0x%x ", + conf_req->cfgParams.setLogChParams.u8Tn, + conf_req->cfgParams.setLogChParams.subCh, + conf_req->cfgParams.setLogChParams.dir); + dump_lch_par(LOGL_INFO, + &conf_req->cfgParams.setLogChParams.logChParams, + conf_req->cfgParams.setLogChParams.sapi); + + return l1if_gsm_req_compl(fl1h, msg, chmod_modif_compl_cb, NULL); +} + +static void enqueue_sapi_logchpar_cmd(struct gsm_lchan *lchan, int dir, GsmL1_Sapi_t sapi) +{ + struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + + cmd->dir = dir; + cmd->sapi = sapi; + cmd->type = SAPI_CMD_CONFIG_LOGCH_PARAM; + queue_sapi_command(lchan, cmd); +} + +static int tx_confreq_logchpar(struct gsm_lchan *lchan, uint8_t direction) +{ + enqueue_sapi_logchpar_cmd(lchan, direction, lchan_to_GsmL1_Sapi_t(lchan)); + return 0; +} + +int l1if_set_txpower(struct oc2gl1_hdl *fl1h, float tx_power) +{ + struct msgb *msg = l1p_msgb_alloc(); + GsmL1_MphConfigReq_t *conf_req; + + conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h, 0); + conf_req->cfgParamId = GsmL1_ConfigParamId_SetTxPowerLevel; + conf_req->cfgParams.setTxPowerLevel.fTxPowerLevel = tx_power; + + return l1if_gsm_req_compl(fl1h, msg, chmod_txpower_compl_cb, NULL); +} + +int l1if_set_txpower_backoff_8psk(struct oc2gl1_hdl *fl1h, uint8_t backoff) +{ + struct msgb *msg = l1p_msgb_alloc(); + GsmL1_MphConfigReq_t *conf_req; + + conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h, 0); + conf_req->cfgParamId = GsmL1_ConfigParamId_Set8pskPowerReduction; + conf_req->cfgParams.set8pskPowerReduction.u8PowerReduction = backoff; + + return l1if_gsm_req_compl(fl1h, msg, chmod_txpower_backoff_8psk_compl_cb, NULL); +} + +int l1if_set_max_cell_size(struct oc2gl1_hdl *fl1h, uint8_t cell_size) +{ + struct msgb *msg = sysp_msgb_alloc(); + Oc2g_Prim_t *sys_prim = msgb_sysprim(msg); + sys_prim->id = Oc2g_PrimId_SetMaxCellSizeReq; + sys_prim->u.setMaxCellSizeReq.u8MaxCellSize = cell_size; + + LOGP(DL1C, LOGL_INFO, "%s Set max cell size = %d qbits\n", + gsm_trx_name(fl1h->phy_inst->trx), + cell_size); + + return l1if_req_compl(fl1h, msg, chmod_max_cell_size_compl_cb, NULL); + +} + +int l1if_set_txpower_c0_idle_pwr_red(struct oc2gl1_hdl *fl1h, uint8_t red) +{ + struct msgb *msg = sysp_msgb_alloc(); + Oc2g_Prim_t *sys_prim = msgb_sysprim(msg); + sys_prim->id = Oc2g_PrimId_SetC0IdleSlotPowerReductionReq; + sys_prim->u.setC0IdleSlotPowerReductionReq.u8PowerReduction = red; + + LOGP(DL1C, LOGL_INFO, "%s Set C0 idle slot power reduction = %d dB\n", + gsm_trx_name(fl1h->phy_inst->trx), + red); + + return l1if_req_compl(fl1h, msg, chmod_c0_idle_pwr_red_compl_cb, NULL); +} + +const enum GsmL1_CipherId_t rsl2l1_ciph[] = { + [0] = GsmL1_CipherId_A50, + [1] = GsmL1_CipherId_A50, + [2] = GsmL1_CipherId_A51, + [3] = GsmL1_CipherId_A52, + [4] = GsmL1_CipherId_A53, +}; + +static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(lchan->ts->trx); + struct msgb *msg = l1p_msgb_alloc(); + struct GsmL1_MphConfigReq_t *cfgr; + + cfgr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h, + l1p_handle_for_lchan(lchan)); + + cfgr->cfgParamId = GsmL1_ConfigParamId_SetCipheringParams; + cfgr->cfgParams.setCipheringParams.u8Tn = lchan->ts->nr; + cfgr->cfgParams.setCipheringParams.subCh = lchan_to_GsmL1_SubCh_t(lchan); + cfgr->cfgParams.setCipheringParams.dir = cmd->dir; + cfgr->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan); + + if (lchan->encr.alg_id >= ARRAY_SIZE(rsl2l1_ciph)) + return -EINVAL; + cfgr->cfgParams.setCipheringParams.cipherId = rsl2l1_ciph[lchan->encr.alg_id]; + + LOGP(DL1C, LOGL_NOTICE, "%s SET_CIPHERING (ALG=%u %s)\n", + gsm_lchan_name(lchan), + cfgr->cfgParams.setCipheringParams.cipherId, + get_value_string(oc2gbts_dir_names, + cfgr->cfgParams.setCipheringParams.dir)); + + memcpy(cfgr->cfgParams.setCipheringParams.u8Kc, + lchan->encr.key, lchan->encr.key_len); + + return l1if_gsm_req_compl(fl1h, msg, chmod_modif_compl_cb, NULL); +} + +static void enqueue_sapi_ciphering_cmd(struct gsm_lchan *lchan, int dir) +{ + struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + + cmd->dir = dir; + cmd->type = SAPI_CMD_CONFIG_CIPHERING; + queue_sapi_command(lchan, cmd); +} + +int l1if_set_ciphering(struct oc2gl1_hdl *fl1h, + struct gsm_lchan *lchan, + int dir_downlink) +{ + int dir; + + /* ignore the request when the channel is not active */ + if (lchan->state != LCHAN_S_ACTIVE) + return -1; + + if (dir_downlink) + dir = GsmL1_Dir_TxDownlink; + else + dir = GsmL1_Dir_RxUplink; + + enqueue_sapi_ciphering_cmd(lchan, dir); + + return 0; +} + +int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) +{ + if (lchan->state != LCHAN_S_ACTIVE) + return -1; + + enqueue_sapi_logchpar_cmd(lchan, GsmL1_Dir_RxUplink, GsmL1_Sapi_Sacch); + return 0; +} + +int l1if_rsl_mode_modify(struct gsm_lchan *lchan) +{ + if (lchan->state != LCHAN_S_ACTIVE) + return -1; + + /* channel mode, encryption and/or multirate have changed */ + + /* update multi-rate config */ + tx_confreq_logchpar(lchan, GsmL1_Dir_RxUplink); + tx_confreq_logchpar(lchan, GsmL1_Dir_TxDownlink); + + /* FIXME: update encryption */ + + return 0; +} + +static int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + enum lchan_sapi_state status; + struct sapi_cmd *cmd; + struct gsm_lchan *lchan; + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphDeactivateCnf_t *ic = &l1p->u.mphDeactivateCnf; + + lchan = l1if_hLayer_to_lchan(trx, (uint32_t)ic->hLayer3); + if (!lchan) { + LOGP(DL1C, LOGL_ERROR, + "Failed to find lchan for hLayer3=0x%x\n", (uint32_t)ic->hLayer3); + goto err; + } + + LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.conf (%s ", + gsm_lchan_name(lchan), + get_value_string(oc2gbts_l1sapi_names, ic->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(oc2gbts_dir_names, ic->dir)); + + if (ic->status == GsmL1_Status_Success) { + DEBUGP(DL1C, "Successful deactivation of L1 SAPI %s on TS %u\n", + get_value_string(oc2gbts_l1sapi_names, ic->sapi), ic->u8Tn); + status = LCHAN_SAPI_S_NONE; + } else { + LOGP(DL1C, LOGL_ERROR, "Error deactivating L1 SAPI %s on TS %u: %s\n", + get_value_string(oc2gbts_l1sapi_names, ic->sapi), ic->u8Tn, + get_value_string(oc2gbts_l1status_names, ic->status)); + status = LCHAN_SAPI_S_ERROR; + } + + if (ic->dir & GsmL1_Dir_TxDownlink) + lchan->sapis_dl[ic->sapi] = status; + if (ic->dir & GsmL1_Dir_RxUplink) + lchan->sapis_ul[ic->sapi] = status; + + + if (llist_empty(&lchan->sapi_cmds)) { + LOGP(DL1C, LOGL_ERROR, + "%s Got de-activation confirmation with empty queue\n", + gsm_lchan_name(lchan)); + goto err; + } + + cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry); + if (cmd->sapi != ic->sapi || cmd->dir != ic->dir || + cmd->type != SAPI_CMD_DEACTIVATE) { + LOGP(DL1C, LOGL_ERROR, + "%s Confirmation mismatch (%d, %d) (%d, %d)\n", + gsm_lchan_name(lchan), cmd->sapi, cmd->dir, + ic->sapi, ic->dir); + goto err; + } + + sapi_queue_dispatch(lchan, ic->status); + +err: + msgb_free(l1_msg); + return 0; +} + +static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(lchan->ts->trx); + struct msgb *msg = l1p_msgb_alloc(); + GsmL1_MphDeactivateReq_t *deact_req; + + deact_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDeactivateReq, + fl1h, l1p_handle_for_lchan(lchan)); + deact_req->u8Tn = lchan->ts->nr; + deact_req->subCh = lchan_to_GsmL1_SubCh_t(lchan); + deact_req->dir = cmd->dir; + deact_req->sapi = cmd->sapi; + deact_req->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan); + + LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.req (%s ", + gsm_lchan_name(lchan), + get_value_string(oc2gbts_l1sapi_names, deact_req->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(oc2gbts_dir_names, deact_req->dir)); + + /* send the primitive for all GsmL1_Sapi_* that match the LCHAN */ + return l1if_gsm_req_compl(fl1h, msg, lchan_deact_compl_cb, NULL); +} + +static int sapi_deactivate_cb(struct gsm_lchan *lchan, int status) +{ + /* FIXME: Error handling. There is no NACK... */ + if (status != GsmL1_Status_Success && lchan->state == LCHAN_S_REL_REQ) { + LOGP(DL1C, LOGL_ERROR, "%s is now broken. Stopping the release.\n", + gsm_lchan_name(lchan)); + lchan_set_state(lchan, LCHAN_S_BROKEN); + sapi_clear_queue(&lchan->sapi_cmds); + mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0); + return -1; + } + + if (!llist_empty(&lchan->sapi_cmds)) + return 0; + + /* Don't send an REL ACK on SACCH deactivate */ + if (lchan->state != LCHAN_S_REL_REQ) + return 0; + + lchan_set_state(lchan, LCHAN_S_NONE); + mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0); + + /* Reactivate CCCH due to SI3 update in RSL */ + if (lchan->rel_act_kind == LCHAN_REL_ACT_REACT) { + lchan->rel_act_kind = LCHAN_REL_ACT_RSL; + lchan_activate(lchan); + } + return 0; +} + +static int enqueue_sapi_deact_cmd(struct gsm_lchan *lchan, int sapi, int dir) +{ + struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + + cmd->sapi = sapi; + cmd->dir = dir; + cmd->type = SAPI_CMD_DEACTIVATE; + cmd->callback = sapi_deactivate_cb; + return queue_sapi_command(lchan, cmd); +} + +/* + * Release the SAPI if it was allocated. E.g. the SACCH might be already + * deactivated or during a hand-over the TCH was not allocated yet. + */ +static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir) +{ + /* check if we should schedule a release */ + if (dir & GsmL1_Dir_TxDownlink) { + if (lchan->sapis_dl[sapi] != LCHAN_SAPI_S_ASSIGNED) + return 0; + lchan->sapis_dl[sapi] = LCHAN_SAPI_S_REL; + } else if (dir & GsmL1_Dir_RxUplink) { + if (lchan->sapis_ul[sapi] != LCHAN_SAPI_S_ASSIGNED) + return 0; + lchan->sapis_ul[sapi] = LCHAN_SAPI_S_REL; + } + + /* now schedule the command and maybe dispatch it */ + return enqueue_sapi_deact_cmd(lchan, sapi, dir); +} + +static int release_sapis_for_ho(struct gsm_lchan *lchan) +{ + int res = 0; + int i; + + const struct lchan_sapis *s4l = &sapis_for_ho; + + for (i = s4l->num_sapis-1; i >= 0; i--) + res |= check_sapi_release(lchan, + s4l->sapis[i].sapi, s4l->sapis[i].dir); + return res; +} + +static int lchan_deactivate_sapis(struct gsm_lchan *lchan) +{ + struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(lchan->ts->trx); + const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type]; + int i, res; + + res = 0; + + /* The order matters.. the Facch needs to be released first */ + for (i = s4l->num_sapis-1; i >= 0; i--) { + /* Stop the alive timer once we deactivate the SCH */ + if (s4l->sapis[i].sapi == GsmL1_Sapi_Sch) + osmo_timer_del(&fl1h->alive_timer); + + /* Release if it was allocated */ + res |= check_sapi_release(lchan, s4l->sapis[i].sapi, s4l->sapis[i].dir); + } + + /* always attempt to disable the RACH burst */ + res |= release_sapis_for_ho(lchan); + + /* nothing was queued */ + if (res == 0) { + LOGP(DL1C, LOGL_ERROR, "%s all SAPIs already released?\n", + gsm_lchan_name(lchan)); + lchan_set_state(lchan, LCHAN_S_BROKEN); + mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0); + } + + return res; +} + +static void enqueue_rel_marker(struct gsm_lchan *lchan) +{ + struct sapi_cmd *cmd; + + /* remember we need to release all active SAPIs */ + cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + cmd->type = SAPI_CMD_REL_MARKER; + queue_sapi_command(lchan, cmd); +} + +int bts_model_lchan_deactivate(struct gsm_lchan *lchan) +{ + lchan_set_state(lchan, LCHAN_S_REL_REQ); + enqueue_rel_marker(lchan); + return 0; +} + +static void enqueue_sacch_rel_marker(struct gsm_lchan *lchan) +{ + struct sapi_cmd *cmd; + + /* remember we need to check if the SACCH is allocated */ + cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + cmd->type = SAPI_CMD_SACCH_REL_MARKER; + queue_sapi_command(lchan, cmd); +} + +int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan) +{ + enqueue_sacch_rel_marker(lchan); + return 0; +} + +/* callback from OML */ +int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, + struct tlv_parsed *old_attr, struct tlv_parsed *new_attr, + void *obj) +{ + /* FIXME: more checks if the attributes are valid */ + + switch (msg_type) { + case NM_MT_SET_CHAN_ATTR: + /* our L1 only supports one global TSC for all channels + * one one TRX, so we need to make sure not to activate + * channels with a different TSC!! */ + if (TLVP_PRESENT(new_attr, NM_ATT_TSC) && + TLVP_LEN(new_attr, NM_ATT_TSC) >= 1 && + *TLVP_VAL(new_attr, NM_ATT_TSC) != (bts->bsic & 7)) { + LOGP(DOML, LOGL_ERROR, "Channel TSC %u != BSIC-TSC %u\n", + *TLVP_VAL(new_attr, NM_ATT_TSC), bts->bsic & 7); + return -NM_NACK_PARAM_RANGE; + } + break; + } + return 0; +} + +/* callback from OML */ +int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, + struct tlv_parsed *new_attr, int kind, void *obj) +{ + if (kind == NM_OC_RADIO_CARRIER) { + struct gsm_bts_trx *trx = obj; + struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx); + /* convert max TA to max cell size in qbits */ + uint8_t cell_size = bts->max_ta << 2; + + /* We do not need to check for L1 handle + * because the max cell size parameter can receive before MphInit */ + if (fl1h->phy_inst->u.oc2g.max_cell_size != cell_size) { + /* instruct L1 to apply max cell size */ + l1if_set_max_cell_size(fl1h, cell_size); + /* update current max cell size */ + fl1h->phy_inst->u.oc2g.max_cell_size = cell_size; + } + + /* Did we go through MphInit yet? If yes fire and forget */ + if (fl1h->hLayer1) { + power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0); + + if (fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk != trx->max_power_backoff_8psk) { + /* update current Tx power backoff for 8-PSK */ + fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk = trx->max_power_backoff_8psk; + /* instruct L1 to apply Tx power backoff for 8 PSK */ + l1if_set_txpower_backoff_8psk(fl1h, fl1h->phy_inst->u.oc2g.tx_pwr_red_8psk); + } + + if (fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red != trx->c0_idle_power_red) { + /* update current C0 idle slot Tx power reduction */ + fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red = trx->c0_idle_power_red; + /* instruct L1 to apply C0 idle slot power reduction */ + l1if_set_txpower_c0_idle_pwr_red(fl1h, fl1h->phy_inst->u.oc2g.tx_c0_idle_pwr_red); + } + } + + } + + /* FIXME: we actaully need to send a ACK or NACK for the OML message */ + return oml_fom_ack_nack(msg, 0); +} + +/* callback from OML */ +int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, + void *obj) +{ + int rc; + + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + rc = trx_init(obj); + break; + case NM_OC_CHANNEL: + rc = ts_opstart(obj); + break; + case NM_OC_BTS: + case NM_OC_SITE_MANAGER: + case NM_OC_BASEB_TRANSC: + case NM_OC_GPRS_NSE: + case NM_OC_GPRS_CELL: + case NM_OC_GPRS_NSVC: + oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, -1); + rc = oml_mo_opstart_ack(mo); + if (mo->obj_class == NM_OC_BTS) { + oml_mo_state_chg(&bts->mo, -1, NM_AVSTATE_OK); + oml_mo_state_chg(&bts->gprs.nse.mo, -1, NM_AVSTATE_OK); + oml_mo_state_chg(&bts->gprs.cell.mo, -1, NM_AVSTATE_OK); + oml_mo_state_chg(&bts->gprs.nsvc[0].mo, -1, NM_AVSTATE_OK); + } + break; + default: + rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP); + } + return rc; +} + +int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo, + void *obj, uint8_t adm_state) +{ + int rc = -EINVAL; + int granted = 0; + + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + + if (mo->procedure_pending) { + LOGP(DL1C, LOGL_ERROR, "Discarding adm change command: " + "pending procedure on RC %d\n", + ((struct gsm_bts_trx *)obj)->nr); + return 0; + } + mo->procedure_pending = 1; + switch (adm_state) { + case NM_STATE_LOCKED: + rc = trx_rf_lock(obj, 1, NULL); + break; + case NM_STATE_UNLOCKED: + rc = trx_rf_lock(obj, 0, NULL); + break; + default: + granted = 1; + break; + } + + if (!granted && rc == 0) + /* in progress, will send ack/nack after completion */ + return 0; + + mo->procedure_pending = 0; + + break; + default: + /* blindly accept all state changes */ + granted = 1; + break; + } + + if (granted) { + mo->nm_state.administrative = adm_state; + return oml_mo_statechg_ack(mo); + } else + return oml_mo_statechg_nack(mo, NM_NACK_REQ_NOT_GRANT); + +} + +int l1if_rsl_chan_act(struct gsm_lchan *lchan) +{ + //uint8_t mode = *TLVP_VAL(tp, RSL_IE_CHAN_MODE); + //uint8_t type = *TLVP_VAL(tp, RSL_IE_ACT_TYPE); + lchan_activate(lchan); + return 0; +} + +/** + * Modify the given lchan in the handover scenario. This is a lot like + * second channel activation but with some additional activation. + */ +int l1if_rsl_chan_mod(struct gsm_lchan *lchan) +{ + const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type]; + unsigned int i; + + if (lchan->ho.active == HANDOVER_NONE) + return -1; + + LOGP(DHO, LOGL_ERROR, "%s modifying channel for handover\n", + gsm_lchan_name(lchan)); + + /* Give up listening to RACH bursts */ + release_sapis_for_ho(lchan); + + /* Activate the normal SAPIs */ + for (i = 0; i < s4l->num_sapis; i++) { + int sapi = s4l->sapis[i].sapi; + int dir = s4l->sapis[i].dir; + enqueue_sapi_act_cmd(lchan, sapi, dir); + } + + return 0; +} + +int l1if_rsl_chan_rel(struct gsm_lchan *lchan) +{ + /* A duplicate RF Release Request, ignore it */ + if (lchan->state == LCHAN_S_REL_REQ) { + LOGP(DL1C, LOGL_ERROR, "%s already in release request state.\n", + gsm_lchan_name(lchan)); + return 0; + } + + lchan_deactivate(lchan); + return 0; +} + +int l1if_rsl_deact_sacch(struct gsm_lchan *lchan) +{ + /* Only de-activate the SACCH if the lchan is active */ + if (lchan->state != LCHAN_S_ACTIVE) + return 0; + return bts_model_lchan_deactivate_sacch(lchan); +} + +int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) +{ + struct oc2gl1_hdl *fl1 = trx_oc2gl1_hdl(trx); + + return l1if_activate_rf(fl1, 0); +} + +int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm) +{ + return l1if_set_txpower(trx_oc2gl1_hdl(trx), ((float) p_trxout_mdBm)/1000.0); +} + +static int ts_disconnect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphDisconnectCnf_t *cnf = &l1p->u.mphDisconnectCnf; + struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn]; + OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS); + + LOGP(DL1C, LOGL_DEBUG, "%s Rx mphDisconnectCnf\n", + gsm_lchan_name(ts->lchan)); + + cb_ts_disconnected(ts); + + msgb_free(l1_msg); + + return 0; +} + +int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) +{ + struct msgb *msg = l1p_msgb_alloc(); + struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(ts->trx); + GsmL1_MphDisconnectReq_t *cr; + + DEBUGP(DRSL, "%s TS disconnect\n", gsm_lchan_name(ts->lchan)); + cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDisconnectReq, fl1h, + l1p_handle_for_ts(ts)); + cr->u8Tn = ts->nr; + + return l1if_gsm_req_compl(fl1h, msg, ts_disconnect_cb, NULL); +} + +static int ts_connect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphConnectCnf_t *cnf = &l1p->u.mphConnectCnf; + struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn]; + OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS); + + DEBUGP(DL1C, "%s %s Rx mphConnectCnf flags=%s%s%s\n", + gsm_lchan_name(ts->lchan), + gsm_pchan_name(ts->pchan), + ts->flags & TS_F_PDCH_ACTIVE ? "ACTIVE " : "", + ts->flags & TS_F_PDCH_ACT_PENDING ? "ACT_PENDING " : "", + ts->flags & TS_F_PDCH_DEACT_PENDING ? "DEACT_PENDING " : ""); + + cb_ts_connected(ts); + + msgb_free(l1_msg); + + return 0; +} + +int bts_model_ts_connect(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config as_pchan) +{ + return ts_connect_as(ts, as_pchan, ts_connect_cb, NULL); +} diff --git a/src/osmo-bts-oc2g/oml_router.c b/src/osmo-bts-oc2g/oml_router.c new file mode 100644 index 000000000..198d5e301 --- /dev/null +++ b/src/osmo-bts-oc2g/oml_router.c @@ -0,0 +1,132 @@ +/* Beginnings of an OML router */ + +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * (C) 2014 by sysmocom s.f.m.c. GmbH + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include "oml_router.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +static int oml_router_read_cb(struct osmo_fd *fd, unsigned int what) +{ + struct msgb *msg; + int rc; + + msg = oml_msgb_alloc(); + if (!msg) { + LOGP(DL1C, LOGL_ERROR, "Failed to allocate oml msgb.\n"); + return -1; + } + + rc = recv(fd->fd, msg->tail, msg->data_len, 0); + if (rc <= 0) { + close(fd->fd); + osmo_fd_unregister(fd); + fd->fd = -1; + goto err; + } + + msg->l1h = msgb_put(msg, rc); + rc = msg_verify_ipa_structure(msg); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, + "OML Router: Invalid IPA message rc(%d)\n", rc); + goto err; + } + + rc = msg_verify_oml_structure(msg); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, + "OML Router: Invalid OML message rc(%d)\n", rc); + goto err; + } + + /* todo dispatch message */ + +err: + msgb_free(msg); + return -1; +} + +static int oml_router_accept_cb(struct osmo_fd *accept_fd, unsigned int what) +{ + int fd; + struct osmo_fd *read_fd = (struct osmo_fd *) accept_fd->data; + + /* Accept only one connection at a time. De-register it */ + if (read_fd->fd > -1) { + LOGP(DL1C, LOGL_NOTICE, + "New OML router connection. Closing old one.\n"); + close(read_fd->fd); + osmo_fd_unregister(read_fd); + read_fd->fd = -1; + } + + fd = accept(accept_fd->fd, NULL, NULL); + if (fd < 0) { + LOGP(DL1C, LOGL_ERROR, "Failed to accept. errno: %s.\n", + strerror(errno)); + return -1; + } + + read_fd->fd = fd; + if (osmo_fd_register(read_fd) != 0) { + LOGP(DL1C, LOGL_ERROR, "Registering the read fd failed.\n"); + close(fd); + read_fd->fd = -1; + return -1; + } + + return 0; +} + +int oml_router_init(struct gsm_bts *bts, const char *path, + struct osmo_fd *accept_fd, struct osmo_fd *read_fd) +{ + int rc; + + memset(accept_fd, 0, sizeof(*accept_fd)); + memset(read_fd, 0, sizeof(*read_fd)); + + accept_fd->cb = oml_router_accept_cb; + accept_fd->data = read_fd; + + read_fd->cb = oml_router_read_cb; + read_fd->data = bts; + read_fd->when = BSC_FD_READ; + read_fd->fd = -1; + + rc = osmo_sock_unix_init_ofd(accept_fd, SOCK_SEQPACKET, 0, + path, + OSMO_SOCK_F_BIND | OSMO_SOCK_F_NONBLOCK); + return rc; +} diff --git a/src/osmo-bts-oc2g/oml_router.h b/src/osmo-bts-oc2g/oml_router.h new file mode 100644 index 000000000..4b22e9c5a --- /dev/null +++ b/src/osmo-bts-oc2g/oml_router.h @@ -0,0 +1,13 @@ +#pragma once + +struct gsm_bts; +struct osmo_fd; + +/** + * The default path oc2gbts will listen for incoming + * registrations for OML routing and sending. + */ +#define OML_ROUTER_PATH "/var/run/oc2gbts_oml_router" + + +int oml_router_init(struct gsm_bts *bts, const char *path, struct osmo_fd *accept, struct osmo_fd *read); diff --git a/src/osmo-bts-oc2g/tch.c b/src/osmo-bts-oc2g/tch.c new file mode 100644 index 000000000..1bd93e4b6 --- /dev/null +++ b/src/osmo-bts-oc2g/tch.c @@ -0,0 +1,545 @@ +/* Traffic channel support for NuRAN Wireless OC-2G BTS L1 */ + +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * (C) 2011-2012 by Harald Welte + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "oc2gbts.h" +#include "l1_if.h" + +static struct msgb *l1_to_rtppayload_fr(uint8_t *l1_payload, uint8_t payload_len, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + + /* new L1 can deliver bits like we need them */ + cur = msgb_put(msg, GSM_FR_BYTES); + memcpy(cur, l1_payload, GSM_FR_BYTES); + + lchan_set_marker(osmo_fr_check_sid(l1_payload, payload_len), lchan); + + return msg; +} + +/*! \brief convert GSM-FR from RTP payload to L1 format + * \param[out] l1_payload payload part of L1 buffer + * \param[in] rtp_payload pointer to RTP payload data + * \param[in] payload_len length of \a rtp_payload + * \returns number of \a l1_payload bytes filled + */ +static int rtppayload_to_l1_fr(uint8_t *l1_payload, const uint8_t *rtp_payload, + unsigned int payload_len) +{ + /* new L1 can deliver bits like we need them */ + memcpy(l1_payload, rtp_payload, GSM_FR_BYTES); + return GSM_FR_BYTES; +} + +static struct msgb *l1_to_rtppayload_efr(uint8_t *l1_payload, + uint8_t payload_len, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + + /* new L1 can deliver bits like we need them */ + cur = msgb_put(msg, GSM_EFR_BYTES); + memcpy(cur, l1_payload, GSM_EFR_BYTES); + enum osmo_amr_type ft; + enum osmo_amr_quality bfi; + uint8_t cmr; + int8_t sti, cmi; + osmo_amr_rtp_dec(l1_payload, payload_len, &cmr, &cmi, &ft, &bfi, &sti); + lchan_set_marker(ft == AMR_GSM_EFR_SID, lchan); + + return msg; +} + +static int rtppayload_to_l1_efr(uint8_t *l1_payload, const uint8_t *rtp_payload, + unsigned int payload_len) +{ + memcpy(l1_payload, rtp_payload, payload_len); + + return payload_len; +} + +static struct msgb *l1_to_rtppayload_hr(uint8_t *l1_payload, uint8_t payload_len, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + + if (payload_len != GSM_HR_BYTES) { + LOGP(DL1P, LOGL_ERROR, "L1 HR frame length %u != expected %u\n", + payload_len, GSM_HR_BYTES); + return NULL; + } + + cur = msgb_put(msg, GSM_HR_BYTES); + memcpy(cur, l1_payload, GSM_HR_BYTES); + + lchan_set_marker(osmo_hr_check_sid(l1_payload, payload_len), lchan); + + return msg; +} + +/*! \brief convert GSM-FR from RTP payload to L1 format + * \param[out] l1_payload payload part of L1 buffer + * \param[in] rtp_payload pointer to RTP payload data + * \param[in] payload_len length of \a rtp_payload + * \returns number of \a l1_payload bytes filled + */ +static int rtppayload_to_l1_hr(uint8_t *l1_payload, const uint8_t *rtp_payload, + unsigned int payload_len) +{ + + if (payload_len != GSM_HR_BYTES) { + LOGP(DL1P, LOGL_ERROR, "RTP HR frame length %u != expected %u\n", + payload_len, GSM_HR_BYTES); + return 0; + } + + memcpy(l1_payload, rtp_payload, GSM_HR_BYTES); + + return GSM_HR_BYTES; +} + +static struct msgb *l1_to_rtppayload_amr(uint8_t *l1_payload, uint8_t payload_len, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t amr_if2_len = payload_len - 2; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + + cur = msgb_put(msg, amr_if2_len); + memcpy(cur, l1_payload+2, amr_if2_len); + + /* + * Audiocode's MGW doesn't like receiving CMRs that are not + * the same as the previous one. This means we need to patch + * the content here. + */ + if ((cur[0] & 0xF0) == 0xF0) + cur[0]= lchan->tch.last_cmr << 4; + else + lchan->tch.last_cmr = cur[0] >> 4; + + return msg; +} + +/*! \brief convert AMR from RTP payload to L1 format + * \param[out] l1_payload payload part of L1 buffer + * \param[in] rtp_payload pointer to RTP payload data + * \param[in] payload_len length of \a rtp_payload + * \returns number of \a l1_payload bytes filled + */ +static int rtppayload_to_l1_amr(uint8_t *l1_payload, const uint8_t *rtp_payload, + uint8_t payload_len, uint8_t ft) +{ + memcpy(l1_payload, rtp_payload, payload_len); + return payload_len; +} + +#define RTP_MSGB_ALLOC_SIZE 512 + +/*! \brief function for incoming RTP via TCH.req + * \param[in] rtp_pl buffer containing RTP payload + * \param[in] rtp_pl_len length of \a rtp_pl + * \param[in] use_cache Use cached payload instead of parsing RTP + * \param[in] marker RTP header Marker bit (indicates speech onset) + * \returns 0 if encoding result can be sent further to L1 without extra actions + * positive value if data is ready AND extra actions are required + * negative value otherwise (no data for L1 encoded) + * + * This function prepares a msgb with a L1 PH-DATA.req primitive and + * queues it into lchan->dl_tch_queue. + * + * Note that the actual L1 primitive header is not fully initialized + * yet, as things like the frame number, etc. are unknown at the time we + * pre-fill the primtive. + */ +int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len, + const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn, + bool use_cache, bool marker) +{ + uint8_t *payload_type; + uint8_t *l1_payload, ft; + int rc = 0; + bool is_sid = false; + + DEBUGP(DRTP, "%s RTP IN: %s\n", gsm_lchan_name(lchan), + osmo_hexdump(rtp_pl, rtp_pl_len)); + + payload_type = &data[0]; + l1_payload = &data[1]; + + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + if (lchan->type == GSM_LCHAN_TCH_F) { + *payload_type = GsmL1_TchPlType_Fr; + rc = rtppayload_to_l1_fr(l1_payload, + rtp_pl, rtp_pl_len); + if (rc && lchan->ts->trx->bts->dtxd) + is_sid = osmo_fr_check_sid(rtp_pl, rtp_pl_len); + } else{ + *payload_type = GsmL1_TchPlType_Hr; + rc = rtppayload_to_l1_hr(l1_payload, + rtp_pl, rtp_pl_len); + if (rc && lchan->ts->trx->bts->dtxd) + is_sid = osmo_hr_check_sid(rtp_pl, rtp_pl_len); + } + if (is_sid) + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, -1); + break; + case GSM48_CMODE_SPEECH_EFR: + *payload_type = GsmL1_TchPlType_Efr; + rc = rtppayload_to_l1_efr(l1_payload, rtp_pl, + rtp_pl_len); + /* FIXME: detect and save EFR SID */ + break; + case GSM48_CMODE_SPEECH_AMR: + if (use_cache) { + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload, lchan->tch.dtx.cache, + lchan->tch.dtx.len, ft); + *len = lchan->tch.dtx.len + 1; + return 0; + } + + rc = dtx_dl_amr_fsm_step(lchan, rtp_pl, rtp_pl_len, fn, + l1_payload, marker, len, &ft); + if (rc < 0) + return rc; + if (!dtx_dl_amr_enabled(lchan)) { + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + } + + /* DTX DL-specific logic below: */ + switch (lchan->tch.dtx.dl_amr_fsm->state) { + case ST_ONSET_V: + *payload_type = GsmL1_TchPlType_Amr_Onset; + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0); + *len = 3; + return 1; + case ST_VOICE: + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + case ST_SID_F1: + if (lchan->type == GSM_LCHAN_TCH_H) { /* AMR HR */ + *payload_type = GsmL1_TchPlType_Amr_SidFirstP1; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, + rtp_pl_len, ft); + return 0; + } + /* AMR FR */ + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + case ST_SID_F2: + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + case ST_F1_INH_V: + *payload_type = GsmL1_TchPlType_Amr_SidFirstInH; + *len = 3; + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0); + return 1; + case ST_U_INH_V: + *payload_type = GsmL1_TchPlType_Amr_SidUpdateInH; + *len = 3; + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0); + return 1; + case ST_SID_U: + case ST_U_NOINH: + return -EAGAIN; + case ST_FACCH: + return -EBADMSG; + default: + LOGP(DRTP, LOGL_ERROR, "Unhandled DTX DL AMR FSM state " + "%d\n", lchan->tch.dtx.dl_amr_fsm->state); + return -EINVAL; + } + break; + default: + /* we don't support CSD modes */ + rc = -1; + break; + } + + if (rc < 0) { + LOGP(DRTP, LOGL_ERROR, "%s unable to parse RTP payload\n", + gsm_lchan_name(lchan)); + return -EBADMSG; + } + + *len = rc + 1; + + DEBUGP(DRTP, "%s RTP->L1: %s\n", gsm_lchan_name(lchan), + osmo_hexdump(data, *len)); + return 0; +} + +static int is_recv_only(uint8_t speech_mode) +{ + return (speech_mode & 0xF0) == (1 << 4); +} + +/*! \brief receive a traffic L1 primitive for a given lchan */ +int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1p_msg); + GsmL1_PhDataInd_t *data_ind = &l1p->u.phDataInd; + uint8_t *payload, payload_type, payload_len, sid_first[9] = { 0 }; + struct msgb *rmsg = NULL; + struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)]; + + if (is_recv_only(lchan->abis_ip.speech_mode)) + return -EAGAIN; + + if (data_ind->msgUnitParam.u8Size < 1) { + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "chan_nr %d Rx Payload size 0\n", chan_nr); + /* Push empty payload to upper layers */ + rmsg = msgb_alloc_headroom(256, 128, "L1P-to-RTP"); + return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn, + data_ind->measParam.fBer * 10000, + data_ind->measParam.fLinkQuality * 10); + } + + payload_type = data_ind->msgUnitParam.u8Buffer[0]; + payload = data_ind->msgUnitParam.u8Buffer + 1; + payload_len = data_ind->msgUnitParam.u8Size - 1; + + /* clear RTP marker if the marker has previously sent */ + if (!lchan->tch.dtx.is_speech_resume) + lchan->rtp_tx_marker = false; + + switch (payload_type) { + case GsmL1_TchPlType_Fr: + case GsmL1_TchPlType_Efr: + if (lchan->type != GSM_LCHAN_TCH_F) + goto err_payload_match; + break; + case GsmL1_TchPlType_Hr: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + break; + case GsmL1_TchPlType_Amr: + if (lchan->type != GSM_LCHAN_TCH_H && + lchan->type != GSM_LCHAN_TCH_F) + goto err_payload_match; + break; + case GsmL1_TchPlType_Amr_Onset: + if (lchan->type != GSM_LCHAN_TCH_H && + lchan->type != GSM_LCHAN_TCH_F) + goto err_payload_match; + /* according to 3GPP TS 26.093 ONSET frames precede the first + speech frame of a speech burst - set the marker for next RTP + frame */ + lchan->tch.dtx.is_speech_resume = true; + lchan->rtp_tx_marker = true; + break; + case GsmL1_TchPlType_Amr_SidFirstP1: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P1 from L1 " + "(%d bytes)\n", payload_len); + break; + case GsmL1_TchPlType_Amr_SidFirstP2: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P2 from L1 " + "(%d bytes)\n", payload_len); + break; + case GsmL1_TchPlType_Amr_SidFirstInH: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + lchan->tch.dtx.is_speech_resume = true; + lchan->rtp_tx_marker = true; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_INH from L1 " + "(%d bytes)\n", payload_len); + break; + case GsmL1_TchPlType_Amr_SidUpdateInH: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + lchan->tch.dtx.is_speech_resume = true; + lchan->rtp_tx_marker = true; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_UPDATE_INH from L1 " + "(%d bytes)\n", payload_len); + break; + default: + LOGPFN(DL1P, LOGL_NOTICE, data_ind->u32Fn, "%s Rx Payload Type %s is unsupported\n", + gsm_lchan_name(lchan), + get_value_string(oc2gbts_tch_pl_names, payload_type)); + break; + } + + LOGP(DL1P, LOGL_DEBUG, "%s %s lchan->rtp_tx_marker = %s, len=%u\n", + gsm_lchan_name(lchan), + get_value_string(oc2gbts_tch_pl_names, payload_type), + lchan->rtp_tx_marker ? "true" : "false", + payload_len); + + switch (payload_type) { + case GsmL1_TchPlType_Fr: + rmsg = l1_to_rtppayload_fr(payload, payload_len, lchan); + break; + case GsmL1_TchPlType_Hr: + rmsg = l1_to_rtppayload_hr(payload, payload_len, lchan); + break; + case GsmL1_TchPlType_Efr: + rmsg = l1_to_rtppayload_efr(payload, payload_len, lchan); + break; + case GsmL1_TchPlType_Amr: + rmsg = l1_to_rtppayload_amr(payload, payload_len, lchan); + break; + case GsmL1_TchPlType_Amr_SidFirstP1: + memcpy(sid_first, payload, payload_len); + int len = osmo_amr_rtp_enc(sid_first, 0, AMR_SID, AMR_GOOD); + if (len < 0) + return 0; + rmsg = l1_to_rtppayload_amr(sid_first, len, lchan); + break; + } + + if (rmsg) + return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn, + data_ind->measParam.fBer * 10000, + data_ind->measParam.fLinkQuality * 10); + + return 0; + +err_payload_match: + LOGPFN(DL1P, LOGL_ERROR, data_ind->u32Fn, "%s Rx Payload Type %s incompatible with lchan\n", + gsm_lchan_name(lchan), get_value_string(oc2gbts_tch_pl_names, payload_type)); + return -EINVAL; +} + +struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan, uint32_t fn) +{ + struct msgb *msg; + GsmL1_Prim_t *l1p; + GsmL1_PhDataReq_t *data_req; + GsmL1_MsgUnitParam_t *msu_param; + uint8_t *payload_type; + uint8_t *l1_payload; + int rc; + + msg = l1p_msgb_alloc(); + if (!msg) + return NULL; + + l1p = msgb_l1prim(msg); + data_req = &l1p->u.phDataReq; + msu_param = &data_req->msgUnitParam; + payload_type = &msu_param->u8Buffer[0]; + l1_payload = &msu_param->u8Buffer[1]; + + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_AMR: + if (lchan->type == GSM_LCHAN_TCH_H && + dtx_dl_amr_enabled(lchan)) { + /* we have to explicitly handle sending SID FIRST P2 for + AMR HR in here */ + *payload_type = GsmL1_TchPlType_Amr_SidFirstP2; + rc = dtx_dl_amr_fsm_step(lchan, NULL, 0, fn, l1_payload, + false, &(msu_param->u8Size), + NULL); + if (rc == 0) + return msg; + } + *payload_type = GsmL1_TchPlType_Amr; + break; + case GSM48_CMODE_SPEECH_V1: + if (lchan->type == GSM_LCHAN_TCH_F) + *payload_type = GsmL1_TchPlType_Fr; + else + *payload_type = GsmL1_TchPlType_Hr; + break; + case GSM48_CMODE_SPEECH_EFR: + *payload_type = GsmL1_TchPlType_Efr; + break; + default: + msgb_free(msg); + return NULL; + } + + rc = repeat_last_sid(lchan, l1_payload, fn); + if (!rc) { + msgb_free(msg); + return NULL; + } + msu_param->u8Size = rc; + + return msg; +} diff --git a/src/osmo-bts-oc2g/utils.c b/src/osmo-bts-oc2g/utils.c new file mode 100644 index 000000000..289443902 --- /dev/null +++ b/src/osmo-bts-oc2g/utils.c @@ -0,0 +1,114 @@ +/* Helper utilities that are used in OMLs */ + +/* Copyright (C) 2015 by Yves Godin + * + * Based on sysmoBTS: + * (C) 2011-2013 by Harald Welte + * (C) 2013 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include "utils.h" + +#include +#include +#include + +#include "oc2gbts.h" +#include "l1_if.h" + +int band_oc2g2osmo(GsmL1_FreqBand_t band) +{ + switch (band) { + case GsmL1_FreqBand_850: + return GSM_BAND_850; + case GsmL1_FreqBand_900: + return GSM_BAND_900; + case GsmL1_FreqBand_1800: + return GSM_BAND_1800; + case GsmL1_FreqBand_1900: + return GSM_BAND_1900; + default: + return -1; + } +} + +static int band_osmo2oc2g(struct gsm_bts_trx *trx, enum gsm_band osmo_band) +{ + struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx); + + /* check if the TRX hardware actually supports the given band */ + if (!(fl1h->hw_info.band_support & osmo_band)) + return -1; + + /* if yes, convert from osmcoom style band definition to L1 band */ + switch (osmo_band) { + case GSM_BAND_850: + return GsmL1_FreqBand_850; + case GSM_BAND_900: + return GsmL1_FreqBand_900; + case GSM_BAND_1800: + return GsmL1_FreqBand_1800; + case GSM_BAND_1900: + return GsmL1_FreqBand_1900; + default: + return -1; + } +} + +/** + * Select the band that matches the ARFCN. In general the ARFCNs + * for GSM1800 and GSM1900 overlap and one needs to specify the + * rightband. When moving between GSM900/GSM1800 and GSM850/1900 + * modifying the BTS configuration is a bit annoying. The auto-band + * configuration allows to ease with this transition. + */ +int oc2gbts_select_oc2g_band(struct gsm_bts_trx *trx, uint16_t arfcn) +{ + enum gsm_band band; + struct gsm_bts *bts = trx->bts; + if (!bts->auto_band) + return band_osmo2oc2g(trx, bts->band); + + /* + * We need to check what will happen now. + */ + band = gsm_arfcn2band(arfcn); + + /* if we are already on the right band return */ + if (band == bts->band) + return band_osmo2oc2g(trx, bts->band); + + /* Check if it is GSM1800/GSM1900 */ + if (band == GSM_BAND_1800 && bts->band == GSM_BAND_1900) + return band_osmo2oc2g(trx, bts->band); + + /* + * Now to the actual autobauding. We just want DCS/DCS and + * PCS/PCS for PCS we check for 850/1800 though + */ + if ((band == GSM_BAND_900 && bts->band == GSM_BAND_1800) + || (band == GSM_BAND_1800 && bts->band == GSM_BAND_900) + || (band == GSM_BAND_850 && bts->band == GSM_BAND_1900)) + return band_osmo2oc2g(trx, band); + if (band == GSM_BAND_1800 && bts->band == GSM_BAND_850) + return band_osmo2oc2g(trx, GSM_BAND_1900); + + /* give up */ + return -1; +} diff --git a/src/osmo-bts-oc2g/utils.h b/src/osmo-bts-oc2g/utils.h new file mode 100644 index 000000000..f2f13e71f --- /dev/null +++ b/src/osmo-bts-oc2g/utils.h @@ -0,0 +1,13 @@ +#ifndef _UTILS_H +#define _UTILS_H + +#include +#include "oc2gbts.h" + +struct gsm_bts_trx; + +int band_oc2g2osmo(GsmL1_FreqBand_t band); + +int oc2gbts_select_oc2g_band(struct gsm_bts_trx *trx, uint16_t arfcn); + +#endif