Add OC-2G BTS sources

Change-Id: I327384fe5ac944dc3996a3f00932d6f1a10d5a35
This commit is contained in:
Omar Ramadan 2018-10-23 15:42:46 -07:00 committed by Harald Welte
parent ee9e8e9eb2
commit 9c75c387c0
57 changed files with 13546 additions and 1 deletions

View File

@ -279,6 +279,27 @@ if test "$enable_litecell15" = "yes"; then
CPPFLAGS=$oldCPPFLAGS
fi
AC_MSG_CHECKING([whether to enable NuRAN Wireless OC-2G hardware support])
AC_ARG_ENABLE(oc2g,
AC_HELP_STRING([--enable-oc2g],
[enable code for NuRAN Wireless OC-2G bts [default=no]]),
[enable_oc2g="yes"],[enable_oc2g="no"])
AC_ARG_WITH([oc2g], [AS_HELP_STRING([--with-oc2g=INCLUDE_DIR], [Location of the OC-2G API header files])],
[oc2g_incdir="$withval"],[oc2g_incdir="$incdir"])
AC_SUBST([OC2G_INCDIR], -I$oc2g_incdir)
AC_MSG_RESULT([$enable_oc2g])
AM_CONDITIONAL(ENABLE_OC2GBTS, test "x$enable_oc2g" = "xyes")
if test "$enable_oc2g" = "yes"; then
oldCPPFLAGS=$CPPFLAGS
CPPFLAGS="$CPPFLAGS $OC2G_INCDIR -I$srcdir/include"
AC_CHECK_HEADER([nrw/oc2g/oc2g.h],[],
[AC_MSG_ERROR([nrw/oc2g/oc2g.h can not be found in $oc2g_incdir])],
[#include <nrw/oc2g/oc2g.h>])
PKG_CHECK_MODULES(LIBSYSTEMD, libsystemd)
PKG_CHECK_MODULES(LIBGPS, libgps)
CPPFLAGS=$oldCPPFLAGS
fi
# https://www.freedesktop.org/software/systemd/man/daemon.html
AC_ARG_WITH([systemdsystemunitdir],
[AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files])],,
@ -307,6 +328,7 @@ AC_OUTPUT(
src/osmo-bts-omldummy/Makefile
src/osmo-bts-sysmo/Makefile
src/osmo-bts-litecell15/Makefile
src/osmo-bts-oc2g/Makefile
src/osmo-bts-trx/Makefile
src/osmo-bts-octphy/Makefile
include/Makefile

View File

@ -30,6 +30,10 @@ case "$bts_model" in
./contrib/jenkins_lc15.sh
;;
oc2g)
./contrib/jenkins_oc2g.sh
;;
trx)
./contrib/jenkins_bts_trx.sh
;;

25
contrib/jenkins_oc2g.sh Executable file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
};

View File

@ -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;
};

View File

@ -15,3 +15,8 @@ endif
if ENABLE_LC15BTS
SUBDIRS += osmo-bts-litecell15
endif
if ENABLE_OC2GBTS
SUBDIRS += osmo-bts-oc2g
endif

View File

@ -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" },

View File

@ -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)

View File

@ -0,0 +1,469 @@
/* NuRAN Wireless OC-2G BTS L1 calibration file routines*/
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
* Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org>
*
* Based on sysmoBTS:
* (C) 2012 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <limits.h>
#include <errno.h>
#include <osmocom/core/utils.h>
#include <osmo-bts/gsm_data.h>
#include <osmo-bts/logging.h>
#include <nrw/oc2g/oc2g.h>
#include <nrw/oc2g/gsml1const.h>
#include "l1_if.h"
#include "oc2gbts.h"
#include "utils.h"
#include "osmo-bts/oml.h"
/* Maximum calibration data chunk size */
#define MAX_CALIB_TBL_SIZE 65536
/* Calibration header version */
#define CALIB_HDR_V1 0x01
struct calib_file_desc {
const char *fname;
int rx;
int trx;
int rxpath;
};
static const struct calib_file_desc calib_files[] = {
{
.fname = "calib_rx0.conf",
.rx = 1,
.trx = 0,
.rxpath = 0,
}, {
.fname = "calib_tx0.conf",
.rx = 0,
.trx = 0,
},
};
struct calTbl_t
{
union
{
struct
{
uint8_t u8Version; /* Header version (1) */
uint8_t u8Parity; /* Parity byte (xor) */
uint8_t u8Type; /* Table type (0:TX Downlink, 1:RX-A Uplink, 2:RX-B Uplink) */
uint8_t u8Band; /* GSM Band (0:GSM-850, 1:EGSM-900, 2:DCS-1800, 3:PCS-1900) */
uint32_t u32Len; /* Table length in bytes including the header */
struct
{
uint32_t u32DescOfst; /* Description section offset */
uint32_t u32DateOfst; /* Date section offset */
uint32_t u32StationOfst; /* Calibration test station section offset */
uint32_t u32FpgaFwVerOfst; /* Calibration FPGA firmware version section offset */
uint32_t u32DspFwVerOfst; /* Calibration DSP firmware section offset */
uint32_t u32DataOfst; /* Calibration data section offset */
} toc;
} v1;
} hdr;
uint8_t u8RawData[MAX_CALIB_TBL_SIZE - 32];
};
static int calib_file_send(struct oc2gl1_hdl *fl1h,
const struct calib_file_desc *desc);
static int calib_verify(struct oc2gl1_hdl *fl1h,
const struct calib_file_desc *desc);
/* determine next calibration file index based on supported bands */
static int get_next_calib_file_idx(struct oc2gl1_hdl *fl1h, int last_idx)
{
struct phy_link *plink = fl1h->phy_inst->phy_link;
int i;
for (i = last_idx+1; i < ARRAY_SIZE(calib_files); i++) {
if (calib_files[i].trx == plink->num)
return i;
}
return -1;
}
static int calib_file_open(struct oc2gl1_hdl *fl1h,
const struct calib_file_desc *desc)
{
struct calib_send_state *st = &fl1h->st;
char *calib_path = fl1h->phy_inst->u.oc2g.calib_path;
char fname[PATH_MAX];
if (st->fp) {
LOGP(DL1C, LOGL_NOTICE, "L1 calibration file was left opened !!\n");
fclose(st->fp);
st->fp = NULL;
}
fname[0] = '\0';
snprintf(fname, sizeof(fname)-1, "%s/%s", calib_path, desc->fname);
fname[sizeof(fname)-1] = '\0';
st->fp = fopen(fname, "rb");
if (!st->fp) {
LOGP(DL1C, LOGL_NOTICE, "Failed to open '%s' for calibration data.\n", fname);
/* TODO (oramadan): Fix OML alarms
if( fl1h->phy_inst->trx ){
fl1h->phy_inst->trx->mo.obj_inst.trx_nr = fl1h->phy_inst->trx->nr;
alarm_sig_data.mo = &fl1h->phy_inst->trx->mo;
alarm_sig_data.add_text = (char*)&fname[0];
osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_FAIL_OPEN_CALIB_ALARM, &alarm_sig_data);
}
*/
return -1;
}
return 0;
}
static int calib_file_close(struct oc2gl1_hdl *fl1h)
{
struct calib_send_state *st = &fl1h->st;
if (st->fp) {
fclose(st->fp);
st->fp = NULL;
}
return 0;
}
/* iteratively download the calibration data into the L1 */
static int calib_send_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
void *data);
/* send a chunk of calibration tabledata for a single specified file */
static int calib_file_send_next_chunk(struct oc2gl1_hdl *fl1h)
{
struct calib_send_state *st = &fl1h->st;
Oc2g_Prim_t *prim;
struct msgb *msg;
size_t n;
msg = sysp_msgb_alloc();
prim = msgb_sysprim(msg);
prim->id = Oc2g_PrimId_SetCalibTblReq;
prim->u.setCalibTblReq.offset = (uint32_t)ftell(st->fp);
n = fread(prim->u.setCalibTblReq.u8Data, 1,
sizeof(prim->u.setCalibTblReq.u8Data), st->fp);
prim->u.setCalibTblReq.length = n;
if (n == 0) {
/* The table data has been completely sent and acknowledged */
LOGP(DL1C, LOGL_NOTICE, "L1 calibration table %s loaded\n",
calib_files[st->last_file_idx].fname);
calib_file_close(fl1h);
msgb_free(msg);
/* Send the next one if any */
st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx);
if (st->last_file_idx >= 0) {
return calib_file_send(fl1h,
&calib_files[st->last_file_idx]);
}
LOGP(DL1C, LOGL_INFO, "L1 calibration table loading complete!\n");
return 0;
}
return l1if_req_compl(fl1h, msg, calib_send_compl_cb, NULL);
}
/* send the calibration table for a single specified file */
static int calib_file_send(struct oc2gl1_hdl *fl1h,
const struct calib_file_desc *desc)
{
struct calib_send_state *st = &fl1h->st;
int rc;
rc = calib_file_open(fl1h, desc);
if (rc < 0) {
/* still, we'd like to continue trying to load
* calibration for all other bands */
st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx);
if (st->last_file_idx >= 0)
return calib_file_send(fl1h,
&calib_files[st->last_file_idx]);
LOGP(DL1C, LOGL_INFO, "L1 calibration table loading complete!\n");
return 0;
}
rc = calib_verify(fl1h, desc);
if (rc < 0) {
LOGP(DL1C, LOGL_NOTICE,"Verify L1 calibration table %s -> failed (%d)\n", desc->fname, rc);
/* TODO (oramadan): Fix OML alarms
if (fl1h->phy_inst->trx) {
fl1h->phy_inst->trx->mo.obj_inst.trx_nr = fl1h->phy_inst->trx->nr;
alarm_sig_data.mo = &fl1h->phy_inst->trx->mo;
alarm_sig_data.add_text = (char*)&desc->fname[0];
memcpy(alarm_sig_data.spare, &rc, sizeof(int));
osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_FAIL_VERIFY_CALIB_ALARM, &alarm_sig_data);
}
*/
st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx);
if (st->last_file_idx >= 0)
return calib_file_send(fl1h,
&calib_files[st->last_file_idx]);
return 0;
}
LOGP(DL1C, LOGL_INFO, "Verify L1 calibration table %s -> done\n", desc->fname);
return calib_file_send_next_chunk(fl1h);
}
/* completion callback after every SetCalibTbl is confirmed */
static int calib_send_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
void *data)
{
struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
struct calib_send_state *st = &fl1h->st;
Oc2g_Prim_t *prim = msgb_sysprim(l1_msg);
if (prim->u.setCalibTblCnf.status != GsmL1_Status_Success) {
LOGP(DL1C, LOGL_ERROR, "L1 rejected calibration table\n");
msgb_free(l1_msg);
calib_file_close(fl1h);
/* Skip this one and try the next one */
st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx);
if (st->last_file_idx >= 0) {
return calib_file_send(fl1h,
&calib_files[st->last_file_idx]);
}
LOGP(DL1C, LOGL_INFO, "L1 calibration table loading complete!\n");
return 0;
}
msgb_free(l1_msg);
/* Keep sending the calibration file data */
return calib_file_send_next_chunk(fl1h);
}
int calib_load(struct oc2gl1_hdl *fl1h)
{
int rc;
struct calib_send_state *st = &fl1h->st;
char *calib_path = fl1h->phy_inst->u.oc2g.calib_path;
if (!calib_path) {
LOGP(DL1C, LOGL_NOTICE, "Calibration file path not specified\n");
/* TODO (oramadan): Fix OML alarms
if( fl1h->phy_inst->trx ){
fl1h->phy_inst->trx->mo.obj_inst.trx_nr = fl1h->phy_inst->trx->nr;
alarm_sig_data.mo = &fl1h->phy_inst->trx->mo;
osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_NO_CALIB_PATH_ALARM, &alarm_sig_data);
}
*/
return -1;
}
rc = get_next_calib_file_idx(fl1h, -1);
if (rc < 0) {
return -1;
}
st->last_file_idx = rc;
return calib_file_send(fl1h, &calib_files[st->last_file_idx]);
}
static int calib_verify(struct oc2gl1_hdl *fl1h, const struct calib_file_desc *desc)
{
int rc, sz;
struct calib_send_state *st = &fl1h->st;
struct phy_link *plink = fl1h->phy_inst->phy_link;
char *rbuf;
struct calTbl_t *calTbl;
char calChkSum ;
/* calculate file size in bytes */
fseek(st->fp, 0L, SEEK_END);
sz = ftell(st->fp);
/* rewind read poiner */
fseek(st->fp, 0L, SEEK_SET);
/* read file */
rbuf = (char *) malloc( sizeof(char) * sz );
rc = fread(rbuf, 1, sizeof(char) * sz, st->fp);
if (rc != sz) {
LOGP(DL1C, LOGL_ERROR, "%s reading error\n", desc->fname);
free(rbuf);
/* close file */
rc = calib_file_close(fl1h);
if (rc < 0 ) {
LOGP(DL1C, LOGL_ERROR, "%s can not close\n", desc->fname);
return rc;
}
return -2;
}
calTbl = (struct calTbl_t*) rbuf;
/* calculate file checksum */
calChkSum = 0;
while (sz--) {
calChkSum ^= rbuf[sz];
}
/* validate Tx calibration parity */
if (calChkSum) {
LOGP(DL1C, LOGL_ERROR, "%s has invalid checksum %x.\n", desc->fname, calChkSum);
return -4;
}
/* validate Tx calibration header */
if (calTbl->hdr.v1.u8Version != CALIB_HDR_V1) {
LOGP(DL1C, LOGL_ERROR, "%s has invalid header version %u.\n", desc->fname, calTbl->hdr.v1.u8Version);
return -5;
}
/* validate calibration description */
if (calTbl->hdr.v1.toc.u32DescOfst == 0xFFFFFFFF) {
LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration description offset.\n", desc->fname);
return -6;
}
/* validate calibration date */
if (calTbl->hdr.v1.toc.u32DateOfst == 0xFFFFFFFF) {
LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration date offset.\n", desc->fname);
return -7;
}
LOGP(DL1C, LOGL_INFO, "L1 calibration table %s created on %s\n",
desc->fname,
calTbl->u8RawData + calTbl->hdr.v1.toc.u32DateOfst);
/* validate calibration station */
if (calTbl->hdr.v1.toc.u32StationOfst == 0xFFFFFFFF) {
LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration station ID offset.\n", desc->fname);
return -8;
}
/* validate FPGA FW version */
if (calTbl->hdr.v1.toc.u32FpgaFwVerOfst == 0xFFF) {
LOGP(DL1C, LOGL_ERROR, "%s has invalid FPGA FW version offset.\n", desc->fname);
return -9;
}
/* validate DSP FW version */
if (calTbl->hdr.v1.toc.u32DspFwVerOfst == 0xFFFFFFFF) {
LOGP(DL1C, LOGL_ERROR, "%s has invalid DSP FW version offset.\n", desc->fname);
return -10;
}
/* validate Tx calibration data offset */
if (calTbl->hdr.v1.toc.u32DataOfst == 0xFFFFFFFF) {
LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration data offset.\n", desc->fname);
return -11;
}
if (!desc->rx) {
/* parse min/max Tx power */
fl1h->phy_inst->u.oc2g.minTxPower = calTbl->u8RawData[calTbl->hdr.v1.toc.u32DataOfst + (5 << 2)];
fl1h->phy_inst->u.oc2g.maxTxPower = calTbl->u8RawData[calTbl->hdr.v1.toc.u32DataOfst + (6 << 2)];
/* override nominal Tx power of given TRX if needed */
if (fl1h->phy_inst->trx->nominal_power > fl1h->phy_inst->u.oc2g.maxTxPower) {
LOGP(DL1C, LOGL_INFO, "Set TRX %u nominal Tx power to %d dBm (%d)\n",
plink->num,
fl1h->phy_inst->u.oc2g.maxTxPower,
fl1h->phy_inst->trx->nominal_power);
fl1h->phy_inst->trx->nominal_power = fl1h->phy_inst->u.oc2g.maxTxPower;
}
if (fl1h->phy_inst->trx->nominal_power < fl1h->phy_inst->u.oc2g.minTxPower) {
LOGP(DL1C, LOGL_INFO, "Set TRX %u nominal Tx power to %d dBm (%d)\n",
plink->num,
fl1h->phy_inst->u.oc2g.minTxPower,
fl1h->phy_inst->trx->nominal_power);
fl1h->phy_inst->trx->nominal_power = fl1h->phy_inst->u.lc15.minTxPower;
}
if (fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm > to_mdB(fl1h->phy_inst->u.oc2g.maxTxPower) ) {
LOGP(DL1C, LOGL_INFO, "Set TRX %u Tx power parameter to %d dBm (%d)\n",
plink->num,
to_mdB(fl1h->phy_inst->u.oc2g.maxTxPower),
fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm);
fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm = to_mdB(fl1h->phy_inst->u.oc2g.maxTxPower);
}
if (fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm < to_mdB(fl1h->phy_inst->u.oc2g.minTxPower) ) {
LOGP(DL1C, LOGL_INFO, "Set TRX %u Tx power parameter to %d dBm (%d)\n",
plink->num,
to_mdB(fl1h->phy_inst->u.oc2g.minTxPower),
fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm);
fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm = to_mdB(fl1h->phy_inst->u.oc2g.minTxPower);
}
LOGP(DL1C, LOGL_DEBUG, "%s: minTxPower=%d, maxTxPower=%d\n",
desc->fname,
fl1h->phy_inst->u.oc2g.minTxPower,
fl1h->phy_inst->u.oc2g.maxTxPower );
}
/* rewind read pointer for subsequence tasks */
fseek(st->fp, 0L, SEEK_SET);
free(rbuf);
return 0;
}

View File

View File

@ -0,0 +1,88 @@
/* Misc HW routines for NuRAN Wireless OC-2G BTS */
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* Based on sysmoBTS:
* (C) 2012 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdint.h>
#include <unistd.h>
#include <limits.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <osmocom/core/utils.h>
#include "hw_misc.h"
int oc2gbts_led_set(enum oc2gbts_led_color c)
{
int fd, rc;
uint8_t cmd[2];
switch (c) {
case LED_OFF:
cmd[0] = 0;
cmd[1] = 0;
break;
case LED_RED:
cmd[0] = 1;
cmd[1] = 0;
break;
case LED_GREEN:
cmd[0] = 0;
cmd[1] = 1;
break;
case LED_ORANGE:
cmd[0] = 1;
cmd[1] = 1;
break;
default:
return -EINVAL;
}
fd = open("/var/oc2g/leds/led0/brightness", O_WRONLY);
if (fd < 0)
return -ENODEV;
rc = write(fd, cmd[0] ? "1" : "0", 2);
if (rc != 2) {
close(fd);
return -1;
}
close(fd);
fd = open("/var/oc2g/leds/led1/brightness", O_WRONLY);
if (fd < 0)
return -ENODEV;
rc = write(fd, cmd[1] ? "1" : "0", 2);
if (rc != 2) {
close(fd);
return -1;
}
close(fd);
return 0;
}

View File

@ -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

1755
src/osmo-bts-oc2g/l1_if.c Normal file

File diff suppressed because it is too large Load Diff

145
src/osmo-bts-oc2g/l1_if.h Normal file
View File

@ -0,0 +1,145 @@
#ifndef _L1_IF_H
#define _L1_IF_H
#include <osmocom/core/select.h>
#include <osmocom/core/write_queue.h>
#include <osmocom/core/gsmtap_util.h>
#include <osmocom/core/timer.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmo-bts/phy_link.h>
#include <nrw/oc2g/gsml1prim.h>
#include <nrw/oc2g/gsml1types.h>
#include <stdbool.h>
enum {
MQ_SYS_READ,
MQ_L1_READ,
MQ_TCH_READ,
MQ_PDTCH_READ,
_NUM_MQ_READ
};
enum {
MQ_SYS_WRITE,
MQ_L1_WRITE,
MQ_TCH_WRITE,
MQ_PDTCH_WRITE,
_NUM_MQ_WRITE
};
struct calib_send_state {
FILE *fp;
const char *path;
int last_file_idx;
};
struct oc2gl1_hdl {
struct gsm_time gsm_time;
HANDLE hLayer1; /* handle to the L1 instance in the DSP */
uint32_t dsp_trace_f; /* currently operational DSP trace flags */
struct llist_head wlc_list;
struct llist_head alarm_list; /* list of sent alarms */
struct phy_instance *phy_inst;
struct osmo_timer_list alive_timer;
unsigned int alive_prim_cnt;
struct osmo_fd read_ofd[_NUM_MQ_READ]; /* osmo file descriptors */
struct osmo_wqueue write_q[_NUM_MQ_WRITE];
struct {
/* from DSP/FPGA after L1 Init */
uint8_t dsp_version[3];
uint8_t fpga_version[3];
uint32_t band_support;
uint8_t ver_major;
uint8_t ver_minor;
uint32_t options;
} hw_info;
struct calib_send_state st;
uint8_t last_rf_mute[8];
struct {
struct osmo_timer_list dsp_alive_timer;
unsigned int dsp_alive_cnt;
uint8_t dsp_alive_period;
} hw_alive;
};
#define msgb_l1prim(msg) ((GsmL1_Prim_t *)(msg)->l1h)
#define msgb_sysprim(msg) ((Oc2g_Prim_t *)(msg)->l1h)
typedef int l1if_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, void *data);
/* send a request primitive to the L1 and schedule completion call-back */
int l1if_req_compl(struct oc2gl1_hdl *fl1h, struct msgb *msg,
l1if_compl_cb *cb, void *cb_data);
int l1if_gsm_req_compl(struct oc2gl1_hdl *fl1h, struct msgb *msg,
l1if_compl_cb *cb, void *cb_data);
struct oc2gl1_hdl *l1if_open(struct phy_instance *pinst);
int l1if_close(struct oc2gl1_hdl *hdl);
int l1if_reset(struct oc2gl1_hdl *hdl);
int l1if_activate_rf(struct oc2gl1_hdl *hdl, int on);
int l1if_set_trace_flags(struct oc2gl1_hdl *hdl, uint32_t flags);
int l1if_set_txpower(struct oc2gl1_hdl *fl1h, float tx_power);
int l1if_set_txpower_backoff_8psk(struct oc2gl1_hdl *fl1h, uint8_t backoff);
int l1if_set_txpower_c0_idle_pwr_red(struct oc2gl1_hdl *fl1h, uint8_t red);
int l1if_set_max_cell_size(struct oc2gl1_hdl *fl1h, uint8_t cell_size);
int l1if_mute_rf(struct oc2gl1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb);
struct msgb *l1p_msgb_alloc(void);
struct msgb *sysp_msgb_alloc(void);
uint32_t l1if_lchan_to_hLayer(struct gsm_lchan *lchan);
struct gsm_lchan *l1if_hLayer_to_lchan(struct gsm_bts_trx *trx, uint32_t hLayer);
/* tch.c */
int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len,
const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn,
bool use_cache, bool marker);
int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg);
int l1if_tch_fill(struct gsm_lchan *lchan, uint8_t *l1_buffer);
struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan, uint32_t fn);
/* ciphering */
int l1if_set_ciphering(struct oc2gl1_hdl *fl1h,
struct gsm_lchan *lchan,
int dir_downlink);
/* channel control */
int l1if_rsl_chan_act(struct gsm_lchan *lchan);
int l1if_rsl_chan_rel(struct gsm_lchan *lchan);
int l1if_rsl_chan_mod(struct gsm_lchan *lchan);
int l1if_rsl_deact_sacch(struct gsm_lchan *lchan);
int l1if_rsl_mode_modify(struct gsm_lchan *lchan);
/* calibration loading */
int calib_load(struct oc2gl1_hdl *fl1h);
/* public helpers for test */
int bts_check_for_ciph_cmd(struct oc2gl1_hdl *fl1h,
struct msgb *msg, struct gsm_lchan *lchan);
int l1if_ms_pwr_ctrl(struct gsm_lchan *lchan, const int uplink_target,
const uint8_t ms_power, const float rxLevel);
static inline struct oc2gl1_hdl *trx_oc2gl1_hdl(struct gsm_bts_trx *trx)
{
struct phy_instance *pinst = trx_phy_instance(trx);
OSMO_ASSERT(pinst);
return pinst->u.oc2g.hdl;
}
static inline struct gsm_bts_trx *oc2gl1_hdl_trx(struct oc2gl1_hdl *fl1h)
{
OSMO_ASSERT(fl1h->phy_inst);
return fl1h->phy_inst->trx;
}
#endif /* _L1_IF_H */

View File

@ -0,0 +1,14 @@
#ifndef _L1_TRANSP_H
#define _L1_TRANSP_H
#include <osmocom/core/msgb.h>
/* functions a transport calls on arrival of primitive from BTS */
int l1if_handle_l1prim(int wq, struct oc2gl1_hdl *fl1h, struct msgb *msg);
int l1if_handle_sysprim(struct oc2gl1_hdl *fl1h, struct msgb *msg);
/* functions exported by a transport */
int l1if_transport_open(int q, struct oc2gl1_hdl *fl1h);
int l1if_transport_close(int q, struct oc2gl1_hdl *fl1h);
#endif /* _L1_TRANSP_H */

View File

@ -0,0 +1,326 @@
/* Interface handler for Nuran Wireless OC-2G L1 (real hardware) */
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* Based on sysmoBTS:
* (C) 2011 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <assert.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/select.h>
#include <osmocom/core/write_queue.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmo-bts/logging.h>
#include <osmo-bts/gsm_data.h>
#include <nrw/oc2g/oc2g.h>
#include <nrw/oc2g/gsml1prim.h>
#include <nrw/oc2g/gsml1const.h>
#include <nrw/oc2g/gsml1types.h>
#include "oc2gbts.h"
#include "l1_if.h"
#include "l1_transp.h"
#define DEV_SYS_DSP2ARM_NAME "/dev/msgq/oc2g_dsp2arm_trx"
#define DEV_SYS_ARM2DSP_NAME "/dev/msgq/oc2g_arm2dsp_trx"
#define DEV_L1_DSP2ARM_NAME "/dev/msgq/gsml1_sig_dsp2arm_trx"
#define DEV_L1_ARM2DSP_NAME "/dev/msgq/gsml1_sig_arm2dsp_trx"
#define DEV_TCH_DSP2ARM_NAME "/dev/msgq/gsml1_tch_dsp2arm_trx"
#define DEV_TCH_ARM2DSP_NAME "/dev/msgq/gsml1_tch_arm2dsp_trx"
#define DEV_PDTCH_DSP2ARM_NAME "/dev/msgq/gsml1_pdtch_dsp2arm_trx"
#define DEV_PDTCH_ARM2DSP_NAME "/dev/msgq/gsml1_pdtch_arm2dsp_trx"
static const char *rd_devnames[] = {
[MQ_SYS_READ] = DEV_SYS_DSP2ARM_NAME,
[MQ_L1_READ] = DEV_L1_DSP2ARM_NAME,
[MQ_TCH_READ] = DEV_TCH_DSP2ARM_NAME,
[MQ_PDTCH_READ] = DEV_PDTCH_DSP2ARM_NAME,
};
static const char *wr_devnames[] = {
[MQ_SYS_WRITE] = DEV_SYS_ARM2DSP_NAME,
[MQ_L1_WRITE] = DEV_L1_ARM2DSP_NAME,
[MQ_TCH_WRITE] = DEV_TCH_ARM2DSP_NAME,
[MQ_PDTCH_WRITE]= DEV_PDTCH_ARM2DSP_NAME,
};
/*
* Make sure that all structs we read fit into the OC2GBTS_PRIM_SIZE
*/
osmo_static_assert(sizeof(GsmL1_Prim_t) + 128 <= OC2GBTS_PRIM_SIZE, l1_prim)
osmo_static_assert(sizeof(Oc2g_Prim_t) + 128 <= OC2GBTS_PRIM_SIZE, super_prim)
static int wqueue_vector_cb(struct osmo_fd *fd, unsigned int what)
{
struct osmo_wqueue *queue;
queue = container_of(fd, struct osmo_wqueue, bfd);
if (what & BSC_FD_READ)
queue->read_cb(fd);
if (what & BSC_FD_EXCEPT)
queue->except_cb(fd);
if (what & BSC_FD_WRITE) {
struct iovec iov[5];
struct msgb *msg, *tmp;
int written, count = 0;
fd->when &= ~BSC_FD_WRITE;
llist_for_each_entry(msg, &queue->msg_queue, list) {
/* more writes than we have */
if (count >= ARRAY_SIZE(iov))
break;
iov[count].iov_base = msg->l1h;
iov[count].iov_len = msgb_l1len(msg);
count += 1;
}
/* TODO: check if all lengths are the same. */
/* Nothing scheduled? This should not happen. */
if (count == 0) {
if (!llist_empty(&queue->msg_queue))
fd->when |= BSC_FD_WRITE;
return 0;
}
written = writev(fd->fd, iov, count);
if (written < 0) {
/* nothing written?! */
if (!llist_empty(&queue->msg_queue))
fd->when |= BSC_FD_WRITE;
return 0;
}
/* now delete the written entries */
written = written / iov[0].iov_len;
count = 0;
llist_for_each_entry_safe(msg, tmp, &queue->msg_queue, list) {
queue->current_length -= 1;
llist_del(&msg->list);
msgb_free(msg);
count += 1;
if (count >= written)
break;
}
if (!llist_empty(&queue->msg_queue))
fd->when |= BSC_FD_WRITE;
}
return 0;
}
static int prim_size_for_queue(int queue)
{
switch (queue) {
case MQ_SYS_WRITE:
return sizeof(Oc2g_Prim_t);
case MQ_L1_WRITE:
case MQ_TCH_WRITE:
case MQ_PDTCH_WRITE:
return sizeof(GsmL1_Prim_t);
default:
/* The compiler can't know that priv_nr is an enum. Assist. */
LOGP(DL1C, LOGL_FATAL, "writing on a wrong queue: %d\n",
queue);
assert(false);
break;
}
}
/* callback when there's something to read from the l1 msg_queue */
static int read_dispatch_one(struct oc2gl1_hdl *fl1h, struct msgb *msg, int queue)
{
switch (queue) {
case MQ_SYS_WRITE:
return l1if_handle_sysprim(fl1h, msg);
case MQ_L1_WRITE:
case MQ_TCH_WRITE:
case MQ_PDTCH_WRITE:
return l1if_handle_l1prim(queue, fl1h, msg);
default:
/* The compiler can't know that priv_nr is an enum. Assist. */
LOGP(DL1C, LOGL_FATAL, "writing on a wrong queue: %d\n",
queue);
assert(false);
break;
}
};
static int l1if_fd_cb(struct osmo_fd *ofd, unsigned int what)
{
int i, rc;
const uint32_t prim_size = prim_size_for_queue(ofd->priv_nr);
uint32_t count;
struct iovec iov[3];
struct msgb *msg[ARRAY_SIZE(iov)];
for (i = 0; i < ARRAY_SIZE(iov); ++i) {
msg[i] = msgb_alloc_headroom(prim_size + 128, 128, "1l_fd");
msg[i]->l1h = msg[i]->data;
iov[i].iov_base = msg[i]->l1h;
iov[i].iov_len = msgb_tailroom(msg[i]);
}
rc = readv(ofd->fd, iov, ARRAY_SIZE(iov));
if (rc < 0) {
LOGP(DL1C, LOGL_ERROR, "failed to read from fd: %s\n", strerror(errno));
/* N. B: we do not abort to let the cycle below cleanup allocated memory properly,
the return value is ignored by the caller anyway.
TODO: use libexplain's explain_readv() to provide detailed error description */
count = 0;
} else
count = rc / prim_size;
for (i = 0; i < count; ++i) {
msgb_put(msg[i], prim_size);
read_dispatch_one(ofd->data, msg[i], ofd->priv_nr);
}
for (i = count; i < ARRAY_SIZE(iov); ++i)
msgb_free(msg[i]);
return 1;
}
/* callback when we can write to one of the l1 msg_queue devices */
static int l1fd_write_cb(struct osmo_fd *ofd, struct msgb *msg)
{
int rc;
rc = write(ofd->fd, msg->l1h, msgb_l1len(msg));
if (rc < 0) {
LOGP(DL1C, LOGL_ERROR, "error writing to L1 msg_queue: %s\n",
strerror(errno));
return rc;
} else if (rc < msg->len) {
LOGP(DL1C, LOGL_ERROR, "short write to L1 msg_queue: "
"%u < %u\n", rc, msg->len);
return -EIO;
}
return 0;
}
int l1if_transport_open(int q, struct oc2gl1_hdl *hdl)
{
struct phy_link *plink = hdl->phy_inst->phy_link;
int rc;
char buf[PATH_MAX];
/* Step 1: Open all msg_queue file descriptors */
struct osmo_fd *read_ofd = &hdl->read_ofd[q];
struct osmo_wqueue *wq = &hdl->write_q[q];
struct osmo_fd *write_ofd = &hdl->write_q[q].bfd;
snprintf(buf, sizeof(buf)-1, "%s%d", rd_devnames[q], plink->num);
buf[sizeof(buf)-1] = '\0';
rc = open(buf, O_RDONLY);
if (rc < 0) {
LOGP(DL1C, LOGL_FATAL, "unable to open msg_queue %s: %s\n",
buf, strerror(errno));
return rc;
}
read_ofd->fd = rc;
read_ofd->priv_nr = q;
read_ofd->data = hdl;
read_ofd->cb = l1if_fd_cb;
read_ofd->when = BSC_FD_READ;
rc = osmo_fd_register(read_ofd);
if (rc < 0) {
close(read_ofd->fd);
read_ofd->fd = -1;
return rc;
}
snprintf(buf, sizeof(buf)-1, "%s%d", wr_devnames[q], plink->num);
buf[sizeof(buf)-1] = '\0';
rc = open(buf, O_WRONLY);
if (rc < 0) {
LOGP(DL1C, LOGL_FATAL, "unable to open msg_queue %s: %s\n",
buf, strerror(errno));
goto out_read;
}
osmo_wqueue_init(wq, 10);
wq->write_cb = l1fd_write_cb;
write_ofd->cb = wqueue_vector_cb;
write_ofd->fd = rc;
write_ofd->priv_nr = q;
write_ofd->data = hdl;
write_ofd->when = BSC_FD_WRITE;
rc = osmo_fd_register(write_ofd);
if (rc < 0) {
close(write_ofd->fd);
write_ofd->fd = -1;
goto out_read;
}
return 0;
out_read:
close(hdl->read_ofd[q].fd);
osmo_fd_unregister(&hdl->read_ofd[q]);
return rc;
}
int l1if_transport_close(int q, struct oc2gl1_hdl *hdl)
{
struct osmo_fd *read_ofd = &hdl->read_ofd[q];
struct osmo_fd *write_ofd = &hdl->write_q[q].bfd;
osmo_fd_unregister(read_ofd);
close(read_ofd->fd);
read_ofd->fd = -1;
osmo_fd_unregister(write_ofd);
close(write_ofd->fd);
write_ofd->fd = -1;
return 0;
}

240
src/osmo-bts-oc2g/main.c Normal file
View File

@ -0,0 +1,240 @@
/* Main program for NuRAN Wireless OC-2G BTS */
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
* Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org>
*
* Based on sysmoBTS:
* (C) 2011-2013 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <getopt.h>
#include <limits.h>
#include <sys/signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sched.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/application.h>
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/logging.h>
#include <osmocom/vty/ports.h>
#include <osmo-bts/gsm_data.h>
#include <osmo-bts/logging.h>
#include <osmo-bts/bts.h>
#include <osmo-bts/vty.h>
#include <osmo-bts/bts_model.h>
#include <osmo-bts/pcu_if.h>
#include <osmo-bts/l1sap.h>
static int write_status_file(char *status_file, char *status_str)
{
FILE *outf;
char tmp[PATH_MAX+1];
snprintf(tmp, sizeof(tmp)-1, "/var/run/osmo-bts/%s", status_file);
tmp[PATH_MAX-1] = '\0';
outf = fopen(tmp, "w");
if (!outf)
return -1;
fprintf(outf, "%s\n", status_str);
fclose(outf);
return 0;
}
/*NTQD: Change how rx_nr is handle in multi-trx*/
#define OC2GBTS_RF_LOCK_PATH "/var/lock/bts_rf_lock"
#include "utils.h"
#include "l1_if.h"
#include "hw_misc.h"
#include "oml_router.h"
#include "misc/oc2gbts_bid.h"
unsigned int dsp_trace = 0x00000000;
int bts_model_init(struct gsm_bts *bts)
{
struct gsm_bts_trx *trx;
struct stat st;
static struct osmo_fd accept_fd, read_fd;
int rc;
bts->variant = BTS_OSMO_OC2G;
bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3);
/* specific default values for OC2G platform */
/* TODO(oramadan) MERGE
bts->oc2g.led_ctrl_mode = OC2G_BTS_LED_CTRL_MODE_DEFAULT;
/* RTP drift threshold default * /
bts->oc2g.rtp_drift_thres_ms = OC2G_BTS_RTP_DRIFT_THRES_DEFAULT;
*/
rc = oml_router_init(bts, OML_ROUTER_PATH, &accept_fd, &read_fd);
if (rc < 0) {
fprintf(stderr, "Error creating the OML router: %s rc=%d\n",
OML_ROUTER_PATH, rc);
exit(1);
}
llist_for_each_entry(trx, &bts->trx_list, list) {
trx->nominal_power = 40;
trx->power_params.trx_p_max_out_mdBm = to_mdB(bts->c0->nominal_power);
}
if (stat(OC2GBTS_RF_LOCK_PATH, &st) == 0) {
LOGP(DL1C, LOGL_NOTICE, "Not starting BTS due to RF_LOCK file present\n");
exit(23);
}
gsm_bts_set_feature(bts, BTS_FEAT_GPRS);
gsm_bts_set_feature(bts, BTS_FEAT_EGPRS);
gsm_bts_set_feature(bts, BTS_FEAT_OML_ALERTS);
gsm_bts_set_feature(bts, BTS_FEAT_AGCH_PCH_PROP);
gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_V1);
gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_V1);
gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_EFR);
gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_F_AMR);
gsm_bts_set_feature(bts, BTS_FEAT_SPEECH_H_AMR);
bts_model_vty_init(bts);
return 0;
}
void bts_model_phy_link_set_defaults(struct phy_link *plink)
{
}
void bts_model_phy_instance_set_defaults(struct phy_instance *pinst)
{
}
int bts_model_oml_estab(struct gsm_bts *bts)
{
/* update status file */
write_status_file("state", "");
return 0;
}
void bts_update_status(enum bts_global_status which, int on)
{
static uint64_t states = 0;
uint64_t old_states = states;
int led_rf_active_on;
if (on)
states |= (1ULL << which);
else
states &= ~(1ULL << which);
led_rf_active_on =
(states & (1ULL << BTS_STATUS_RF_ACTIVE)) &&
!(states & (1ULL << BTS_STATUS_RF_MUTE));
LOGP(DL1C, LOGL_INFO,
"Set global status #%d to %d (%04llx -> %04llx), LEDs: ACT %d\n",
which, on,
(long long)old_states, (long long)states,
led_rf_active_on);
oc2gbts_led_set(led_rf_active_on ? LED_GREEN : LED_OFF);
}
void bts_model_print_help()
{
printf( " -w --hw-version Print the targeted HW Version\n"
" -M --pcu-direct Force PCU to access message queue for PDCH dchannel directly\n"
" -p --dsp-trace Set DSP trace flags\n"
);
}
static void print_hwversion()
{
printf(get_hwversion_desc());
printf("\n");
}
int bts_model_handle_options(int argc, char **argv)
{
int num_errors = 0;
while (1) {
int option_idx = 0, c;
static const struct option long_options[] = {
{ "dsp-trace", 1, 0, 'p' },
{ "hw-version", 0, 0, 'w' },
{ "pcu-direct", 0, 0, 'M' },
{ 0, 0, 0, 0 }
};
c = getopt_long(argc, argv, "p:wM",
long_options, &option_idx);
if (c == -1)
break;
switch (c) {
case 'p':
dsp_trace = strtoul(optarg, NULL, 16);
break;
case 'M':
pcu_direct = 1;
break;
case 'w':
print_hwversion();
exit(0);
break;
default:
num_errors++;
break;
}
}
return num_errors;
}
void bts_model_abis_close(struct gsm_bts *bts)
{
/* write to status file */
write_status_file("state", "ABIS DOWN");
/* for now, we simply terminate the program and re-spawn */
bts_shutdown(bts, "Abis close");
}
int main(int argc, char **argv)
{
/* create status file with initial state */
write_status_file("state", "ABIS DOWN");
return bts_main(argc, argv);
}

View File

@ -0,0 +1,175 @@
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include "oc2gbts_bid.h"
#define BOARD_REV_MAJ_SYSFS "/var/oc2g/platform/rev/major"
#define BOARD_REV_MIN_SYSFS "/var/oc2g/platform/rev/minor"
#define BOARD_OPT_SYSFS "/var/oc2g/platform/option"
static const int option_type_mask[_NUM_OPTION_TYPES] = {
[OC2GBTS_OPTION_OCXO] = 0x07,
[OC2GBTS_OPTION_FPGA] = 0x03,
[OC2GBTS_OPTION_PA] = 0x01,
[OC2GBTS_OPTION_BAND] = 0x03,
[OC2GBTS_OPTION_TX_ATT] = 0x01,
[OC2GBTS_OPTION_TX_FIL] = 0x01,
[OC2GBTS_OPTION_RX_ATT] = 0x01,
[OC2GBTS_OPTION_RMS_FWD] = 0x01,
[OC2GBTS_OPTION_RMS_REFL] = 0x01,
[OC2GBTS_OPTION_DDR_32B] = 0x01,
[OC2GBTS_OPTION_DDR_ECC] = 0x01,
[OC2GBTS_OPTION_PA_TEMP] = 0x01,
};
static const int option_type_shift[_NUM_OPTION_TYPES] = {
[OC2GBTS_OPTION_OCXO] = 0,
[OC2GBTS_OPTION_FPGA] = 3,
[OC2GBTS_OPTION_PA] = 5,
[OC2GBTS_OPTION_BAND] = 6,
[OC2GBTS_OPTION_TX_ATT] = 8,
[OC2GBTS_OPTION_TX_FIL] = 9,
[OC2GBTS_OPTION_RX_ATT] = 10,
[OC2GBTS_OPTION_RMS_FWD] = 11,
[OC2GBTS_OPTION_RMS_REFL] = 12,
[OC2GBTS_OPTION_DDR_32B] = 13,
[OC2GBTS_OPTION_DDR_ECC] = 14,
[OC2GBTS_OPTION_PA_TEMP] = 15,
};
static char board_rev_maj = -1;
static char board_rev_min = -1;
static int board_option = -1;
void oc2gbts_rev_get(char *rev_maj, char *rev_min)
{
FILE *fp;
char rev;
*rev_maj = 0;
*rev_min = 0;
if (board_rev_maj != -1 && board_rev_min != -1) {
*rev_maj = board_rev_maj;
*rev_min = board_rev_min;
}
fp = fopen(BOARD_REV_MAJ_SYSFS, "r");
if (fp == NULL) return;
if (fscanf(fp, "%c", &rev) != 1) {
fclose(fp);
return;
}
fclose(fp);
*rev_maj = board_rev_maj = rev;
fp = fopen(BOARD_REV_MIN_SYSFS, "r");
if (fp == NULL) return;
if (fscanf(fp, "%c", &rev) != 1) {
fclose(fp);
return;
}
fclose(fp);
*rev_min = board_rev_min = rev;
}
const char* get_hwversion_desc()
{
int rev;
int model;
size_t len;
static char model_name[64] = {0, };
len = snprintf(model_name, sizeof(model_name), "NuRAN OC-2G BTS");
char rev_maj = 0, rev_min = 0;
int rc = 0;
oc2gbts_rev_get(&rev_maj, &rev_min);
if (rc < 0)
return rc;
if (rev >= 0) {
len += snprintf(model_name + len, sizeof(model_name) - len,
" Rev %d.%d", (uint8_t)rev_maj, (uint8_t)rev_min);
}
model = oc2gbts_model_get();
if (model >= 0) {
snprintf(model_name + len, sizeof(model_name) - len,
"%s (%05X)", model_name, model);
}
return model_name;
}
int oc2gbts_model_get(void)
{
FILE *fp;
int opt;
if (board_option == -1) {
fp = fopen(BOARD_OPT_SYSFS, "r");
if (fp == NULL) {
return -1;
}
if (fscanf(fp, "%X", &opt) != 1) {
fclose( fp );
return -1;
}
fclose(fp);
board_option = opt;
}
return board_option;
}
int oc2gbts_option_get(enum oc2gbts_option_type type)
{
int rc;
int option;
if (type >= _NUM_OPTION_TYPES) {
return -EINVAL;
}
if (board_option == -1) {
rc = oc2gbts_model_get();
if (rc < 0) return rc;
}
option = (board_option >> option_type_shift[type])
& option_type_mask[type];
return option;
}

View File

@ -0,0 +1,47 @@
#ifndef _OC2GBTS_BOARD_H
#define _OC2GBTS_BOARD_H
#include <stdint.h>
enum oc2gbts_option_type {
OC2GBTS_OPTION_OCXO,
OC2GBTS_OPTION_FPGA,
OC2GBTS_OPTION_PA,
OC2GBTS_OPTION_BAND,
OC2GBTS_OPTION_TX_ATT,
OC2GBTS_OPTION_TX_FIL,
OC2GBTS_OPTION_RX_ATT,
OC2GBTS_OPTION_RMS_FWD,
OC2GBTS_OPTION_RMS_REFL,
OC2GBTS_OPTION_DDR_32B,
OC2GBTS_OPTION_DDR_ECC,
OC2GBTS_OPTION_PA_TEMP,
_NUM_OPTION_TYPES
};
enum oc2gbts_ocxo_type {
OC2GBTS_OCXO_BILAY_NVG45AV2072,
OC2GBTS_OCXO_TAITIEN_NJ26M003,
_NUM_OCXO_TYPES
};
enum oc2gbts_fpga_type {
OC2GBTS_FPGA_35T,
OC2GBTS_FPGA_50T,
OC2GBTS_FPGA_75T,
OC2GBTS_FPGA_100T,
_NUM_FPGA_TYPES
};
enum oc2gbts_gsm_band {
OC2GBTS_BAND_850,
OC2GBTS_BAND_900,
OC2GBTS_BAND_1800,
OC2GBTS_BAND_1900,
};
void oc2gbts_rev_get(char *rev_maj, char *rev_min);
int oc2gbts_model_get(void);
int oc2gbts_option_get(enum oc2gbts_option_type type);
#endif

View File

@ -0,0 +1,129 @@
/* Copyright (C) 2016 by NuRAN Wireless <support@nuranwireless.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <sys/ioctl.h>
#include <net/if.h>
#include <netdb.h>
#include <ifaddrs.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include "oc2gbts_mgr.h"
#include "oc2gbts_bts.h"
static int check_eth_status(char *dev_name)
{
int fd, rc;
struct ifreq ifr;
fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
if (fd < 0)
return fd;
memset(&ifr, 0, sizeof(ifr));
memcpy(&ifr.ifr_name, dev_name, sizeof(ifr.ifr_name));
rc = ioctl(fd, SIOCGIFFLAGS, &ifr);
close(fd);
if (rc < 0)
return rc;
if ((ifr.ifr_flags & IFF_UP) && (ifr.ifr_flags & IFF_RUNNING))
return 0;
return 1;
}
void check_bts_led_pattern(uint8_t *led)
{
FILE *fp;
char str[64] = "\0";
int rc;
/* check for existing of BTS state file */
if ((fp = fopen("/var/run/osmo-bts/state", "r")) == NULL) {
led[BLINK_PATTERN_INIT] = 1;
return;
}
/* check Ethernet interface status */
rc = check_eth_status("eth0");
if (rc > 0) {
LOGP(DTEMP, LOGL_DEBUG,"External link is DOWN\n");
led[BLINK_PATTERN_EXT_LINK_MALFUNC] = 1;
fclose(fp);
return;
}
/* check for BTS is still alive */
if (system("pidof osmo-bts-oc2g > /dev/null")) {
LOGP(DTEMP, LOGL_DEBUG,"BTS process has stopped\n");
led[BLINK_PATTERN_INT_PROC_MALFUNC] = 1;
fclose(fp);
return;
}
/* check for BTS state */
while (fgets(str, 64, fp) != NULL) {
LOGP(DTEMP, LOGL_DEBUG,"BTS state is %s\n", (strstr(str, "ABIS DOWN") != NULL) ? "DOWN" : "UP");
if (strstr(str, "ABIS DOWN") != NULL)
led[BLINK_PATTERN_INT_PROC_MALFUNC] = 1;
}
fclose(fp);
return;
}
int check_sensor_led_pattern( struct oc2gbts_mgr_instance *mgr, uint8_t *led)
{
if(mgr->alarms.temp_high == 1)
led[BLINK_PATTERN_TEMP_HIGH] = 1;
if(mgr->alarms.temp_max == 1)
led[BLINK_PATTERN_TEMP_MAX] = 1;
if(mgr->alarms.supply_low == 1)
led[BLINK_PATTERN_SUPPLY_VOLT_LOW] = 1;
if(mgr->alarms.supply_min == 1)
led[BLINK_PATTERN_SUPPLY_VOLT_MIN] = 1;
if(mgr->alarms.vswr_high == 1)
led[BLINK_PATTERN_VSWR_HIGH] = 1;
if(mgr->alarms.vswr_max == 1)
led[BLINK_PATTERN_VSWR_MAX] = 1;
if(mgr->alarms.supply_pwr_high == 1)
led[BLINK_PATTERN_SUPPLY_PWR_HIGH] = 1;
if(mgr->alarms.supply_pwr_max == 1)
led[BLINK_PATTERN_SUPPLY_PWR_MAX] = 1;
if(mgr->alarms.pa_pwr_high == 1)
led[BLINK_PATTERN_PA_PWR_HIGH] = 1;
if(mgr->alarms.pa_pwr_max == 1)
led[BLINK_PATTERN_PA_PWR_MAX] = 1;
if(mgr->alarms.gps_fix_lost == 1)
led[BLINK_PATTERN_GPS_FIX_LOST] = 1;
return 0;
}

View File

@ -0,0 +1,21 @@
#ifndef _OC2GBTS_BTS_H_
#define _OC2GBTS_BTS_H_
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <stdint.h>
#include <ctype.h>
#include <string.h>
#include <sys/signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <osmo-bts/logging.h>
/* public function prototypes */
void check_bts_led_pattern(uint8_t *led);
int check_sensor_led_pattern( struct oc2gbts_mgr_instance *mgr, uint8_t *led);
#endif

View File

@ -0,0 +1,263 @@
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include "oc2gbts_clock.h"
#define CLKERR_ERR_SYSFS "/var/oc2g/clkerr/clkerr1_average"
#define CLKERR_ACC_SYSFS "/var/oc2g/clkerr/clkerr1_average_accuracy"
#define CLKERR_INT_SYSFS "/var/oc2g/clkerr/clkerr1_average_interval"
#define CLKERR_FLT_SYSFS "/var/oc2g/clkerr/clkerr1_fault"
#define CLKERR_RFS_SYSFS "/var/oc2g/clkerr/refresh"
#define CLKERR_RST_SYSFS "/var/oc2g/clkerr/reset"
#define OCXODAC_VAL_SYSFS "/var/oc2g/ocxo/voltage"
#define OCXODAC_ROM_SYSFS "/var/oc2g/ocxo/eeprom"
/* clock error */
static int clkerr_fd_err = -1;
static int clkerr_fd_accuracy = -1;
static int clkerr_fd_interval = -1;
static int clkerr_fd_fault = -1;
static int clkerr_fd_refresh = -1;
static int clkerr_fd_reset = -1;
/* ocxo dac */
static int ocxodac_fd_value = -1;
static int ocxodac_fd_save = -1;
static int sysfs_read_val(int fd, int *val)
{
int rc;
char szVal[32] = {0};
lseek( fd, 0, SEEK_SET );
rc = read(fd, szVal, sizeof(szVal) - 1);
if (rc < 0) {
return -errno;
}
rc = sscanf(szVal, "%d", val);
if (rc != 1) {
return -1;
}
return 0;
}
static int sysfs_write_val(int fd, int val)
{
int n, rc;
char szVal[32] = {0};
n = sprintf(szVal, "%d", val);
lseek(fd, 0, SEEK_SET);
rc = write(fd, szVal, n+1);
if (rc < 0) {
return -errno;
}
return 0;
}
static int sysfs_write_str(int fd, const char *str)
{
int rc;
lseek( fd, 0, SEEK_SET );
rc = write(fd, str, strlen(str)+1);
if (rc < 0) {
return -errno;
}
return 0;
}
int oc2gbts_clock_err_open(void)
{
int rc;
int fault;
if (clkerr_fd_err < 0) {
clkerr_fd_err = open(CLKERR_ERR_SYSFS, O_RDONLY);
if (clkerr_fd_err < 0) {
oc2gbts_clock_err_close();
return clkerr_fd_err;
}
}
if (clkerr_fd_accuracy < 0) {
clkerr_fd_accuracy = open(CLKERR_ACC_SYSFS, O_RDONLY);
if (clkerr_fd_accuracy < 0) {
oc2gbts_clock_err_close();
return clkerr_fd_accuracy;
}
}
if (clkerr_fd_interval < 0) {
clkerr_fd_interval = open(CLKERR_INT_SYSFS, O_RDONLY);
if (clkerr_fd_interval < 0) {
oc2gbts_clock_err_close();
return clkerr_fd_interval;
}
}
if (clkerr_fd_fault < 0) {
clkerr_fd_fault = open(CLKERR_FLT_SYSFS, O_RDONLY);
if (clkerr_fd_fault < 0) {
oc2gbts_clock_err_close();
return clkerr_fd_fault;
}
}
if (clkerr_fd_refresh < 0) {
clkerr_fd_refresh = open(CLKERR_RFS_SYSFS, O_WRONLY);
if (clkerr_fd_refresh < 0) {
oc2gbts_clock_err_close();
return clkerr_fd_refresh;
}
}
if (clkerr_fd_reset < 0) {
clkerr_fd_reset = open(CLKERR_RST_SYSFS, O_WRONLY);
if (clkerr_fd_reset < 0) {
oc2gbts_clock_err_close();
return clkerr_fd_reset;
}
}
return 0;
}
void oc2gbts_clock_err_close(void)
{
if (clkerr_fd_err >= 0) {
close(clkerr_fd_err);
clkerr_fd_err = -1;
}
if (clkerr_fd_accuracy >= 0) {
close(clkerr_fd_accuracy);
clkerr_fd_accuracy = -1;
}
if (clkerr_fd_interval >= 0) {
close(clkerr_fd_interval);
clkerr_fd_interval = -1;
}
if (clkerr_fd_fault >= 0) {
close(clkerr_fd_fault);
clkerr_fd_fault = -1;
}
if (clkerr_fd_refresh >= 0) {
close(clkerr_fd_refresh);
clkerr_fd_refresh = -1;
}
if (clkerr_fd_reset >= 0) {
close(clkerr_fd_reset);
clkerr_fd_reset = -1;
}
}
int oc2gbts_clock_err_reset(void)
{
return sysfs_write_val(clkerr_fd_reset, 1);
}
int oc2gbts_clock_err_get(int *fault, int *error_ppt,
int *accuracy_ppq, int *interval_sec)
{
int rc;
rc = sysfs_write_str(clkerr_fd_refresh, "once");
if (rc < 0) {
return -1;
}
rc = sysfs_read_val(clkerr_fd_fault, fault);
rc |= sysfs_read_val(clkerr_fd_err, error_ppt);
rc |= sysfs_read_val(clkerr_fd_accuracy, accuracy_ppq);
rc |= sysfs_read_val(clkerr_fd_interval, interval_sec);
if (rc) {
return -1;
}
return 0;
}
int oc2gbts_clock_dac_open(void)
{
if (ocxodac_fd_value < 0) {
ocxodac_fd_value = open(OCXODAC_VAL_SYSFS, O_RDWR);
if (ocxodac_fd_value < 0) {
oc2gbts_clock_dac_close();
return ocxodac_fd_value;
}
}
if (ocxodac_fd_save < 0) {
ocxodac_fd_save = open(OCXODAC_ROM_SYSFS, O_WRONLY);
if (ocxodac_fd_save < 0) {
oc2gbts_clock_dac_close();
return ocxodac_fd_save;
}
}
return 0;
}
void oc2gbts_clock_dac_close(void)
{
if (ocxodac_fd_value >= 0) {
close(ocxodac_fd_value);
ocxodac_fd_value = -1;
}
if (ocxodac_fd_save >= 0) {
close(ocxodac_fd_save);
ocxodac_fd_save = -1;
}
}
int oc2gbts_clock_dac_get(int *dac_value)
{
return sysfs_read_val(ocxodac_fd_value, dac_value);
}
int oc2gbts_clock_dac_set(int dac_value)
{
return sysfs_write_val(ocxodac_fd_value, dac_value);
}
int oc2gbts_clock_dac_save(void)
{
return sysfs_write_val(ocxodac_fd_save, 1);
}

View File

@ -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

View File

@ -0,0 +1,332 @@
/* Copyright (C) 2016 by NuRAN Wireless <support@nuranwireless.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "oc2gbts_led.h"
#include "oc2gbts_bts.h"
#include <osmocom/core/talloc.h>
#include <osmocom/core/linuxlist.h>
static struct oc2gbts_led led_entries[] = {
{
.name = "led0",
.fullname = "led red",
.path = "/var/oc2g/leds/led0/brightness"
},
{
.name = "led1",
.fullname = "led green",
.path = "/var/oc2g/leds/led1/brightness"
}
};
static const struct value_string oc2gbts_led_strs[] = {
{ OC2GBTS_LED_RED, "LED red" },
{ OC2GBTS_LED_GREEN, "LED green" },
{ OC2GBTS_LED_ORANGE, "LED orange" },
{ OC2GBTS_LED_OFF, "LED off" },
{ 0, NULL }
};
static uint8_t led_priority[] = {
BLINK_PATTERN_INIT,
BLINK_PATTERN_INT_PROC_MALFUNC,
BLINK_PATTERN_SUPPLY_PWR_MAX,
BLINK_PATTERN_PA_PWR_MAX,
BLINK_PATTERN_VSWR_MAX,
BLINK_PATTERN_SUPPLY_VOLT_MIN,
BLINK_PATTERN_TEMP_MAX,
BLINK_PATTERN_EXT_LINK_MALFUNC,
BLINK_PATTERN_SUPPLY_VOLT_LOW,
BLINK_PATTERN_TEMP_HIGH,
BLINK_PATTERN_VSWR_HIGH,
BLINK_PATTERN_SUPPLY_PWR_HIGH,
BLINK_PATTERN_PA_PWR_HIGH,
BLINK_PATTERN_GPS_FIX_LOST,
BLINK_PATTERN_NORMAL
};
char *blink_pattern_command[] = BLINK_PATTERN_COMMAND;
static int oc2gbts_led_write(char *path, char *str)
{
int fd;
if ((fd = open(path, O_WRONLY)) == -1)
{
return 0;
}
write(fd, str, strlen(str)+1);
close(fd);
return 1;
}
static void led_set_red()
{
oc2gbts_led_write(led_entries[0].path, "1");
oc2gbts_led_write(led_entries[1].path, "0");
}
static void led_set_green()
{
oc2gbts_led_write(led_entries[0].path, "0");
oc2gbts_led_write(led_entries[1].path, "1");
}
static void led_set_orange()
{
oc2gbts_led_write(led_entries[0].path, "1");
oc2gbts_led_write(led_entries[1].path, "1");
}
static void led_set_off()
{
oc2gbts_led_write(led_entries[0].path, "0");
oc2gbts_led_write(led_entries[1].path, "0");
}
static void led_sleep( struct oc2gbts_mgr_instance *mgr, struct oc2gbts_led_timer *led_timer, void (*led_timer_cb)(void *_data)) {
/* Cancel any pending timer */
osmo_timer_del(&led_timer->timer);
/* Start LED timer */
led_timer->timer.cb = led_timer_cb;
led_timer->timer.data = mgr;
mgr->oc2gbts_leds.active_timer = led_timer->idx;
osmo_timer_schedule(&led_timer->timer, led_timer->param.sleep_sec, led_timer->param.sleep_usec);
LOGP(DTEMP, LOGL_DEBUG,"%s timer scheduled for %d sec + %d usec\n",
get_value_string(oc2gbts_led_strs, led_timer->idx),
led_timer->param.sleep_sec,
led_timer->param.sleep_usec);
switch (led_timer->idx) {
case OC2GBTS_LED_RED:
led_set_red();
break;
case OC2GBTS_LED_GREEN:
led_set_green();
break;
case OC2GBTS_LED_ORANGE:
led_set_orange();
break;
case OC2GBTS_LED_OFF:
led_set_off();
break;
default:
led_set_off();
}
}
static void led_sleep_cb(void *_data) {
struct oc2gbts_mgr_instance *mgr = _data;
struct oc2gbts_led_timer_list *led_list;
/* make sure the timer list is not empty */
if (llist_empty(&mgr->oc2gbts_leds.list))
return;
llist_for_each_entry(led_list, &mgr->oc2gbts_leds.list, list) {
if (led_list->led_timer.idx == mgr->oc2gbts_leds.active_timer) {
LOGP(DTEMP, LOGL_DEBUG,"Delete expired %s timer %d sec + %d usec\n",
get_value_string(oc2gbts_led_strs, led_list->led_timer.idx),
led_list->led_timer.param.sleep_sec,
led_list->led_timer.param.sleep_usec);
/* Delete current timer */
osmo_timer_del(&led_list->led_timer.timer);
/* Rotate the timer list */
llist_move_tail(led_list, &mgr->oc2gbts_leds.list);
break;
}
}
/* Execute next timer */
led_list = llist_first_entry(&mgr->oc2gbts_leds.list, struct oc2gbts_led_timer_list, list);
if (led_list) {
LOGP(DTEMP, LOGL_DEBUG,"Execute %s timer %d sec + %d usec, total entries=%d\n",
get_value_string(oc2gbts_led_strs, led_list->led_timer.idx),
led_list->led_timer.param.sleep_sec,
led_list->led_timer.param.sleep_usec,
llist_count(&mgr->oc2gbts_leds.list));
led_sleep(mgr, &led_list->led_timer, led_sleep_cb);
}
}
static void delete_led_timer_entries(struct oc2gbts_mgr_instance *mgr)
{
struct oc2gbts_led_timer_list *led_list, *led_list2;
if (llist_empty(&mgr->oc2gbts_leds.list))
return;
llist_for_each_entry_safe(led_list, led_list2, &mgr->oc2gbts_leds.list, list) {
/* Delete the timer in list */
if (led_list) {
LOGP(DTEMP, LOGL_DEBUG,"Delete %s timer entry from list, %d sec + %d usec\n",
get_value_string(oc2gbts_led_strs, led_list->led_timer.idx),
led_list->led_timer.param.sleep_sec,
led_list->led_timer.param.sleep_usec);
/* Delete current timer */
osmo_timer_del(&led_list->led_timer.timer);
llist_del(&led_list->list);
talloc_free(led_list);
}
}
return;
}
static int add_led_timer_entry(struct oc2gbts_mgr_instance *mgr, char *cmdstr)
{
double sec, int_sec, frac_sec;
struct oc2gbts_sleep_time led_param;
led_param.sleep_sec = 0;
led_param.sleep_usec = 0;
if (strstr(cmdstr, "set red") != NULL)
mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_RED;
else if (strstr(cmdstr, "set green") != NULL)
mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_GREEN;
else if (strstr(cmdstr, "set orange") != NULL)
mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_ORANGE;
else if (strstr(cmdstr, "set off") != NULL)
mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_OFF;
else if (strstr(cmdstr, "sleep") != NULL) {
sec = atof(cmdstr + 6);
/* split time into integer and fractional of seconds */
frac_sec = modf(sec, &int_sec) * 1000000.0;
led_param.sleep_sec = (int)int_sec;
led_param.sleep_usec = (int)frac_sec;
if ((mgr->oc2gbts_leds.led_idx >= OC2GBTS_LED_RED) && (mgr->oc2gbts_leds.led_idx < _OC2GBTS_LED_MAX)) {
struct oc2gbts_led_timer_list *led_list;
/* allocate timer entry */
led_list = talloc_zero(tall_mgr_ctx, struct oc2gbts_led_timer_list);
if (led_list) {
led_list->led_timer.idx = mgr->oc2gbts_leds.led_idx;
led_list->led_timer.param.sleep_sec = led_param.sleep_sec;
led_list->led_timer.param.sleep_usec = led_param.sleep_usec;
llist_add_tail(&led_list->list, &mgr->oc2gbts_leds.list);
LOGP(DTEMP, LOGL_DEBUG,"Add %s timer to list, %d sec + %d usec, total entries=%d\n",
get_value_string(oc2gbts_led_strs, mgr->oc2gbts_leds.led_idx),
led_list->led_timer.param.sleep_sec,
led_list->led_timer.param.sleep_usec,
llist_count(&mgr->oc2gbts_leds.list));
}
}
} else
return -1;
return 0;
}
static int parse_led_pattern(char *pattern, struct oc2gbts_mgr_instance *mgr)
{
char str[1024];
char *pstr;
char *sep;
int rc = 0;
strcpy(str, pattern);
pstr = str;
while ((sep = strsep(&pstr, ";")) != NULL) {
rc = add_led_timer_entry(mgr, sep);
if (rc < 0) {
break;
}
}
return rc;
}
/*** led interface ***/
void led_set(struct oc2gbts_mgr_instance *mgr, int pattern_id)
{
int rc;
struct oc2gbts_led_timer_list *led_list;
if (pattern_id > BLINK_PATTERN_MAX_ITEM - 1) {
LOGP(DTEMP, LOGL_ERROR, "Invalid LED pattern : %d. LED pattern must be between %d..%d\n",
pattern_id,
BLINK_PATTERN_POWER_ON,
BLINK_PATTERN_MAX_ITEM - 1);
return;
}
if (pattern_id == mgr->oc2gbts_leds.last_pattern_id)
return;
mgr->oc2gbts_leds.last_pattern_id = pattern_id;
LOGP(DTEMP, LOGL_INFO, "blink pattern command : %d\n", pattern_id);
LOGP(DTEMP, LOGL_INFO, "%s\n", blink_pattern_command[pattern_id]);
/* Empty existing LED timer in the list */
delete_led_timer_entries(mgr);
/* parse LED pattern */
rc = parse_led_pattern(blink_pattern_command[pattern_id], mgr);
if (rc < 0) {
LOGP(DTEMP, LOGL_ERROR,"LED pattern not found or invalid LED pattern\n");
return;
}
/* make sure the timer list is not empty */
if (llist_empty(&mgr->oc2gbts_leds.list))
return;
/* Start the first LED timer in the list */
led_list = llist_first_entry(&mgr->oc2gbts_leds.list, struct oc2gbts_led_timer_list, list);
if (led_list) {
LOGP(DTEMP, LOGL_DEBUG,"Execute timer %s for %d sec + %d usec\n",
get_value_string(oc2gbts_led_strs, led_list->led_timer.idx),
led_list->led_timer.param.sleep_sec,
led_list->led_timer.param.sleep_usec);
led_sleep(mgr, &led_list->led_timer, led_sleep_cb);
}
}
void select_led_pattern(struct oc2gbts_mgr_instance *mgr)
{
int i;
uint8_t led[BLINK_PATTERN_MAX_ITEM] = {0};
/* set normal LED pattern at first */
led[BLINK_PATTERN_NORMAL] = 1;
/* check on-board sensors for new LED pattern */
check_sensor_led_pattern(mgr, led);
/* check BTS status for new LED pattern */
check_bts_led_pattern(led);
/* check by priority */
for (i = 0; i < sizeof(led_priority)/sizeof(uint8_t); i++) {
if(led[led_priority[i]] == 1) {
led_set(mgr, led_priority[i]);
break;
}
}
}

View File

@ -0,0 +1,22 @@
#ifndef _OC2GBTS_LED_H
#define _OC2GBTS_LED_H
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <math.h>
#include <sys/stat.h>
#include <osmo-bts/logging.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/utils.h>
#include "oc2gbts_mgr.h"
/* public function prototypes */
void led_set(struct oc2gbts_mgr_instance *mgr, int pattern_id);
void select_led_pattern(struct oc2gbts_mgr_instance *mgr);
#endif

View File

@ -0,0 +1,353 @@
/* Main program for NuRAN Wireless OC-2G BTS management daemon */
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* Based on sysmoBTS:
* sysmobts_mgr.c
* (C) 2012 by Harald Welte <laforge@gnumonks.org>
* (C) 2014 by Holger Hans Peter Freyther
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <getopt.h>
#include <limits.h>
#include <sys/signal.h>
#include <sys/stat.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/application.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/msgb.h>
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/logging.h>
#include <osmocom/vty/ports.h>
#include "misc/oc2gbts_misc.h"
#include "misc/oc2gbts_mgr.h"
#include "misc/oc2gbts_par.h"
#include "misc/oc2gbts_bid.h"
#include "misc/oc2gbts_power.h"
#include "misc/oc2gbts_swd.h"
#include "oc2gbts_led.h"
static int no_rom_write = 0;
static int daemonize = 0;
void *tall_mgr_ctx;
/* every 6 hours means 365*4 = 1460 rom writes per year (max) */
#define SENSOR_TIMER_SECS (6 * 3600)
/* every 1 hours means 365*24 = 8760 rom writes per year (max) */
#define HOURS_TIMER_SECS (1 * 3600)
/* the initial state */
static struct oc2gbts_mgr_instance manager = {
.config_file = "oc2gbts-mgr.cfg",
.temp = {
.supply_temp_limit = {
.thresh_warn_max = 80,
.thresh_crit_max = 85,
.thresh_warn_min = -40,
},
.soc_temp_limit = {
.thresh_warn_max = 95,
.thresh_crit_max = 100,
.thresh_warn_min = -40,
},
.fpga_temp_limit = {
.thresh_warn_max = 95,
.thresh_crit_max = 100,
.thresh_warn_min = -40,
},
.rmsdet_temp_limit = {
.thresh_warn_max = 80,
.thresh_crit_max = 85,
.thresh_warn_min = -40,
},
.ocxo_temp_limit = {
.thresh_warn_max = 80,
.thresh_crit_max = 85,
.thresh_warn_min = -40,
},
.tx_temp_limit = {
.thresh_warn_max = 80,
.thresh_crit_max = 85,
.thresh_warn_min = -20,
},
.pa_temp_limit = {
.thresh_warn_max = 80,
.thresh_crit_max = 85,
.thresh_warn_min = -40,
}
},
.volt = {
.supply_volt_limit = {
.thresh_warn_max = 30000,
.thresh_crit_max = 30500,
.thresh_warn_min = 19000,
.thresh_crit_min = 17500,
}
},
.pwr = {
.supply_pwr_limit = {
.thresh_warn_max = 30,
.thresh_crit_max = 40,
},
.pa_pwr_limit = {
.thresh_warn_max = 20,
.thresh_crit_max = 30,
}
},
.vswr = {
.vswr_limit = {
.thresh_warn_max = 3000,
.thresh_crit_max = 5000,
}
},
.gps = {
.gps_fix_limit = {
.thresh_warn_max = 7,
}
},
.state = {
.action_norm = SENSOR_ACT_NORM_PA_ON,
.action_warn = 0,
.action_crit = 0,
.action_comb = 0,
.state = STATE_NORMAL,
}
};
static struct osmo_timer_list sensor_timer;
static void check_sensor_timer_cb(void *unused)
{
oc2gbts_check_temp(no_rom_write);
oc2gbts_check_power(no_rom_write);
oc2gbts_check_vswr(no_rom_write);
osmo_timer_schedule(&sensor_timer, SENSOR_TIMER_SECS, 0);
/* TODO checks if oc2gbts_check_temp/oc2gbts_check_power/oc2gbts_check_vswr went ok */
oc2gbts_swd_event(&manager, SWD_CHECK_SENSOR);
}
static struct osmo_timer_list hours_timer;
static void hours_timer_cb(void *unused)
{
oc2gbts_update_hours(no_rom_write);
osmo_timer_schedule(&hours_timer, HOURS_TIMER_SECS, 0);
/* TODO: validates if oc2gbts_update_hours went correctly */
oc2gbts_swd_event(&manager, SWD_UPDATE_HOURS);
}
static void print_help(void)
{
printf("oc2gbts-mgr [-nsD] [-d cat]\n");
printf(" -n Do not write to ROM\n");
printf(" -s Disable color\n");
printf(" -d CAT enable debugging\n");
printf(" -D daemonize\n");
printf(" -c Specify the filename of the config file\n");
}
static int parse_options(int argc, char **argv)
{
int opt;
while ((opt = getopt(argc, argv, "nhsd:c:")) != -1) {
switch (opt) {
case 'n':
no_rom_write = 1;
break;
case 'h':
print_help();
return -1;
case 's':
log_set_use_color(osmo_stderr_target, 0);
break;
case 'd':
log_parse_category_mask(osmo_stderr_target, optarg);
break;
case 'D':
daemonize = 1;
break;
case 'c':
manager.config_file = optarg;
break;
default:
return -1;
}
}
return 0;
}
static void signal_handler(int signal)
{
fprintf(stderr, "signal %u received\n", signal);
switch (signal) {
case SIGINT:
oc2gbts_check_temp(no_rom_write);
oc2gbts_check_power(no_rom_write);
oc2gbts_check_vswr(no_rom_write);
oc2gbts_update_hours(no_rom_write);
exit(0);
break;
case SIGABRT:
case SIGUSR1:
case SIGUSR2:
talloc_report_full(tall_mgr_ctx, stderr);
break;
default:
break;
}
}
static struct log_info_cat mgr_log_info_cat[] = {
[DTEMP] = {
.name = "DTEMP",
.description = "Temperature monitoring",
.color = "\033[1;35m",
.enabled = 1, .loglevel = LOGL_INFO,
},
[DFW] = {
.name = "DFW",
.description = "Firmware management",
.color = "\033[1;36m",
.enabled = 1, .loglevel = LOGL_INFO,
},
[DFIND] = {
.name = "DFIND",
.description = "ipaccess-find handling",
.color = "\033[1;37m",
.enabled = 1, .loglevel = LOGL_INFO,
},
[DCALIB] = {
.name = "DCALIB",
.description = "Calibration handling",
.color = "\033[1;37m",
.enabled = 1, .loglevel = LOGL_INFO,
},
[DSWD] = {
.name = "DSWD",
.description = "Software Watchdog",
.color = "\033[1;37m",
.enabled = 1, .loglevel = LOGL_INFO,
},
};
static const struct log_info mgr_log_info = {
.cat = mgr_log_info_cat,
.num_cat = ARRAY_SIZE(mgr_log_info_cat),
};
static int mgr_log_init(void)
{
osmo_init_logging(&mgr_log_info);
return 0;
}
int main(int argc, char **argv)
{
void *tall_msgb_ctx;
int rc;
pthread_t tid;
tall_mgr_ctx = talloc_named_const(NULL, 1, "bts manager");
tall_msgb_ctx = talloc_named_const(tall_mgr_ctx, 1, "msgb");
msgb_set_talloc_ctx(tall_msgb_ctx);
mgr_log_init();
osmo_init_ignore_signals();
signal(SIGINT, &signal_handler);
signal(SIGUSR1, &signal_handler);
signal(SIGUSR2, &signal_handler);
rc = parse_options(argc, argv);
if (rc < 0)
exit(2);
oc2gbts_mgr_vty_init();
logging_vty_add_cmds(&mgr_log_info);
rc = oc2gbts_mgr_parse_config(&manager);
if (rc < 0) {
LOGP(DFIND, LOGL_FATAL, "Cannot parse config file\n");
exit(1);
}
rc = telnet_init(tall_mgr_ctx, NULL, OSMO_VTY_PORT_BTSMGR);
if (rc < 0) {
fprintf(stderr, "Error initializing telnet\n");
exit(1);
}
INIT_LLIST_HEAD(&manager.oc2gbts_leds.list);
INIT_LLIST_HEAD(&manager.alarms.list);
/* Initialize the service watchdog notification for SWD_LAST event(s) */
if (oc2gbts_swd_init(&manager, (int)(SWD_LAST)) != 0)
exit(3);
/* start temperature check timer */
sensor_timer.cb = check_sensor_timer_cb;
check_sensor_timer_cb(NULL);
/* start operational hours timer */
hours_timer.cb = hours_timer_cb;
hours_timer_cb(NULL);
if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
/* Enable the PAs */
rc = oc2gbts_power_set(OC2GBTS_POWER_PA, 1);
if (rc < 0) {
exit(3);
}
}
/* handle broadcast messages for ipaccess-find */
if (oc2gbts_mgr_nl_init() != 0)
exit(3);
/* Initialize the sensor control */
oc2gbts_mgr_sensor_init(&manager);
if (oc2gbts_mgr_calib_init(&manager) != 0)
exit(3);
if (oc2gbts_mgr_control_init(&manager))
exit(3);
if (daemonize) {
rc = osmo_daemonize();
if (rc < 0) {
perror("Error during daemonize");
exit(1);
}
}
while (1) {
log_reset_context();
osmo_select_main(0);
oc2gbts_swd_event(&manager, SWD_MAINLOOP);
}
}

View File

@ -0,0 +1,398 @@
#ifndef _OC2GBTS_MGR_H
#define _OC2GBTS_MGR_H
#include <osmocom/vty/vty.h>
#include <osmocom/vty/command.h>
#include <osmocom/core/select.h>
#include <osmocom/core/timer.h>
#include <stdint.h>
#include <gps.h>
#define OC2GBTS_SENSOR_TIMER_DURATION 60
#define OC2GBTS_PREVENT_TIMER_DURATION 15 * 60
#define OC2GBTS_PREVENT_TIMER_SHORT_DURATION 5 * 60
#define OC2GBTS_PREVENT_TIMER_NONE 0
#define OC2GBTS_PREVENT_RETRY INT_MAX - 1
enum BLINK_PATTERN {
BLINK_PATTERN_POWER_ON = 0, //hardware set
BLINK_PATTERN_INIT,
BLINK_PATTERN_NORMAL,
BLINK_PATTERN_EXT_LINK_MALFUNC,
BLINK_PATTERN_INT_PROC_MALFUNC,
BLINK_PATTERN_SUPPLY_VOLT_LOW,
BLINK_PATTERN_SUPPLY_VOLT_MIN,
BLINK_PATTERN_VSWR_HIGH,
BLINK_PATTERN_VSWR_MAX,
BLINK_PATTERN_TEMP_HIGH,
BLINK_PATTERN_TEMP_MAX,
BLINK_PATTERN_SUPPLY_PWR_HIGH,
BLINK_PATTERN_SUPPLY_PWR_MAX,
BLINK_PATTERN_PA_PWR_HIGH,
BLINK_PATTERN_PA_PWR_MAX,
BLINK_PATTERN_GPS_FIX_LOST,
BLINK_PATTERN_MAX_ITEM
};
#define BLINK_PATTERN_COMMAND {\
"set red; sleep 5.0",\
"set orange; sleep 5.0",\
"set green; sleep 2.5; set off; sleep 2.5",\
"set red; sleep 0.5; set off; sleep 0.5",\
"set red; sleep 2.5; set off; sleep 2.5",\
"set green; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5",\
"set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5 ",\
"set green; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\
"set red; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\
"set orange; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5 ",\
"set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\
"set green; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\
"set red; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\
"set green; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\
"set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\
"set green; sleep 2.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\
}
enum {
DTEMP,
DFW,
DFIND,
DCALIB,
DSWD,
};
// TODO NTQD: Define new actions like reducing output power, limit ARM core speed, shutdown second TRX/PA, ...
enum {
#if 0
SENSOR_ACT_PWR_CONTRL = 0x1,
#endif
SENSOR_ACT_PA_OFF = 0x2,
SENSOR_ACT_BTS_SRV_OFF = 0x10,
};
/* actions only for normal state */
enum {
#if 0
SENSOR_ACT_NORM_PW_CONTRL = 0x1,
#endif
SENSOR_ACT_NORM_PA_ON = 0x2,
SENSOR_ACT_NORM_BTS_SRV_ON= 0x10,
};
enum oc2gbts_sensor_state {
STATE_NORMAL, /* Everything is fine */
STATE_WARNING_HYST, /* Go back to normal next? */
STATE_WARNING, /* We are above the warning threshold */
STATE_CRITICAL, /* We have an issue. Wait for below warning */
};
enum oc2gbts_leds_name {
OC2GBTS_LED_RED = 0,
OC2GBTS_LED_GREEN,
OC2GBTS_LED_ORANGE,
OC2GBTS_LED_OFF,
_OC2GBTS_LED_MAX
};
struct oc2gbts_led{
char *name;
char *fullname;
char *path;
};
/**
* Temperature Limits. We separate from a threshold
* that will generate a warning and one that is so
* severe that an action will be taken.
*/
struct oc2gbts_temp_limit {
int thresh_warn_max;
int thresh_crit_max;
int thresh_warn_min;
};
struct oc2gbts_volt_limit {
int thresh_warn_max;
int thresh_crit_max;
int thresh_warn_min;
int thresh_crit_min;
};
struct oc2gbts_pwr_limit {
int thresh_warn_max;
int thresh_crit_max;
};
struct oc2gbts_vswr_limit {
int thresh_warn_max;
int thresh_crit_max;
};
struct oc2gbts_gps_fix_limit {
int thresh_warn_max;
};
struct oc2gbts_sleep_time {
int sleep_sec;
int sleep_usec;
};
struct oc2gbts_led_timer {
uint8_t idx;
struct osmo_timer_list timer;
struct oc2gbts_sleep_time param;
};
struct oc2gbts_led_timer_list {
struct llist_head list;
struct oc2gbts_led_timer led_timer;
};
struct oc2gbts_preventive_list {
struct llist_head list;
struct oc2gbts_sleep_time param;
int action_flag;
};
enum mgr_vty_node {
MGR_NODE = _LAST_OSMOVTY_NODE + 1,
ACT_NORM_NODE,
ACT_WARN_NODE,
ACT_CRIT_NODE,
LIMIT_SUPPLY_TEMP_NODE,
LIMIT_SOC_NODE,
LIMIT_FPGA_NODE,
LIMIT_RMSDET_NODE,
LIMIT_OCXO_NODE,
LIMIT_TX_TEMP_NODE,
LIMIT_PA_TEMP_NODE,
LIMIT_SUPPLY_VOLT_NODE,
LIMIT_VSWR_NODE,
LIMIT_SUPPLY_PWR_NODE,
LIMIT_PA_PWR_NODE,
LIMIT_GPS_FIX_NODE,
};
enum mgr_vty_limit_type {
MGR_LIMIT_TYPE_TEMP = 0,
MGR_LIMIT_TYPE_VOLT,
MGR_LIMIT_TYPE_VSWR,
MGR_LIMIT_TYPE_PWR,
_MGR_LIMIT_TYPE_MAX,
};
struct oc2gbts_mgr_instance {
const char *config_file;
struct {
struct oc2gbts_temp_limit supply_temp_limit;
struct oc2gbts_temp_limit soc_temp_limit;
struct oc2gbts_temp_limit fpga_temp_limit;
struct oc2gbts_temp_limit rmsdet_temp_limit;
struct oc2gbts_temp_limit ocxo_temp_limit;
struct oc2gbts_temp_limit tx_temp_limit;
struct oc2gbts_temp_limit pa_temp_limit;
} temp;
struct {
struct oc2gbts_volt_limit supply_volt_limit;
} volt;
struct {
struct oc2gbts_pwr_limit supply_pwr_limit;
struct oc2gbts_pwr_limit pa_pwr_limit;
} pwr;
struct {
struct oc2gbts_vswr_limit vswr_limit;
int last_vswr;
} vswr;
struct {
struct oc2gbts_gps_fix_limit gps_fix_limit;
int last_update;
time_t last_gps_fix;
time_t gps_fix_now;
int gps_open;
struct osmo_fd gpsfd;
struct gps_data_t gpsdata;
struct osmo_timer_list fix_timeout;
} gps;
struct {
int action_norm;
int action_warn;
int action_crit;
int action_comb;
enum oc2gbts_sensor_state state;
} state;
struct {
int state;
int calib_from_loop;
struct osmo_timer_list calib_timeout;
} calib;
struct {
int state;
int swd_from_loop;
unsigned long long int swd_events;
unsigned long long int swd_events_cache;
unsigned long long int swd_eventmasks;
int num_events;
struct osmo_timer_list swd_timeout;
} swd;
struct {
uint8_t led_idx;
uint8_t last_pattern_id;
uint8_t active_timer;
struct llist_head list;
} oc2gbts_leds;
struct {
int is_up;
uint32_t last_seqno;
struct osmo_timer_list recon_timer;
struct ipa_client_conn *bts_conn;
uint32_t crit_flags;
uint32_t warn_flags;
} oc2gbts_ctrl;
struct oc2gbts_alarms {
int temp_high;
int temp_max;
int supply_low;
int supply_min;
int vswr_high;
int vswr_max;
int supply_pwr_high;
int supply_pwr_max;
int pa_pwr_high;
int pa_pwr_max;
int gps_fix_lost;
struct llist_head list;
struct osmo_timer_list preventive_timer;
int preventive_duration;
int preventive_retry;
} alarms;
};
enum oc2gbts_mgr_fail_evt_rep_crit_sig {
/* Critical alarms */
S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM = (1 << 0),
S_MGR_TEMP_SOC_CRIT_MAX_ALARM = (1 << 1),
S_MGR_TEMP_FPGA_CRIT_MAX_ALARM = (1 << 2),
S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM = (1 << 3),
S_MGR_TEMP_OCXO_CRIT_MAX_ALARM = (1 << 4),
S_MGR_TEMP_TRX_CRIT_MAX_ALARM = (1 << 5),
S_MGR_TEMP_PA_CRIT_MAX_ALARM = (1 << 6),
S_MGR_SUPPLY_CRIT_MAX_ALARM = (1 << 7),
S_MGR_SUPPLY_CRIT_MIN_ALARM = (1 << 8),
S_MGR_VSWR_CRIT_MAX_ALARM = (1 << 9),
S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM = (1 << 10),
S_MGR_PWR_PA_CRIT_MAX_ALARM = (1 << 11),
_S_MGR_CRIT_ALARM_MAX,
};
enum oc2gbts_mgr_fail_evt_rep_warn_sig {
/* Warning alarms */
S_MGR_TEMP_SUPPLY_WARN_MIN_ALARM = (1 << 0),
S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM = (1 << 1),
S_MGR_TEMP_SOC_WARN_MIN_ALARM = (1 << 2),
S_MGR_TEMP_SOC_WARN_MAX_ALARM = (1 << 3),
S_MGR_TEMP_FPGA_WARN_MIN_ALARM = (1 << 4),
S_MGR_TEMP_FPGA_WARN_MAX_ALARM = (1 << 5),
S_MGR_TEMP_RMS_DET_WARN_MIN_ALARM = (1 << 6),
S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM = (1 << 7),
S_MGR_TEMP_OCXO_WARN_MIN_ALARM = (1 << 8),
S_MGR_TEMP_OCXO_WARN_MAX_ALARM = (1 << 9),
S_MGR_TEMP_TRX_WARN_MIN_ALARM = (1 << 10),
S_MGR_TEMP_TRX_WARN_MAX_ALARM = (1 << 11),
S_MGR_TEMP_PA_WARN_MIN_ALARM = (1 << 12),
S_MGR_TEMP_PA_WARN_MAX_ALARM = (1 << 13),
S_MGR_SUPPLY_WARN_MIN_ALARM = (1 << 14),
S_MGR_SUPPLY_WARN_MAX_ALARM = (1 << 15),
S_MGR_VSWR_WARN_MAX_ALARM = (1 << 16),
S_MGR_PWR_SUPPLY_WARN_MAX_ALARM = (1 << 17),
S_MGR_PWR_PA_WARN_MAX_ALARM = (1 << 18),
S_MGR_GPS_FIX_WARN_ALARM = (1 << 19),
_S_MGR_WARN_ALARM_MAX,
};
enum oc2gbts_mgr_failure_event_causes {
/* Critical causes */
NM_EVT_CAUSE_CRIT_TEMP_SUPPLY_MAX_FAIL = 0x4100,
NM_EVT_CAUSE_CRIT_TEMP_FPGA_MAX_FAIL = 0x4101,
NM_EVT_CAUSE_CRIT_TEMP_SOC_MAX_FAIL = 0x4102,
NM_EVT_CAUSE_CRIT_TEMP_RMS_DET_MAX_FAIL = 0x4103,
NM_EVT_CAUSE_CRIT_TEMP_OCXO_MAX_FAIL = 0x4104,
NM_EVT_CAUSE_CRIT_TEMP_TRX_MAX_FAIL = 0x4105,
NM_EVT_CAUSE_CRIT_TEMP_PA_MAX_FAIL = 0x4106,
NM_EVT_CAUSE_CRIT_SUPPLY_MAX_FAIL = 0x4107,
NM_EVT_CAUSE_CRIT_SUPPLY_MIN_FAIL = 0x4108,
NM_EVT_CAUSE_CRIT_VSWR_MAX_FAIL = 0x4109,
NM_EVT_CAUSE_CRIT_PWR_SUPPLY_MAX_FAIL = 0x410A,
NM_EVT_CAUSE_CRIT_PWR_PA_MAX_FAIL = 0x410B,
/* Warning causes */
NM_EVT_CAUSE_WARN_TEMP_SUPPLY_LOW_FAIL = 0x4400,
NM_EVT_CAUSE_WARN_TEMP_SUPPLY_HIGH_FAIL = 0x4401,
NM_EVT_CAUSE_WARN_TEMP_FPGA_LOW_FAIL = 0x4402,
NM_EVT_CAUSE_WARN_TEMP_FPGA_HIGH_FAIL = 0x4403,
NM_EVT_CAUSE_WARN_TEMP_SOC_LOW_FAIL = 0x4404,
NM_EVT_CAUSE_WARN_TEMP_SOC_HIGH_FAIL = 0x4405,
NM_EVT_CAUSE_WARN_TEMP_RMS_DET_LOW_FAIL = 0x4406,
NM_EVT_CAUSE_WARN_TEMP_RMS_DET_HIGH_FAIL= 0x4407,
NM_EVT_CAUSE_WARN_TEMP_OCXO_LOW_FAIL = 0x4408,
NM_EVT_CAUSE_WARN_TEMP_OCXO_HIGH_FAIL = 0x4409,
NM_EVT_CAUSE_WARN_TEMP_TRX_LOW_FAIL = 0x440A,
NM_EVT_CAUSE_WARN_TEMP_TRX_HIGH_FAIL = 0x440B,
NM_EVT_CAUSE_WARN_TEMP_PA_LOW_FAIL = 0x440C,
NM_EVT_CAUSE_WARN_TEMP_PA_HIGH_FAIL = 0x440D,
NM_EVT_CAUSE_WARN_SUPPLY_LOW_FAIL = 0x440E,
NM_EVT_CAUSE_WARN_SUPPLY_HIGH_FAIL = 0x440F,
NM_EVT_CAUSE_WARN_VSWR_HIGH_FAIL = 0x4410,
NM_EVT_CAUSE_WARN_PWR_SUPPLY_HIGH_FAIL = 0x4411,
NM_EVT_CAUSE_WARN_PWR_PA_HIGH_FAIL = 0x4412,
NM_EVT_CAUSE_WARN_GPS_FIX_FAIL = 0x4413,
};
/* This defines the list of notification events for systemd service watchdog.
all these events must be notified in a certain service defined timeslot
or the service (this app) would be restarted (only if related systemd service
unit file has WatchdogSec!=0).
WARNING: swd events must begin with event 0. Last events must be
SWD_LAST (max 64 events in this list).
*/
enum mgr_swd_events {
SWD_MAINLOOP = 0,
SWD_CHECK_SENSOR,
SWD_UPDATE_HOURS,
SWD_CHECK_TEMP_SENSOR,
SWD_CHECK_LED_CTRL,
SWD_CHECK_CALIB,
SWD_CHECK_BTS_CONNECTION,
SWD_LAST
};
int oc2gbts_mgr_vty_init(void);
int oc2gbts_mgr_parse_config(struct oc2gbts_mgr_instance *mgr);
int oc2gbts_mgr_nl_init(void);
int oc2gbts_mgr_sensor_init(struct oc2gbts_mgr_instance *mgr);
const char *oc2gbts_mgr_sensor_get_state(enum oc2gbts_sensor_state state);
int oc2gbts_mgr_calib_init(struct oc2gbts_mgr_instance *mgr);
int oc2gbts_mgr_control_init(struct oc2gbts_mgr_instance *mgr);
int oc2gbts_mgr_calib_run(struct oc2gbts_mgr_instance *mgr);
void oc2gbts_mgr_dispatch_alarm(struct oc2gbts_mgr_instance *mgr, const int cause, const char *key, const char *text);
void handle_alert_actions(struct oc2gbts_mgr_instance *mgr);
void handle_ceased_actions(struct oc2gbts_mgr_instance *mgr);
void handle_warn_actions(struct oc2gbts_mgr_instance *mgr);
extern void *tall_mgr_ctx;
#endif

View File

@ -0,0 +1,750 @@
/* OCXO calibration control for OC-2G BTS management daemon */
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* Based on sysmoBTS:
* sysmobts_mgr_calib.c
* (C) 2014,2015 by Holger Hans Peter Freyther
* (C) 2014 by Harald Welte for the IPA code from the oml router
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "misc/oc2gbts_mgr.h"
#include "misc/oc2gbts_misc.h"
#include "misc/oc2gbts_clock.h"
#include "misc/oc2gbts_swd.h"
#include "misc/oc2gbts_par.h"
#include "misc/oc2gbts_led.h"
#include "osmo-bts/msg_utils.h"
#include <osmocom/core/logging.h>
#include <osmocom/core/select.h>
#include <osmocom/ctrl/control_cmd.h>
#include <osmocom/ctrl/ports.h>
#include <osmocom/gsm/ipa.h>
#include <osmocom/gsm/protocol/ipaccess.h>
#include <osmocom/abis/abis.h>
#include <osmocom/abis/e1_input.h>
#include <osmocom/abis/ipa.h>
#include <time.h>
#include <sys/sysinfo.h>
#include <errno.h>
static void calib_adjust(struct oc2gbts_mgr_instance *mgr);
static void calib_state_reset(struct oc2gbts_mgr_instance *mgr, int reason);
static void calib_loop_run(void *_data);
static int ocxodac_saved_value = -1;
enum calib_state {
CALIB_INITIAL,
CALIB_IN_PROGRESS,
CALIB_GPS_WAIT_FOR_FIX,
};
enum calib_result {
CALIB_FAIL_START,
CALIB_FAIL_GPSFIX,
CALIB_FAIL_CLKERR,
CALIB_FAIL_OCXODAC,
CALIB_SUCCESS,
};
static int oc2gbts_par_get_uptime(void *ctx, int *ret)
{
char *fpath;
FILE *fp;
int rc;
fpath = talloc_asprintf(ctx, "%s", UPTIME_TMP_PATH);
if (!fpath)
return NULL;
fp = fopen(fpath, "r");
if (!fp)
fprintf(stderr, "Failed to open %s due to '%s' error\n", fpath, strerror(errno));
talloc_free(fpath);
if (fp == NULL) {
return -errno;
}
rc = fscanf(fp, "%d", ret);
if (rc != 1) {
fclose(fp);
return -EIO;
}
fclose(fp);
return 0;
}
static int oc2gbts_par_set_uptime(void *ctx, int val)
{
char *fpath;
FILE *fp;
int rc;
fpath = talloc_asprintf(ctx, "%s", UPTIME_TMP_PATH);
if (!fpath)
return NULL;
fp = fopen(fpath, "w");
if (!fp)
fprintf(stderr, "Failed to open %s due to '%s' error\n", fpath, strerror(errno));
talloc_free(fpath);
if (fp == NULL) {
return -errno;
}
rc = fprintf(fp, "%d", val);
if (rc < 0) {
fclose(fp);
return -EIO;
}
fsync(fp);
fclose(fp);
return 0;
}
static void mgr_gps_close(struct oc2gbts_mgr_instance *mgr)
{
if (!mgr->gps.gps_open)
return;
osmo_timer_del(&mgr->gps.fix_timeout);
osmo_fd_unregister(&mgr->gps.gpsfd);
gps_close(&mgr->gps.gpsdata);
memset(&mgr->gps.gpsdata, 0, sizeof(mgr->gps.gpsdata));
mgr->gps.gps_open = 0;
}
static void mgr_gps_checkfix(struct oc2gbts_mgr_instance *mgr)
{
struct gps_data_t *data = &mgr->gps.gpsdata;
/* No 3D fix yet */
if (data->fix.mode < MODE_3D) {
LOGP(DCALIB, LOGL_DEBUG, "Fix mode not enough: %d\n",
data->fix.mode);
return;
}
/* Satellite used checking */
if (data->satellites_used < 1) {
LOGP(DCALIB, LOGL_DEBUG, "Not enough satellites used: %d\n",
data->satellites_used);
return;
}
mgr->gps.gps_fix_now = (time_t) data->fix.time;
LOGP(DCALIB, LOGL_INFO, "Got a GPS fix, satellites used: %d, timestamp: %ld\n",
data->satellites_used, mgr->gps.gps_fix_now);
osmo_timer_del(&mgr->gps.fix_timeout);
mgr_gps_close(mgr);
}
static int mgr_gps_read(struct osmo_fd *fd, unsigned int what)
{
int rc;
struct oc2gbts_mgr_instance *mgr = fd->data;
rc = gps_read(&mgr->gps.gpsdata);
if (rc == -1) {
LOGP(DCALIB, LOGL_ERROR, "gpsd vanished during read.\n");
calib_state_reset(mgr, CALIB_FAIL_GPSFIX);
return -1;
}
if (rc > 0)
mgr_gps_checkfix(mgr);
return 0;
}
static void mgr_gps_fix_timeout(void *_data)
{
struct oc2gbts_mgr_instance *mgr = _data;
LOGP(DCALIB, LOGL_ERROR, "Failed to acquire GPS fix.\n");
mgr_gps_close(mgr);
}
static void mgr_gps_open(struct oc2gbts_mgr_instance *mgr)
{
int rc;
if (mgr->gps.gps_open)
return;
rc = gps_open("localhost", DEFAULT_GPSD_PORT, &mgr->gps.gpsdata);
if (rc != 0) {
LOGP(DCALIB, LOGL_ERROR, "Failed to connect to GPS %d\n", rc);
calib_state_reset(mgr, CALIB_FAIL_GPSFIX);
return;
}
mgr->gps.gps_open = 1;
gps_stream(&mgr->gps.gpsdata, WATCH_ENABLE, NULL);
mgr->gps.gpsfd.data = mgr;
mgr->gps.gpsfd.cb = mgr_gps_read;
mgr->gps.gpsfd.when = BSC_FD_READ | BSC_FD_EXCEPT;
mgr->gps.gpsfd.fd = mgr->gps.gpsdata.gps_fd;
if (osmo_fd_register(&mgr->gps.gpsfd) < 0) {
LOGP(DCALIB, LOGL_ERROR, "Failed to register GPSD fd\n");
calib_state_reset(mgr, CALIB_FAIL_GPSFIX);
}
mgr->calib.state = CALIB_GPS_WAIT_FOR_FIX;
mgr->gps.fix_timeout.data = mgr;
mgr->gps.fix_timeout.cb = mgr_gps_fix_timeout;
osmo_timer_schedule(&mgr->gps.fix_timeout, 60, 0);
LOGP(DCALIB, LOGL_INFO, "Opened the GPSD connection waiting for fix: %d\n",
mgr->gps.gpsfd.fd);
}
/* OC2G CTRL interface related functions */
static void send_ctrl_cmd(struct oc2gbts_mgr_instance *mgr, struct msgb *msg)
{
ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL);
ipa_prepend_header(msg, IPAC_PROTO_OSMO);
ipa_client_conn_send(mgr->oc2gbts_ctrl.bts_conn, msg);
}
static void send_set_ctrl_cmd_int(struct oc2gbts_mgr_instance *mgr, const char *key, const int val)
{
struct msgb *msg;
int ret;
msg = msgb_alloc_headroom(1024, 128, "CTRL SET");
ret = snprintf((char *) msg->data, 4096, "SET %u %s %d",
mgr->oc2gbts_ctrl.last_seqno++, key, val);
msg->l2h = msgb_put(msg, ret);
return send_ctrl_cmd(mgr, msg);
}
static void send_set_ctrl_cmd(struct oc2gbts_mgr_instance *mgr, const char *key, const int val, const char *text)
{
struct msgb *msg;
int ret;
msg = msgb_alloc_headroom(1024, 128, "CTRL SET");
ret = snprintf((char *) msg->data, 4096, "SET %u %s %d, %s",
mgr->oc2gbts_ctrl.last_seqno++, key, val, text);
msg->l2h = msgb_put(msg, ret);
return send_ctrl_cmd(mgr, msg);
}
static void calib_start(struct oc2gbts_mgr_instance *mgr)
{
int rc;
rc = oc2gbts_clock_err_open();
if (rc != 0) {
LOGP(DCALIB, LOGL_ERROR, "Failed to open clock error module %d\n", rc);
calib_state_reset(mgr, CALIB_FAIL_CLKERR);
return;
}
rc = oc2gbts_clock_dac_open();
if (rc != 0) {
LOGP(DCALIB, LOGL_ERROR, "Failed to open OCXO dac module %d\n", rc);
calib_state_reset(mgr, CALIB_FAIL_OCXODAC);
return;
}
calib_adjust(mgr);
}
static int get_uptime(int *uptime)
{
struct sysinfo s_info;
int rc;
rc = sysinfo(&s_info);
if(!rc)
*uptime = s_info.uptime /(60 * 60);
return rc;
}
static void calib_adjust(struct oc2gbts_mgr_instance *mgr)
{
int rc;
int fault;
int error_ppt;
int accuracy_ppq;
int interval_sec;
int dac_value;
int new_dac_value;
int dac_correction;
int now = 0;
/* Get GPS time via GPSD */
mgr_gps_open(mgr);
rc = oc2gbts_clock_err_get(&fault, &error_ppt,
&accuracy_ppq, &interval_sec);
if (rc < 0) {
LOGP(DCALIB, LOGL_ERROR,
"Failed to get clock error measurement %d\n", rc);
calib_state_reset(mgr, CALIB_FAIL_CLKERR);
return;
}
/* get current up time */
rc = get_uptime(&now);
if (rc < 0)
LOGP(DTEMP, LOGL_ERROR, "Unable to read up time hours: %d (%s)\n", rc, strerror(errno));
/* read last up time */
rc = oc2gbts_par_get_uptime(tall_mgr_ctx, &mgr->gps.last_update);
if (rc < 0)
LOGP(DCALIB, LOGL_NOTICE, "Last GPS 3D fix can not read (%d). Last GPS 3D fix sets to zero\n", rc);
if (fault) {
LOGP(DCALIB, LOGL_NOTICE, "GPS has no fix, warn_flags=0x%08x, last=%d, now=%d\n",
mgr->oc2gbts_ctrl.warn_flags, mgr->gps.last_update, now);
if (now >= mgr->gps.last_update + mgr->gps.gps_fix_limit.thresh_warn_max * 24) {
if (!(mgr->oc2gbts_ctrl.warn_flags & S_MGR_GPS_FIX_WARN_ALARM)) {
mgr->oc2gbts_ctrl.warn_flags |= S_MGR_GPS_FIX_WARN_ALARM;
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_GPS_FIX_FAIL, "oc2g-oml-alert", "GPS 3D fix has been lost");
LOGP(DCALIB, LOGL_NOTICE, "GPS has no fix since the last verification, warn_flags=0x%08x, last=%d, now=%d\n",
mgr->oc2gbts_ctrl.warn_flags, mgr->gps.last_update, now);
/* schedule LED pattern for GPS fix lost */
mgr->alarms.gps_fix_lost = 1;
/* update LED pattern */
select_led_pattern(mgr);
}
} else {
/* read from last GPS 3D fix timestamp */
rc = oc2gbts_par_get_gps_fix(tall_mgr_ctx, &mgr->gps.last_gps_fix);
if (rc < 0)
LOGP(DCALIB, LOGL_NOTICE, "Last GPS 3D fix timestamp can not read (%d)\n", rc);
if (difftime(mgr->gps.gps_fix_now, mgr->gps.last_gps_fix) > mgr->gps.gps_fix_limit.thresh_warn_max * 24 * 60 * 60) {
if (!(mgr->oc2gbts_ctrl.warn_flags & S_MGR_GPS_FIX_WARN_ALARM)) {
mgr->oc2gbts_ctrl.warn_flags |= S_MGR_GPS_FIX_WARN_ALARM;
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_GPS_FIX_FAIL, "oc2g-oml-alert", "GPS 3D fix has been lost");
LOGP(DCALIB, LOGL_NOTICE, "GPS has no fix since the last known GPS fix, warn_flags=0x%08x, gps_last=%ld, gps_now=%ld\n",
mgr->oc2gbts_ctrl.warn_flags, mgr->gps.last_gps_fix, mgr->gps.gps_fix_now);
/* schedule LED pattern for GPS fix lost */
mgr->alarms.gps_fix_lost = 1;
/* update LED pattern */
select_led_pattern(mgr);
}
}
}
rc = oc2gbts_clock_err_reset();
if (rc < 0) {
LOGP(DCALIB, LOGL_ERROR,
"Failed to reset clock error module %d\n", rc);
calib_state_reset(mgr, CALIB_FAIL_CLKERR);
return;
}
calib_state_reset(mgr, CALIB_FAIL_GPSFIX);
return;
}
if (!interval_sec) {
LOGP(DCALIB, LOGL_INFO, "Skipping this iteration, no integration time\n");
calib_state_reset(mgr, CALIB_SUCCESS);
return;
}
/* We got GPS 3D fix */
LOGP(DCALIB, LOGL_DEBUG, "Got GPS 3D fix warn_flags=0x%08x, uptime_last=%d, uptime_now=%d, gps_last=%ld, gps_now=%ld\n",
mgr->oc2gbts_ctrl.warn_flags, mgr->gps.last_update, now, mgr->gps.last_gps_fix, mgr->gps.gps_fix_now);
if (mgr->oc2gbts_ctrl.warn_flags & S_MGR_GPS_FIX_WARN_ALARM) {
/* Store GPS fix as soon as we send ceased alarm */
LOGP(DCALIB, LOGL_NOTICE, "Store GPS fix as soon as we send ceased alarm last=%ld, now=%ld\n",
mgr->gps.last_gps_fix , mgr->gps.gps_fix_now);
rc = oc2gbts_par_set_gps_fix(tall_mgr_ctx, mgr->gps.gps_fix_now);
if (rc < 0)
LOGP(DCALIB, LOGL_ERROR, "Failed to store GPS 3D fix to storage %d\n", rc);
/* Store last up time */
rc = oc2gbts_par_set_uptime(tall_mgr_ctx, now);
if (rc < 0)
LOGP(DCALIB, LOGL_ERROR, "Failed to store uptime to storage %d\n", rc);
mgr->gps.last_update = now;
/* schedule LED pattern for GPS fix resume */
mgr->alarms.gps_fix_lost = 0;
/* update LED pattern */
select_led_pattern(mgr);
/* send ceased alarm if possible */
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_GPS_FIX_FAIL, "oc2g-oml-ceased", "GPS 3D fix has been lost");
mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_GPS_FIX_WARN_ALARM;
}
/* Store GPS fix at every hour */
if (now > mgr->gps.last_update) {
/* Store GPS fix every 60 minutes */
LOGP(DCALIB, LOGL_INFO, "Store GPS fix every hour last=%ld, now=%ld\n",
mgr->gps.last_gps_fix , mgr->gps.gps_fix_now);
rc = oc2gbts_par_set_gps_fix(tall_mgr_ctx, mgr->gps.gps_fix_now);
if (rc < 0)
LOGP(DCALIB, LOGL_ERROR, "Failed to store GPS 3D fix to storage %d\n", rc);
/* Store last up time every 60 minutes */
rc = oc2gbts_par_set_uptime(tall_mgr_ctx, now);
if (rc < 0)
LOGP(DCALIB, LOGL_ERROR, "Failed to store uptime to storage %d\n", rc);
/* update last uptime */
mgr->gps.last_update = now;
}
rc = oc2gbts_clock_dac_get(&dac_value);
if (rc < 0) {
LOGP(DCALIB, LOGL_ERROR,
"Failed to get OCXO dac value %d\n", rc);
calib_state_reset(mgr, CALIB_FAIL_OCXODAC);
return;
}
/* Set OCXO initial dac value */
if (ocxodac_saved_value < 0)
ocxodac_saved_value = dac_value;
LOGP(DCALIB, LOGL_INFO,
"Calibration ERR(%f PPB) ACC(%f PPB) INT(%d) DAC(%d)\n",
error_ppt / 1000., accuracy_ppq / 1000000., interval_sec, dac_value);
/* 1 unit of correction equal about 0.5 - 1 PPB correction */
dac_correction = (int)(-error_ppt * 0.0015);
new_dac_value = dac_value + dac_correction;
if (new_dac_value > 4095)
new_dac_value = 4095;
else if (new_dac_value < 0)
new_dac_value = 0;
/* We have a fix, make sure the measured error is
meaningful (10 times the accuracy) */
if ((new_dac_value != dac_value) && ((100l * abs(error_ppt)) > accuracy_ppq)) {
LOGP(DCALIB, LOGL_INFO,
"Going to apply %d as new clock setting.\n",
new_dac_value);
rc = oc2gbts_clock_dac_set(new_dac_value);
if (rc < 0) {
LOGP(DCALIB, LOGL_ERROR,
"Failed to set OCXO dac value %d\n", rc);
calib_state_reset(mgr, CALIB_FAIL_OCXODAC);
return;
}
rc = oc2gbts_clock_err_reset();
if (rc < 0) {
LOGP(DCALIB, LOGL_ERROR,
"Failed to reset clock error module %d\n", rc);
calib_state_reset(mgr, CALIB_FAIL_CLKERR);
return;
}
}
/* New conditions to store DAC value:
* - Resolution accuracy less or equal than 0.01PPB (or 10000 PPQ)
* - Error less or equal than 2PPB (or 2000PPT)
* - Solution different than the last one */
else if (accuracy_ppq <= 10000) {
if((dac_value != ocxodac_saved_value) && (abs(error_ppt) < 2000)) {
LOGP(DCALIB, LOGL_INFO, "Saving OCXO DAC value to memory... val = %d\n", dac_value);
rc = oc2gbts_clock_dac_save();
if (rc < 0) {
LOGP(DCALIB, LOGL_ERROR,
"Failed to save OCXO dac value %d\n", rc);
calib_state_reset(mgr, CALIB_FAIL_OCXODAC);
} else {
ocxodac_saved_value = dac_value;
}
}
rc = oc2gbts_clock_err_reset();
if (rc < 0) {
LOGP(DCALIB, LOGL_ERROR,
"Failed to reset clock error module %d\n", rc);
calib_state_reset(mgr, CALIB_FAIL_CLKERR);
}
}
calib_state_reset(mgr, CALIB_SUCCESS);
return;
}
static void calib_close(struct oc2gbts_mgr_instance *mgr)
{
oc2gbts_clock_err_close();
oc2gbts_clock_dac_close();
}
static void calib_state_reset(struct oc2gbts_mgr_instance *mgr, int outcome)
{
if (mgr->calib.calib_from_loop) {
/*
* In case of success calibrate in two hours again
* and in case of a failure in some minutes.
*
* TODO NTQ: Select timeout based on last error and accuracy
*/
int timeout = 60;
//int timeout = 2 * 60 * 60;
//if (outcome != CALIB_SUCESS) }
// timeout = 5 * 60;
//}
mgr->calib.calib_timeout.data = mgr;
mgr->calib.calib_timeout.cb = calib_loop_run;
osmo_timer_schedule(&mgr->calib.calib_timeout, timeout, 0);
/* TODO: do we want to notify if we got a calibration error, like no gps fix? */
oc2gbts_swd_event(mgr, SWD_CHECK_CALIB);
}
mgr->calib.state = CALIB_INITIAL;
calib_close(mgr);
}
static int calib_run(struct oc2gbts_mgr_instance *mgr, int from_loop)
{
if (mgr->calib.state != CALIB_INITIAL) {
LOGP(DCALIB, LOGL_ERROR, "Calib is already in progress.\n");
return -1;
}
/* Validates if we have a bts connection */
if (mgr->oc2gbts_ctrl.is_up) {
LOGP(DCALIB, LOGL_DEBUG, "Bts connection is up.\n");
oc2gbts_swd_event(mgr, SWD_CHECK_BTS_CONNECTION);
}
mgr->calib.calib_from_loop = from_loop;
/* From now on everything will be handled from the failure */
mgr->calib.state = CALIB_IN_PROGRESS;
calib_start(mgr);
return 0;
}
static void calib_loop_run(void *_data)
{
int rc;
struct oc2gbts_mgr_instance *mgr = _data;
LOGP(DCALIB, LOGL_INFO, "Going to calibrate the system.\n");
rc = calib_run(mgr, 1);
if (rc != 0) {
calib_state_reset(mgr, CALIB_FAIL_START);
}
}
int oc2gbts_mgr_calib_run(struct oc2gbts_mgr_instance *mgr)
{
return calib_run(mgr, 0);
}
static void schedule_bts_connect(struct oc2gbts_mgr_instance *mgr)
{
DEBUGP(DLCTRL, "Scheduling BTS connect\n");
osmo_timer_schedule(&mgr->oc2gbts_ctrl.recon_timer, 1, 0);
}
/* link to BSC has gone up or down */
static void bts_updown_cb(struct ipa_client_conn *link, int up)
{
struct oc2gbts_mgr_instance *mgr = link->data;
LOGP(DLCTRL, LOGL_INFO, "BTS connection %s\n", up ? "up" : "down");
if (up) {
mgr->oc2gbts_ctrl.is_up = 1;
mgr->oc2gbts_ctrl.last_seqno = 0;
/* handle any pending alarm */
handle_alert_actions(mgr);
handle_warn_actions(mgr);
} else {
mgr->oc2gbts_ctrl.is_up = 0;
schedule_bts_connect(mgr);
}
}
/* BTS re-connect timer call-back */
static void bts_recon_timer_cb(void *data)
{
int rc;
struct oc2gbts_mgr_instance *mgr = data;
/* update LED pattern */
select_led_pattern(mgr);
/* The connection failures are to be expected during boot */
mgr->oc2gbts_ctrl.bts_conn->ofd->when |= BSC_FD_WRITE;
rc = ipa_client_conn_open(mgr->oc2gbts_ctrl.bts_conn);
if (rc < 0) {
LOGP(DLCTRL, LOGL_NOTICE, "Failed to connect to BTS.\n");
schedule_bts_connect(mgr);
}
}
static void oc2gbts_handle_ctrl(struct oc2gbts_mgr_instance *mgr, struct msgb *msg)
{
struct ctrl_cmd *cmd = ctrl_cmd_parse(tall_mgr_ctx, msg);
int cause = atoi(cmd->reply);
if (!cmd) {
LOGP(DCALIB, LOGL_ERROR, "Failed to parse command/response\n");
return;
}
switch (cmd->type) {
case CTRL_TYPE_GET_REPLY:
LOGP(DCALIB, LOGL_INFO, "Got GET_REPLY from BTS cause=0x%x\n", cause);
break;
case CTRL_TYPE_SET_REPLY:
LOGP(DCALIB, LOGL_INFO, "Got SET_REPLY from BTS cause=0x%x\n", cause);
break;
default:
LOGP(DCALIB, LOGL_ERROR,
"Unhandled CTRL response: %d. Resetting state\n",
cmd->type);
break;
}
talloc_free(cmd);
return;
}
static int bts_read_cb(struct ipa_client_conn *link, struct msgb *msg)
{
int rc;
struct ipaccess_head *hh = (struct ipaccess_head *) msgb_l1(msg);
struct ipaccess_head_ext *hh_ext;
LOGP(DLCTRL, LOGL_DEBUG, "Received data from BTS: %s\n",
osmo_hexdump(msgb_data(msg), msgb_length(msg)));
/* regular message handling */
rc = msg_verify_ipa_structure(msg);
if (rc < 0) {
LOGP(DCALIB, LOGL_ERROR,
"Invalid IPA message from BTS (rc=%d)\n", rc);
goto err;
}
switch (hh->proto) {
case IPAC_PROTO_OSMO:
hh_ext = (struct ipaccess_head_ext *) hh->data;
switch (hh_ext->proto) {
case IPAC_PROTO_EXT_CTRL:
oc2gbts_handle_ctrl(link->data, msg);
break;
default:
LOGP(DCALIB, LOGL_NOTICE,
"Unhandled osmo ID %u from BTS\n", hh_ext->proto);
};
msgb_free(msg);
break;
default:
LOGP(DCALIB, LOGL_NOTICE,
"Unhandled stream ID %u from BTS\n", hh->proto);
msgb_free(msg);
break;
}
return 0;
err:
msgb_free(msg);
return -1;
}
int oc2gbts_mgr_calib_init(struct oc2gbts_mgr_instance *mgr)
{
int rc;
/* initialize last uptime */
mgr->gps.last_update = 0;
rc = oc2gbts_par_set_uptime(tall_mgr_ctx, mgr->gps.last_update);
if (rc < 0)
LOGP(DCALIB, LOGL_ERROR, "Failed to store uptime to storage %d\n", rc);
/* get last GPS 3D fix timestamp */
mgr->gps.last_gps_fix = 0;
rc = oc2gbts_par_get_gps_fix(tall_mgr_ctx, &mgr->gps.last_gps_fix);
if (rc < 0) {
LOGP(DCALIB, LOGL_ERROR, "Failed to get last GPS 3D fix timestamp from storage. Create it anyway %d\n", rc);
rc = oc2gbts_par_set_gps_fix(tall_mgr_ctx, mgr->gps.last_gps_fix);
if (rc < 0)
LOGP(DCALIB, LOGL_ERROR, "Failed to store initial GPS fix to storage %d\n", rc);
}
mgr->calib.state = CALIB_INITIAL;
mgr->calib.calib_timeout.data = mgr;
mgr->calib.calib_timeout.cb = calib_loop_run;
osmo_timer_schedule(&mgr->calib.calib_timeout, 0, 0);
return rc;
}
int oc2gbts_mgr_control_init(struct oc2gbts_mgr_instance *mgr)
{
mgr->oc2gbts_ctrl.bts_conn = ipa_client_conn_create(tall_mgr_ctx, NULL, 0,
"127.0.0.1", OSMO_CTRL_PORT_BTS,
bts_updown_cb, bts_read_cb,
NULL, mgr);
if (!mgr->oc2gbts_ctrl.bts_conn) {
LOGP(DLCTRL, LOGL_ERROR, "Failed to create IPA connection to BTS\n");
return -1;
}
mgr->oc2gbts_ctrl.recon_timer.cb = bts_recon_timer_cb;
mgr->oc2gbts_ctrl.recon_timer.data = mgr;
schedule_bts_connect(mgr);
return 0;
}
void oc2gbts_mgr_dispatch_alarm(struct oc2gbts_mgr_instance *mgr, const int cause, const char *key, const char *text)
{
/* Make sure the control link is ready before sending alarm */
if (mgr->oc2gbts_ctrl.bts_conn->state != IPA_CLIENT_LINK_STATE_CONNECTED) {
LOGP(DLCTRL, LOGL_NOTICE, "MGR losts connection to BTS.\n");
LOGP(DLCTRL, LOGL_NOTICE, "MGR drops an alert cause=0x%x, text=%s to BTS\n", cause, text);
return;
}
LOGP(DLCTRL, LOGL_DEBUG, "MGR sends an alert cause=0x%x, text=%s to BTS\n", cause, text);
send_set_ctrl_cmd(mgr, key, cause, text);
return;
}

View File

@ -0,0 +1,208 @@
/* NetworkListen for NuRAN OC-2G BTS management daemon */
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* Based on sysmoBTS:
* sysmobts_mgr_nl.c
* (C) 2014 by Holger Hans Peter Freyther
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "misc/oc2gbts_mgr.h"
#include "misc/oc2gbts_misc.h"
#include "misc/oc2gbts_nl.h"
#include "misc/oc2gbts_par.h"
#include "misc/oc2gbts_bid.h"
#include <osmo-bts/logging.h>
#include <osmocom/gsm/protocol/ipaccess.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/select.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#define ETH0_ADDR_SYSFS "/var/oc2g/net/eth0/address"
static struct osmo_fd nl_fd;
/*
* The TLV structure in IPA messages in UDP packages is a bit
* weird. First the header appears to have an extra NULL byte
* and second the L16 of the L16TV needs to include +1 for the
* tag. The default msgb/tlv and libosmo-abis routines do not
* provide this.
*/
static void ipaccess_prepend_header_quirk(struct msgb *msg, int proto)
{
struct ipaccess_head *hh;
/* prepend the ip.access header */
hh = (struct ipaccess_head *) msgb_push(msg, sizeof(*hh) + 1);
hh->len = htons(msg->len - sizeof(*hh) - 1);
hh->proto = proto;
}
static void quirk_l16tv_put(struct msgb *msg, uint16_t len, uint8_t tag,
const uint8_t *val)
{
uint8_t *buf = msgb_put(msg, len + 2 + 1);
*buf++ = (len + 1) >> 8;
*buf++ = (len + 1) & 0xff;
*buf++ = tag;
memcpy(buf, val, len);
}
/*
* We don't look at the content of the request yet and lie
* about most of the responses.
*/
static void respond_to(struct sockaddr_in *src, struct osmo_fd *fd,
uint8_t *data, size_t len)
{
static int fetched_info = 0;
static char mac_str[20] = {0, };
static char model_name[64] = {0, };
static char ser_str[20] = {0, };
struct sockaddr_in loc_addr;
int rc;
char loc_ip[INET_ADDRSTRLEN];
struct msgb *msg = msgb_alloc_headroom(512, 128, "ipa get response");
if (!msg) {
LOGP(DFIND, LOGL_ERROR, "Failed to allocate msgb\n");
return;
}
if (!fetched_info) {
int fd_eth;
int serno;
int model;
char rev_maj, rev_min;
/* fetch the MAC */
fd_eth = open(ETH0_ADDR_SYSFS, O_RDONLY);
if (fd_eth >= 0) {
read(fd_eth, mac_str, sizeof(mac_str)-1);
mac_str[sizeof(mac_str)-1] = '\0';
close(fd_eth);
}
/* fetch the serial number */
oc2gbts_par_get_int(OC2GBTS_PAR_SERNR, &serno);
snprintf(ser_str, sizeof(ser_str), "%d", serno);
/* fetch the model and trx number */
snprintf(model_name, sizeof(model_name), "OC-2G BTS");
oc2gbts_rev_get(&rev_maj, &rev_min);
snprintf(model_name, sizeof(model_name), "%s Rev %c.%c",
model_name, rev_maj, rev_min);
model = oc2gbts_model_get();
if (model >= 0) {
snprintf(model_name, sizeof(model_name), "%s (%05X)",
model_name, model);
}
fetched_info = 1;
}
if (source_for_dest(&src->sin_addr, &loc_addr.sin_addr) != 0) {
LOGP(DFIND, LOGL_ERROR, "Failed to determine local source\n");
return;
}
msgb_put_u8(msg, IPAC_MSGT_ID_RESP);
/* append MAC addr */
quirk_l16tv_put(msg, strlen(mac_str) + 1, IPAC_IDTAG_MACADDR, (uint8_t *) mac_str);
/* append ip address */
inet_ntop(AF_INET, &loc_addr.sin_addr, loc_ip, sizeof(loc_ip));
quirk_l16tv_put(msg, strlen(loc_ip) + 1, IPAC_IDTAG_IPADDR, (uint8_t *) loc_ip);
/* append the serial number */
quirk_l16tv_put(msg, strlen(ser_str) + 1, IPAC_IDTAG_SERNR, (uint8_t *) ser_str);
/* abuse some flags */
quirk_l16tv_put(msg, strlen(model_name) + 1, IPAC_IDTAG_UNIT, (uint8_t *) model_name);
/* ip.access nanoBTS would reply to port==3006 */
ipaccess_prepend_header_quirk(msg, IPAC_PROTO_IPACCESS);
rc = sendto(fd->fd, msg->data, msg->len, 0, (struct sockaddr *)src, sizeof(*src));
if (rc != msg->len)
LOGP(DFIND, LOGL_ERROR,
"Failed to send with rc(%d) errno(%d)\n", rc, errno);
}
static int ipaccess_bcast(struct osmo_fd *fd, unsigned int what)
{
uint8_t data[2048];
char src[INET_ADDRSTRLEN];
struct sockaddr_in addr = {};
socklen_t len = sizeof(addr);
int rc;
rc = recvfrom(fd->fd, data, sizeof(data), 0,
(struct sockaddr *) &addr, &len);
if (rc <= 0) {
LOGP(DFIND, LOGL_ERROR,
"Failed to read from socket errno(%d)\n", errno);
return -1;
}
LOGP(DFIND, LOGL_DEBUG,
"Received request from: %s size %d\n",
inet_ntop(AF_INET, &addr.sin_addr, src, sizeof(src)), rc);
if (rc < 6)
return 0;
if (data[2] != IPAC_PROTO_IPACCESS || data[4] != IPAC_MSGT_ID_GET)
return 0;
respond_to(&addr, fd, data + 6, rc - 6);
return 0;
}
int oc2gbts_mgr_nl_init(void)
{
int rc;
nl_fd.cb = ipaccess_bcast;
rc = osmo_sock_init_ofd(&nl_fd, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
"0.0.0.0", 3006, OSMO_SOCK_F_BIND);
if (rc < 0) {
perror("Socket creation");
return -1;
}
return 0;
}

View File

@ -0,0 +1,980 @@
/* Temperature control for NuRAN OC-2G BTS management daemon */
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* Based on sysmoBTS:
* sysmobts_mgr_temp.c
* (C) 2014 by Holger Hans Peter Freyther
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <inttypes.h>
#include "misc/oc2gbts_mgr.h"
#include "misc/oc2gbts_misc.h"
#include "misc/oc2gbts_temp.h"
#include "misc/oc2gbts_power.h"
#include "misc/oc2gbts_led.h"
#include "misc/oc2gbts_swd.h"
#include "misc/oc2gbts_bid.h"
#include "limits.h"
#include <osmo-bts/logging.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/linuxlist.h>
struct oc2gbts_mgr_instance *s_mgr;
static struct osmo_timer_list sensor_ctrl_timer;
static const struct value_string state_names[] = {
{ STATE_NORMAL, "NORMAL" },
{ STATE_WARNING_HYST, "WARNING (HYST)" },
{ STATE_WARNING, "WARNING" },
{ STATE_CRITICAL, "CRITICAL" },
{ 0, NULL }
};
/* private function prototype */
static void sensor_ctrl_check(struct oc2gbts_mgr_instance *mgr);
const char *oc2gbts_mgr_sensor_get_state(enum oc2gbts_sensor_state state)
{
return get_value_string(state_names, state);
}
static int next_state(enum oc2gbts_sensor_state current_state, int critical, int warning)
{
int next_state = -1;
switch (current_state) {
case STATE_NORMAL:
if (critical)
next_state = STATE_CRITICAL;
else if (warning)
next_state = STATE_WARNING;
break;
case STATE_WARNING_HYST:
if (critical)
next_state = STATE_CRITICAL;
else if (warning)
next_state = STATE_WARNING;
else
next_state = STATE_NORMAL;
break;
case STATE_WARNING:
if (critical)
next_state = STATE_CRITICAL;
else if (!warning)
next_state = STATE_WARNING_HYST;
break;
case STATE_CRITICAL:
if (!critical && !warning)
next_state = STATE_WARNING;
break;
};
return next_state;
}
static void handle_normal_actions(int actions)
{
/* switch on the PA */
if (actions & SENSOR_ACT_NORM_PA_ON) {
if (oc2gbts_power_set(OC2GBTS_POWER_PA, 1) != 0) {
LOGP(DTEMP, LOGL_ERROR,
"Failed to switch on the PA\n");
} else {
LOGP(DTEMP, LOGL_INFO,
"Switched on the PA as normal action.\n");
}
}
if (actions & SENSOR_ACT_NORM_BTS_SRV_ON) {
LOGP(DTEMP, LOGL_INFO,
"Going to switch on the BTS service\n");
/*
* TODO: use/create something like nspawn that serializes
* and used SIGCHLD/waitpid to pick up the dead processes
* without invoking shell.
*/
system("/bin/systemctl start osmo-bts.service");
}
}
static void handle_actions(int actions)
{
/* switch off the PA */
if (actions & SENSOR_ACT_PA_OFF) {
if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
if (oc2gbts_power_set(OC2GBTS_POWER_PA, 0) != 0) {
LOGP(DTEMP, LOGL_ERROR,
"Failed to switch off the PA. Stop BTS?\n");
} else {
LOGP(DTEMP, LOGL_NOTICE,
"Switched off the PA due temperature.\n");
}
}
}
if (actions & SENSOR_ACT_BTS_SRV_OFF) {
LOGP(DTEMP, LOGL_NOTICE,
"Going to switch off the BTS service\n");
/*
* TODO: use/create something like nspawn that serializes
* and used SIGCHLD/waitpid to pick up the dead processes
* without invoking shell.
*/
system("/bin/systemctl stop osmo-bts.service");
}
}
void handle_ceased_actions(struct oc2gbts_mgr_instance *mgr)
{ int i;
uint32_t cause;
if (!mgr->oc2gbts_ctrl.is_up)
return;
LOGP(DTEMP, LOGL_DEBUG, "handle_ceased_actions in state %s, warn_flags=0x%x, crit_flags=0x%x\n",
oc2gbts_mgr_sensor_get_state(mgr->state.state),
mgr->oc2gbts_ctrl.warn_flags,
mgr->oc2gbts_ctrl.crit_flags);
for (i = 0; i < 32; i++) {
cause = 1 << i;
/* clear warning flag without sending ceased alarm */
if (mgr->oc2gbts_ctrl.warn_flags & cause)
mgr->oc2gbts_ctrl.warn_flags &= ~cause;
/* clear warning flag with sending ceased alarm */
if (mgr->oc2gbts_ctrl.crit_flags & cause) {
/* clear associated flag */
mgr->oc2gbts_ctrl.crit_flags &= ~cause;
/* dispatch ceased alarm */
switch (cause) {
case S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_SUPPLY_MAX_FAIL, "oc2g-oml-ceased", "Main power supply temperature is too high");
break;
case S_MGR_TEMP_SOC_CRIT_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_SOC_MAX_FAIL, "oc2g-oml-ceased", "SoC temperature is too high");
break;
case S_MGR_TEMP_FPGA_CRIT_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_FPGA_MAX_FAIL, "oc2g-oml-ceased", "FPGA temperature is too high");
break;
case S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_RMS_DET_MAX_FAIL, "oc2g-oml-ceased", "RMS detector temperature is too high");
break;
case S_MGR_TEMP_OCXO_CRIT_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_OCXO_MAX_FAIL, "oc2g-oml-ceased", "OCXO temperature is too high");
break;
case S_MGR_TEMP_TRX_CRIT_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_TRX_MAX_FAIL, "oc2g-oml-ceased", "TRX temperature is too high");
break;
case S_MGR_TEMP_PA_CRIT_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_PA_MAX_FAIL, "oc2g-oml-ceased", "PA temperature is too high");
break;
case S_MGR_SUPPLY_CRIT_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_SUPPLY_MAX_FAIL, "oc2g-oml-ceased", "Power supply voltage is too high");
break;
case S_MGR_SUPPLY_CRIT_MIN_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_SUPPLY_MIN_FAIL, "oc2g-oml-ceased", "Power supply voltage is too low");
break;
case S_MGR_VSWR_CRIT_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_VSWR_MAX_FAIL, "oc2g-oml-ceased", "VSWR is too high");
break;
case S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_PWR_SUPPLY_MAX_FAIL, "oc2g-oml-ceased", "Power supply consumption is too high");
break;
case S_MGR_PWR_PA_CRIT_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_PWR_PA_MAX_FAIL, "oc2g-oml-ceased", "PA power consumption is too high");
break;
default:
break;
}
}
}
return;
}
void handle_alert_actions(struct oc2gbts_mgr_instance *mgr)
{ int i;
uint32_t cause;
if (!mgr->oc2gbts_ctrl.is_up)
return;
LOGP(DTEMP, LOGL_DEBUG, "handle_alert_actions in state %s, crit_flags=0x%x\n",
oc2gbts_mgr_sensor_get_state(mgr->state.state),
mgr->oc2gbts_ctrl.crit_flags);
for (i = 0; i < 32; i++) {
cause = 1 << i;
if (mgr->oc2gbts_ctrl.crit_flags & cause) {
switch(cause) {
case S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_SUPPLY_MAX_FAIL, "oc2g-oml-alert", "Main power supply temperature is too high");
break;
case S_MGR_TEMP_SOC_CRIT_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_SOC_MAX_FAIL, "oc2g-oml-alert", "SoC temperature is too high");
break;
case S_MGR_TEMP_FPGA_CRIT_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_FPGA_MAX_FAIL, "oc2g-oml-alert", "FPGA temperature is too high");
break;
case S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_RMS_DET_MAX_FAIL, "oc2g-oml-alert", "RMS detector temperature is too high");
break;
case S_MGR_TEMP_OCXO_CRIT_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_OCXO_MAX_FAIL, "oc2g-oml-alert", "OCXO temperature is too high");
break;
case S_MGR_TEMP_TRX_CRIT_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_TRX_MAX_FAIL, "oc2g-oml-alert", "TRX temperature is too high");
break;
case S_MGR_TEMP_PA_CRIT_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_PA_MAX_FAIL, "oc2g-oml-alert", "PA temperature is too high");
break;
case S_MGR_SUPPLY_CRIT_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_SUPPLY_MAX_FAIL, "oc2g-oml-alert", "Power supply voltage is too high");
break;
case S_MGR_SUPPLY_CRIT_MIN_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_SUPPLY_MIN_FAIL, "oc2g-oml-alert", "Power supply voltage is too low");
break;
case S_MGR_VSWR_CRIT_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_VSWR_MAX_FAIL, "oc2g-oml-alert", "VSWR is too high");
break;
case S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_PWR_SUPPLY_MAX_FAIL, "oc2g-oml-alert", "Power supply consumption is too high");
break;
case S_MGR_PWR_PA_CRIT_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_PWR_PA_MAX_FAIL, "oc2g-oml-alert", "PA power consumption is too high");
break;
default:
break;
}
}
}
return;
}
void handle_warn_actions(struct oc2gbts_mgr_instance *mgr)
{ int i;
uint32_t cause;
if (!mgr->oc2gbts_ctrl.is_up)
return;
LOGP(DTEMP, LOGL_DEBUG, "handle_warn_actions in state %s, warn_flags=0x%x\n",
oc2gbts_mgr_sensor_get_state(mgr->state.state),
mgr->oc2gbts_ctrl.warn_flags);
for (i = 0; i < 32; i++) {
cause = 1 << i;
if (mgr->oc2gbts_ctrl.warn_flags & cause) {
switch(cause) {
case S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_SUPPLY_HIGH_FAIL, "oc2g-oml-alert", "Main power supply temperature is high");
break;
case S_MGR_TEMP_SUPPLY_WARN_MIN_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_SUPPLY_LOW_FAIL, "oc2g-oml-alert", "Main power supply temperature is low");
break;
case S_MGR_TEMP_SOC_WARN_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_SOC_HIGH_FAIL, "oc2g-oml-alert", "SoC temperature is high");
break;
case S_MGR_TEMP_SOC_WARN_MIN_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_SOC_LOW_FAIL, "oc2g-oml-alert", "SoC temperature is low");
break;
case S_MGR_TEMP_FPGA_WARN_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_FPGA_HIGH_FAIL, "oc2g-oml-alert", "FPGA temperature is high");
break;
case S_MGR_TEMP_FPGA_WARN_MIN_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_FPGA_LOW_FAIL, "oc2g-oml-alert", "FPGA temperature is low");
break;
case S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_RMS_DET_HIGH_FAIL, "oc2g-oml-alert", "RMS detector temperature is high");
break;
case S_MGR_TEMP_RMS_DET_WARN_MIN_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_RMS_DET_LOW_FAIL, "oc2g-oml-alert", "RMS detector temperature is low");
break;
case S_MGR_TEMP_OCXO_WARN_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_OCXO_HIGH_FAIL, "oc2g-oml-alert", "OCXO temperature is high");
break;
case S_MGR_TEMP_OCXO_WARN_MIN_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_OCXO_LOW_FAIL, "oc2g-oml-alert", "OCXO temperature is low");
break;
case S_MGR_TEMP_TRX_WARN_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_TRX_HIGH_FAIL, "oc2g-oml-alert", "TRX temperature is high");
break;
case S_MGR_TEMP_TRX_WARN_MIN_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_TRX_LOW_FAIL, "oc2g-oml-alert", "TRX temperature is low");
break;
case S_MGR_TEMP_PA_WARN_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_PA_HIGH_FAIL, "oc2g-oml-alert", "PA temperature is high");
break;
case S_MGR_TEMP_PA_WARN_MIN_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_PA_LOW_FAIL, "oc2g-oml-alert", "PA temperature is low");
break;
case S_MGR_SUPPLY_WARN_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_SUPPLY_HIGH_FAIL, "oc2g-oml-alert", "Power supply voltage is high");
break;
case S_MGR_SUPPLY_WARN_MIN_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_SUPPLY_LOW_FAIL, "oc2g-oml-alert", "Power supply voltage is low");
break;
case S_MGR_VSWR_WARN_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_VSWR_HIGH_FAIL, "oc2g-oml-alert", "VSWR is high");
break;
case S_MGR_PWR_SUPPLY_WARN_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_PWR_SUPPLY_HIGH_FAIL, "oc2g-oml-alert", "Power supply consumption is high");
break;
case S_MGR_PWR_PA_WARN_MAX_ALARM:
oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_PWR_PA_HIGH_FAIL, "oc2g-oml-alert", "PA power consumption is high");
break;
default:
break;
}
}
}
return;
}
/**
* Go back to normal! Depending on the configuration execute the normal
* actions that could (start to) undo everything we did in the other
* states. What is still missing is the power increase/decrease depending
* on the state. E.g. starting from WARNING_HYST we might want to slowly
* ramp up the output power again.
*/
static void execute_normal_act(struct oc2gbts_mgr_instance *manager)
{
LOGP(DTEMP, LOGL_NOTICE, "System is back to normal state.\n");
handle_ceased_actions(manager);
handle_normal_actions(manager->state.action_norm);
}
static void execute_warning_act(struct oc2gbts_mgr_instance *manager)
{
LOGP(DTEMP, LOGL_NOTICE, "System has reached warning state.\n");
handle_warn_actions(manager);
handle_actions(manager->state.action_warn);
}
/* Preventive timer call-back */
static void preventive_timer_cb(void *_data)
{
struct oc2gbts_mgr_instance *mgr = _data;
/* Delete current preventive timer if possible */
osmo_timer_del(&mgr->alarms.preventive_timer);
LOGP(DTEMP, LOGL_DEBUG, "Preventive timer expired in %d sec, retry=%d\n",
mgr->alarms.preventive_duration,
mgr->alarms.preventive_retry);
/* Turn on PA and clear action flag */
if (mgr->state.action_comb & SENSOR_ACT_PA_OFF) {
mgr->state.action_comb &= ~SENSOR_ACT_PA_OFF;
if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
if (oc2gbts_power_set(OC2GBTS_POWER_PA, 1))
LOGP(DTEMP, LOGL_ERROR, "Failed to switch on the PA\n");
else
LOGP(DTEMP, LOGL_DEBUG, "Re-enable PA after preventive timer expired in %d sec\n",
mgr->alarms.preventive_duration);
}
}
/* restart check sensor timer */
osmo_timer_del(&sensor_ctrl_timer);
osmo_timer_schedule(&sensor_ctrl_timer, OC2GBTS_SENSOR_TIMER_DURATION, 0);
return;
}
static void execute_preventive_act(struct oc2gbts_mgr_instance *manager)
{
struct oc2gbts_preventive_list *prevent_list, *prevent_list2;
/* update LED pattern */
select_led_pattern(manager);
/* do nothing if the preventive action list is empty */
if (llist_empty(&manager->alarms.list))
return;
llist_for_each_entry_safe(prevent_list, prevent_list2, &manager->alarms.list, list) {
/* Delete the timer in list and perform action*/
if (prevent_list) {
/* Delete current preventive timer if possible */
osmo_timer_del(&manager->alarms.preventive_timer);
/* Start/restart preventive timer */
if (prevent_list->param.sleep_sec) {
manager->alarms.preventive_timer.cb = preventive_timer_cb;
manager->alarms.preventive_timer.data = manager;
osmo_timer_schedule(&manager->alarms.preventive_timer, prevent_list->param.sleep_sec, 0);
LOGP(DTEMP, LOGL_DEBUG,"Preventive timer scheduled for %d sec, preventive flags=0x%x\n",
prevent_list->param.sleep_sec,
prevent_list->action_flag);
}
/* Update active flags */
manager->state.action_comb |= prevent_list->action_flag;
/* Turn off PA */
if (manager->state.action_comb & SENSOR_ACT_PA_OFF) {
if (oc2gbts_power_set(OC2GBTS_POWER_PA, 0))
LOGP(DTEMP, LOGL_ERROR, "Failed to switch off the PA\n");
}
/* Delete this preventive entry */
llist_del(&prevent_list->list);
talloc_free(prevent_list);
LOGP(DTEMP, LOGL_DEBUG,"Deleted preventive entry from list, entries left=%d\n",
llist_count(&manager->alarms.list));
/* stay in last state is preventive active has exceed maximum number of retries */
if (manager->alarms.preventive_retry > OC2GBTS_PREVENT_RETRY)
LOGP(DTEMP, LOGL_NOTICE, "Maximum number of preventive active exceed\n");
else
/* increase retry counter */
manager->alarms.preventive_retry++;
}
}
return;
}
static void execute_critical_act(struct oc2gbts_mgr_instance *manager)
{
LOGP(DTEMP, LOGL_NOTICE, "System has reached critical warning.\n");
handle_alert_actions(manager);
handle_actions(manager->state.action_crit);
}
static void oc2gbts_mgr_sensor_handle(struct oc2gbts_mgr_instance *manager,
int critical, int warning)
{
int new_state = next_state(manager->state.state, critical, warning);
/* run preventive action if it is possible */
execute_preventive_act(manager);
/* Nothing changed */
if (new_state < 0)
return;
LOGP(DTEMP, LOGL_INFO, "Moving from state %s to %s.\n",
get_value_string(state_names, manager->state.state),
get_value_string(state_names, new_state));
manager->state.state = new_state;
switch (manager->state.state) {
case STATE_NORMAL:
execute_normal_act(manager);
/* reset alarms */
manager->alarms.temp_high = 0;
manager->alarms.temp_max = 0;
manager->alarms.vswr_high = 0;
manager->alarms.vswr_max = 0;
manager->alarms.supply_low = 0;
manager->alarms.supply_min = 0;
manager->alarms.supply_pwr_high = 0;
manager->alarms.supply_pwr_max = 0;
manager->alarms.pa_pwr_max = 0;
manager->alarms.pa_pwr_high = 0;
manager->state.action_comb = 0;
manager->alarms.preventive_retry = 0;
/* update LED pattern */
select_led_pattern(manager);
break;
case STATE_WARNING_HYST:
/* do nothing? Maybe start to increase transmit power? */
break;
case STATE_WARNING:
execute_warning_act(manager);
/* update LED pattern */
select_led_pattern(manager);
break;
case STATE_CRITICAL:
execute_critical_act(manager);
/* update LED pattern */
select_led_pattern(manager);
break;
};
}
static void schedule_preventive_action(struct oc2gbts_mgr_instance *mgr, int action, int duration)
{
struct oc2gbts_preventive_list *prevent_list;
/* add to pending list */
prevent_list = talloc_zero(tall_mgr_ctx, struct oc2gbts_preventive_list);
if (prevent_list) {
prevent_list->action_flag = action;
prevent_list->param.sleep_sec = duration;
prevent_list->param.sleep_usec = 0;
llist_add_tail(&prevent_list->list, &mgr->alarms.list);
LOGP(DTEMP, LOGL_DEBUG,"Added preventive action to list, duration=%d sec, total entries=%d\n",
prevent_list->param.sleep_sec,
llist_count(&mgr->alarms.list));
}
return;
}
static void sensor_ctrl_check(struct oc2gbts_mgr_instance *mgr)
{
int rc;
int temp, volt, vswr, power = 0;
int warn_thresh_passed = 0;
int crit_thresh_passed = 0;
int action = 0;
LOGP(DTEMP, LOGL_INFO, "Going to check the temperature.\n");
/* Read the current supply temperature */
rc = oc2gbts_temp_get(OC2GBTS_TEMP_SUPPLY, &temp);
if (rc < 0) {
LOGP(DTEMP, LOGL_NOTICE,
"Failed to read the supply temperature. rc=%d\n", rc);
warn_thresh_passed = crit_thresh_passed = 1;
} else {
temp = temp / 1000;
if (temp > mgr->temp.supply_temp_limit.thresh_warn_max) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because supply temperature is over %d\n", mgr->temp.supply_temp_limit.thresh_warn_max);
warn_thresh_passed = 1;
mgr->alarms.temp_high = 1;
mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM;
/* add to pending list */
schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
}
if (temp < mgr->temp.supply_temp_limit.thresh_warn_min){
LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because supply temperature is under %d\n", mgr->temp.supply_temp_limit.thresh_warn_min);
warn_thresh_passed = 1;
mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_SUPPLY_WARN_MIN_ALARM;
}
if (temp > mgr->temp.supply_temp_limit.thresh_crit_max) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because supply temperature is over %d\n", mgr->temp.supply_temp_limit.thresh_crit_max);
crit_thresh_passed = 1;
mgr->alarms.temp_max = 1;
mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM;
mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM;
action = SENSOR_ACT_PA_OFF;
/* add to pending list */
schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
}
LOGP(DTEMP, LOGL_INFO, "Supply temperature is: %d\n", temp);
}
/* Read the current SoC temperature */
rc = oc2gbts_temp_get(OC2GBTS_TEMP_SOC, &temp);
if (rc < 0) {
LOGP(DTEMP, LOGL_NOTICE,
"Failed to read the SoC temperature. rc=%d\n", rc);
warn_thresh_passed = crit_thresh_passed = 1;
} else {
temp = temp / 1000;
if (temp > mgr->temp.soc_temp_limit.thresh_warn_max) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because SoC temperature is over %d\n", mgr->temp.soc_temp_limit.thresh_warn_max);
warn_thresh_passed = 1;
mgr->alarms.temp_high = 1;
mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_SOC_WARN_MAX_ALARM;
/* add to pending list */
schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
}
if (temp < mgr->temp.soc_temp_limit.thresh_warn_min){
LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because SoC temperature is under %d\n", mgr->temp.soc_temp_limit.thresh_warn_min);
warn_thresh_passed = 1;
mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_SOC_WARN_MIN_ALARM;
}
if (temp > mgr->temp.soc_temp_limit.thresh_crit_max) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because SoC temperature is over %d\n", mgr->temp.soc_temp_limit.thresh_crit_max);
crit_thresh_passed = 1;
mgr->alarms.temp_max = 1;
mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_SOC_CRIT_MAX_ALARM;
mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_SOC_WARN_MAX_ALARM;
action = SENSOR_ACT_PA_OFF;
/* add to pending list */
schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
}
LOGP(DTEMP, LOGL_INFO, "SoC temperature is: %d\n", temp);
}
/* Read the current fpga temperature */
rc = oc2gbts_temp_get(OC2GBTS_TEMP_FPGA, &temp);
if (rc < 0) {
LOGP(DTEMP, LOGL_NOTICE,
"Failed to read the fpga temperature. rc=%d\n", rc);
warn_thresh_passed = crit_thresh_passed = 1;
} else {
temp = temp / 1000;
if (temp > mgr->temp.fpga_temp_limit.thresh_warn_max) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because fpga temperature is over %d\n", mgr->temp.fpga_temp_limit.thresh_warn_max);
warn_thresh_passed = 1;
mgr->alarms.temp_high = 1;
mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_FPGA_WARN_MAX_ALARM;
/* add to pending list */
schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
}
if (temp < mgr->temp.fpga_temp_limit.thresh_warn_min) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because fpga temperature is under %d\n", mgr->temp.fpga_temp_limit.thresh_warn_min);
warn_thresh_passed = 1;
mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_FPGA_WARN_MIN_ALARM;
}
if (temp > mgr->temp.fpga_temp_limit.thresh_crit_max) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because fpga temperature is over %d\n", mgr->temp.fpga_temp_limit.thresh_crit_max);
crit_thresh_passed = 1;
mgr->alarms.temp_max = 1;
mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_FPGA_CRIT_MAX_ALARM;
mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_FPGA_WARN_MAX_ALARM;
action = SENSOR_ACT_PA_OFF;
/* add to pending list */
schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
}
LOGP(DTEMP, LOGL_INFO, "FPGA temperature is: %d\n", temp);
}
if (oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) || oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
/* Read the current RMS detector temperature */
rc = oc2gbts_temp_get(OC2GBTS_TEMP_RMSDET, &temp);
if (rc < 0) {
LOGP(DTEMP, LOGL_NOTICE,
"Failed to read the RMS detector temperature. rc=%d\n", rc);
warn_thresh_passed = crit_thresh_passed = 1;
} else {
temp = temp / 1000;
if (temp > mgr->temp.rmsdet_temp_limit.thresh_warn_max) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because RMS detector temperature is over %d\n", mgr->temp.rmsdet_temp_limit.thresh_warn_max);
warn_thresh_passed = 1;
mgr->alarms.temp_high = 1;
mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM;
/* add to pending list */
schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
}
if (temp < mgr->temp.rmsdet_temp_limit.thresh_warn_min) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because RMS detector temperature is under %d\n", mgr->temp.rmsdet_temp_limit.thresh_warn_min);
warn_thresh_passed = 1;
mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_RMS_DET_WARN_MIN_ALARM;
}
if (temp > mgr->temp.rmsdet_temp_limit.thresh_crit_max) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because RMS detector temperature is over %d\n", mgr->temp.rmsdet_temp_limit.thresh_crit_max);
crit_thresh_passed = 1;
mgr->alarms.temp_max = 1;
mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM;
mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM;
action = SENSOR_ACT_PA_OFF;
/* add to pending list */
schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
}
LOGP(DTEMP, LOGL_INFO, "RMS detector temperature is: %d\n", temp);
}
}
/* Read the current OCXO temperature */
rc = oc2gbts_temp_get(OC2GBTS_TEMP_OCXO, &temp);
if (rc < 0) {
LOGP(DTEMP, LOGL_NOTICE,
"Failed to read the OCXO temperature. rc=%d\n", rc);
warn_thresh_passed = crit_thresh_passed = 1;
} else {
temp = temp / 1000;
if (temp > mgr->temp.ocxo_temp_limit.thresh_warn_max) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because OCXO temperature is over %d\n", mgr->temp.ocxo_temp_limit.thresh_warn_max);
warn_thresh_passed = 1;
mgr->alarms.temp_high = 1;
mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_OCXO_WARN_MAX_ALARM;
/* add to pending list */
schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
}
if (temp < mgr->temp.ocxo_temp_limit.thresh_warn_min) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because OCXO temperature is under %d\n", mgr->temp.ocxo_temp_limit.thresh_warn_min);
warn_thresh_passed = 1;
mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_OCXO_WARN_MIN_ALARM;
}
if (temp > mgr->temp.ocxo_temp_limit.thresh_crit_max) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because OCXO temperature is over %d\n", mgr->temp.ocxo_temp_limit.thresh_crit_max);
crit_thresh_passed = 1;
mgr->alarms.temp_max = 1;
mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_OCXO_CRIT_MAX_ALARM;
mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_OCXO_WARN_MAX_ALARM;
action = SENSOR_ACT_PA_OFF;
/* add to pending list */
schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
}
LOGP(DTEMP, LOGL_INFO, "OCXO temperature is: %d\n", temp);
}
/* Read the current TX temperature */
rc = oc2gbts_temp_get(OC2GBTS_TEMP_TX, &temp);
if (rc < 0) {
LOGP(DTEMP, LOGL_NOTICE,
"Failed to read the TX temperature. rc=%d\n", rc);
warn_thresh_passed = crit_thresh_passed = 1;
} else {
temp = temp / 1000;
if (temp > mgr->temp.tx_temp_limit.thresh_warn_max) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because TX temperature is over %d\n", mgr->temp.tx_temp_limit.thresh_warn_max);
warn_thresh_passed = 1;
mgr->alarms.temp_high = 1;
mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_TRX_WARN_MAX_ALARM;
/* add to pending list */
schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
}
if (temp < mgr->temp.tx_temp_limit.thresh_warn_min) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because TX temperature is under %d\n", mgr->temp.tx_temp_limit.thresh_warn_min);
warn_thresh_passed = 1;
mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_TRX_WARN_MIN_ALARM;
}
if (temp > mgr->temp.tx_temp_limit.thresh_crit_max) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because TX temperature is over %d\n", mgr->temp.tx_temp_limit.thresh_crit_max);
crit_thresh_passed = 1;
mgr->alarms.temp_max = 1;
mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_TRX_CRIT_MAX_ALARM;
mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_TRX_WARN_MAX_ALARM;
action = SENSOR_ACT_PA_OFF;
/* add to pending list */
schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
}
LOGP(DTEMP, LOGL_INFO, "TX temperature is: %d\n", temp);
}
if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) {
/* Read the current PA temperature */
rc = oc2gbts_temp_get(OC2GBTS_TEMP_PA, &temp);
if (rc < 0) {
LOGP(DTEMP, LOGL_NOTICE,
"Failed to read the PA temperature. rc=%d\n", rc);
warn_thresh_passed = crit_thresh_passed = 1;
} else {
temp = temp / 1000;
if (temp > mgr->temp.pa_temp_limit.thresh_warn_max) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because PA temperature because is over %d\n", mgr->temp.pa_temp_limit.thresh_warn_max);
warn_thresh_passed = 1;
mgr->alarms.temp_high = 1;
mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_PA_WARN_MAX_ALARM;
action = SENSOR_ACT_PA_OFF;
/* add to pending list */
schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
}
if (temp < mgr->temp.pa_temp_limit.thresh_warn_min) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because PA temperature because is under %d\n", mgr->temp.pa_temp_limit.thresh_warn_min);
warn_thresh_passed = 1;
mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_PA_WARN_MIN_ALARM;
}
if (temp > mgr->temp.pa_temp_limit.thresh_crit_max) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because PA temperature because is over %d\n", mgr->temp.pa_temp_limit.thresh_crit_max);
crit_thresh_passed = 1;
mgr->alarms.temp_max = 1;
mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_PA_CRIT_MAX_ALARM;
mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_PA_WARN_MAX_ALARM;
action = SENSOR_ACT_PA_OFF;
/* add to pending list */
schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
}
LOGP(DTEMP, LOGL_INFO, "PA temperature is: %d\n", temp);
}
}
/* Read the current main supply voltage */
if (oc2gbts_power_get(OC2GBTS_POWER_SUPPLY)) {
rc = oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY, OC2GBTS_POWER_VOLTAGE, &volt);
if (rc < 0) {
LOGP(DTEMP, LOGL_NOTICE,
"Failed to read the main supply voltage. rc=%d\n", rc);
warn_thresh_passed = crit_thresh_passed = 1;
} else {
if (volt > mgr->volt.supply_volt_limit.thresh_warn_max) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because supply voltage is over %d\n", mgr->volt.supply_volt_limit.thresh_warn_max);
warn_thresh_passed = 1;
mgr->oc2gbts_ctrl.warn_flags |= S_MGR_SUPPLY_WARN_MAX_ALARM;
}
if (volt < mgr->volt.supply_volt_limit.thresh_warn_min) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because supply voltage is under %d\n", mgr->volt.supply_volt_limit.thresh_warn_min);
warn_thresh_passed = 1;
mgr->alarms.supply_low = 1;
mgr->oc2gbts_ctrl.warn_flags |= S_MGR_SUPPLY_WARN_MIN_ALARM;
}
if (volt > mgr->volt.supply_volt_limit.thresh_crit_max) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because supply voltage is over %d\n", mgr->volt.supply_volt_limit.thresh_crit_max);
crit_thresh_passed = 1;
mgr->oc2gbts_ctrl.crit_flags |= S_MGR_SUPPLY_CRIT_MAX_ALARM;
mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_SUPPLY_WARN_MAX_ALARM;
}
if (volt < mgr->volt.supply_volt_limit.thresh_crit_min) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because supply voltage is under %d\n", mgr->volt.supply_volt_limit.thresh_crit_min);
crit_thresh_passed = 1;
mgr->alarms.supply_min = 1;
mgr->oc2gbts_ctrl.crit_flags |= S_MGR_SUPPLY_CRIT_MIN_ALARM;
mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_SUPPLY_WARN_MIN_ALARM;
action = SENSOR_ACT_PA_OFF;
/* add to pending list */
schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_NONE);
}
LOGP(DTEMP, LOGL_INFO, "Main supply voltage is: %d\n", volt);
}
}
/* Read the main supply power consumption */
if (oc2gbts_power_get(OC2GBTS_POWER_SUPPLY)) {
rc = oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY, OC2GBTS_POWER_POWER, &power);
if (rc < 0) {
LOGP(DTEMP, LOGL_NOTICE,
"Failed to read the power supply current. rc=%d\n", rc);
warn_thresh_passed = crit_thresh_passed = 1;
} else {
power /= 1000000;
if (power > mgr->pwr.supply_pwr_limit.thresh_warn_max) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because main supply power consumption is over %d\n", mgr->pwr.supply_pwr_limit.thresh_warn_max);
warn_thresh_passed = 1;
mgr->alarms.supply_pwr_high = 1;
mgr->oc2gbts_ctrl.warn_flags |= S_MGR_PWR_SUPPLY_WARN_MAX_ALARM;
}
if (power > mgr->pwr.supply_pwr_limit.thresh_crit_max) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because main supply power consumption is over %d\n", mgr->pwr.supply_pwr_limit.thresh_crit_max);
crit_thresh_passed = 1;
mgr->oc2gbts_ctrl.crit_flags |= S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM;
mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_PWR_SUPPLY_WARN_MAX_ALARM;
if (oc2gbts_power_get(OC2GBTS_POWER_PA)) {
mgr->alarms.supply_pwr_max = 1;
/* schedule to turn off PA */
action = SENSOR_ACT_PA_OFF;
/* repeat same alarm to BSC */
handle_alert_actions(mgr);
}
/* add to pending list */
schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_SHORT_DURATION);
}
LOGP(DTEMP, LOGL_INFO, "Main supply current power consumption is: %d\n", power);
}
} else {
/* keep last state */
if (mgr->oc2gbts_ctrl.crit_flags & S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM) {
warn_thresh_passed = 1;
crit_thresh_passed = 1;
}
}
if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
/* Read the current PA power consumption */
if (oc2gbts_power_get(OC2GBTS_POWER_PA)) {
rc = oc2gbts_power_sensor_get(OC2GBTS_POWER_PA, OC2GBTS_POWER_POWER, &power);
if (rc < 0) {
LOGP(DTEMP, LOGL_NOTICE,
"Failed to read the PA power. rc=%d\n", rc);
warn_thresh_passed = crit_thresh_passed = 1;
} else {
power /= 1000000;
if (power > mgr->pwr.pa_pwr_limit.thresh_warn_max) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because PA power consumption is over %d\n", mgr->pwr.pa_pwr_limit.thresh_warn_max);
warn_thresh_passed = 1;
mgr->alarms.pa_pwr_high = 1;
mgr->oc2gbts_ctrl.warn_flags |= S_MGR_PWR_PA_WARN_MAX_ALARM;
}
if (power > mgr->pwr.pa_pwr_limit.thresh_crit_max) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because PA power consumption is over %d\n", mgr->pwr.pa_pwr_limit.thresh_crit_max);
crit_thresh_passed = 1;
mgr->alarms.pa_pwr_max = 1;
mgr->oc2gbts_ctrl.crit_flags |= S_MGR_PWR_PA_CRIT_MAX_ALARM;
mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_PWR_PA_WARN_MAX_ALARM;
action = SENSOR_ACT_PA_OFF;
/* add to pending list */
schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_SHORT_DURATION);
}
LOGP(DTEMP, LOGL_INFO, "PA power consumption is: %d\n", power);
}
} else {
/* keep last state */
if (mgr->oc2gbts_ctrl.crit_flags & S_MGR_PWR_PA_CRIT_MAX_ALARM) {
warn_thresh_passed = 1;
crit_thresh_passed = 1;
}
}
}
if (oc2gbts_option_get(OC2GBTS_OPTION_PA) &&
oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) &&
oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
/* Read the current VSWR of powered ON PA*/
if (oc2gbts_power_get(OC2GBTS_POWER_PA)) {
rc = oc2gbts_vswr_get(OC2GBTS_VSWR, &vswr);
if (rc < 0) {
LOGP(DTEMP, LOGL_NOTICE,
"Failed to read the VSWR. rc=%d\n", rc);
warn_thresh_passed = crit_thresh_passed = 1;
} else {
if ((vswr > mgr->vswr.vswr_limit.thresh_warn_max) && (mgr->vswr.last_vswr > mgr->vswr.vswr_limit.thresh_warn_max)) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because VSWR is over %d\n", mgr->vswr.vswr_limit.thresh_warn_max);
warn_thresh_passed = 1;
mgr->alarms.vswr_high = 1;
mgr->oc2gbts_ctrl.warn_flags |= S_MGR_VSWR_WARN_MAX_ALARM;
}
if ((vswr > mgr->vswr.vswr_limit.thresh_crit_max) && (mgr->vswr.last_vswr > mgr->vswr.vswr_limit.thresh_crit_max)) {
LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because VSWR is over %d\n", mgr->vswr.vswr_limit.thresh_crit_max);
crit_thresh_passed = 1;
mgr->alarms.vswr_max = 1;
mgr->oc2gbts_ctrl.crit_flags |= S_MGR_VSWR_CRIT_MAX_ALARM;
mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_VSWR_WARN_MAX_ALARM;
action = SENSOR_ACT_PA_OFF;
/* add to pending list */
schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
}
LOGP(DTEMP, LOGL_INFO, "VSWR is: current = %d, last = %d\n", vswr, mgr->vswr.last_vswr);
/* update last VSWR */
mgr->vswr.last_vswr = vswr;
}
} else {
/* keep last state */
if (mgr->oc2gbts_ctrl.crit_flags & S_MGR_VSWR_CRIT_MAX_ALARM) {
warn_thresh_passed = 1;
crit_thresh_passed = 1;
}
}
}
select_led_pattern(mgr);
oc2gbts_mgr_sensor_handle(mgr, crit_thresh_passed, warn_thresh_passed);
}
static void sensor_ctrl_check_cb(void *_data)
{
struct oc2gbts_mgr_instance *mgr = _data;
sensor_ctrl_check(mgr);
/* Check every minute? XXX make it configurable! */
osmo_timer_schedule(&sensor_ctrl_timer, OC2GBTS_SENSOR_TIMER_DURATION, 0);
LOGP(DTEMP, LOGL_DEBUG,"Check sensors timer expired\n");
/* TODO: do we want to notify if some sensors could not be read? */
oc2gbts_swd_event(mgr, SWD_CHECK_TEMP_SENSOR);
}
int oc2gbts_mgr_sensor_init(struct oc2gbts_mgr_instance *mgr)
{
int rc = 0;
/* always enable PA GPIO for OC-2G */
if (!oc2gbts_power_get(OC2GBTS_POWER_PA)) {
rc = oc2gbts_power_set(OC2GBTS_POWER_PA, 1);
if (!rc)
LOGP(DTEMP, LOGL_ERROR, "Failed to set GPIO for internal PA\n");
}
s_mgr = mgr;
sensor_ctrl_timer.cb = sensor_ctrl_check_cb;
sensor_ctrl_timer.data = s_mgr;
sensor_ctrl_check_cb(s_mgr);
return rc;
}

View File

@ -0,0 +1,984 @@
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* Based on sysmoBTS:
* sysmobts_mgr_vty.c
* (C) 2014 by oc2gcom - s.f.m.c. GmbH
*
* All Rights Reserved
*
* Author: Alvaro Neira Ayuso <anayuso@oc2gcom.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <stdint.h>
#include <ctype.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <inttypes.h>
#include <osmocom/vty/vty.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/misc.h>
#include <osmo-bts/logging.h>
#include "oc2gbts_misc.h"
#include "oc2gbts_mgr.h"
#include "oc2gbts_temp.h"
#include "oc2gbts_power.h"
#include "oc2gbts_bid.h"
#include "oc2gbts_led.h"
#include "btsconfig.h"
static struct oc2gbts_mgr_instance *s_mgr;
static const char copyright[] =
"(C) 2012 by Harald Welte <laforge@gnumonks.org>\r\n"
"(C) 2014 by Holger Hans Peter Freyther\r\n"
"(C) 2015 by Yves Godin <support@nuranwireless.com>\r\n"
"License AGPLv3+: GNU AGPL version 2 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
"This is free software: you are free to change and redistribute it.\r\n"
"There is NO WARRANTY, to the extent permitted by law.\r\n";
static int go_to_parent(struct vty *vty)
{
switch (vty->node) {
case MGR_NODE:
vty->node = CONFIG_NODE;
break;
case ACT_NORM_NODE:
case ACT_WARN_NODE:
case ACT_CRIT_NODE:
case LIMIT_SUPPLY_TEMP_NODE:
case LIMIT_SOC_NODE:
case LIMIT_FPGA_NODE:
case LIMIT_RMSDET_NODE:
case LIMIT_OCXO_NODE:
case LIMIT_TX_TEMP_NODE:
case LIMIT_PA_TEMP_NODE:
case LIMIT_SUPPLY_VOLT_NODE:
case LIMIT_VSWR_NODE:
case LIMIT_SUPPLY_PWR_NODE:
case LIMIT_PA_PWR_NODE:
vty->node = MGR_NODE;
break;
default:
vty->node = CONFIG_NODE;
}
return vty->node;
}
static int is_config_node(struct vty *vty, int node)
{
switch (node) {
case MGR_NODE:
case ACT_NORM_NODE:
case ACT_WARN_NODE:
case ACT_CRIT_NODE:
case LIMIT_SUPPLY_TEMP_NODE:
case LIMIT_SOC_NODE:
case LIMIT_FPGA_NODE:
case LIMIT_RMSDET_NODE:
case LIMIT_OCXO_NODE:
case LIMIT_TX_TEMP_NODE:
case LIMIT_PA_TEMP_NODE:
case LIMIT_SUPPLY_VOLT_NODE:
case LIMIT_VSWR_NODE:
case LIMIT_SUPPLY_PWR_NODE:
case LIMIT_PA_PWR_NODE:
return 1;
default:
return 0;
}
}
static struct vty_app_info vty_info = {
.name = "oc2gbts-mgr",
.version = PACKAGE_VERSION,
.go_parent_cb = go_to_parent,
.is_config_node = is_config_node,
.copyright = copyright,
};
#define MGR_STR "Configure oc2gbts-mgr\n"
static struct cmd_node mgr_node = {
MGR_NODE,
"%s(oc2gbts-mgr)# ",
1,
};
static struct cmd_node act_norm_node = {
ACT_NORM_NODE,
"%s(actions-normal)# ",
1,
};
static struct cmd_node act_warn_node = {
ACT_WARN_NODE,
"%s(actions-warn)# ",
1,
};
static struct cmd_node act_crit_node = {
ACT_CRIT_NODE,
"%s(actions-critical)# ",
1,
};
static struct cmd_node limit_supply_temp_node = {
LIMIT_SUPPLY_TEMP_NODE,
"%s(limit-supply-temp)# ",
1,
};
static struct cmd_node limit_soc_node = {
LIMIT_SOC_NODE,
"%s(limit-soc)# ",
1,
};
static struct cmd_node limit_fpga_node = {
LIMIT_FPGA_NODE,
"%s(limit-fpga)# ",
1,
};
static struct cmd_node limit_rmsdet_node = {
LIMIT_RMSDET_NODE,
"%s(limit-rmsdet)# ",
1,
};
static struct cmd_node limit_ocxo_node = {
LIMIT_OCXO_NODE,
"%s(limit-ocxo)# ",
1,
};
static struct cmd_node limit_tx_temp_node = {
LIMIT_TX_TEMP_NODE,
"%s(limit-tx-temp)# ",
1,
};
static struct cmd_node limit_pa_temp_node = {
LIMIT_PA_TEMP_NODE,
"%s(limit-pa-temp)# ",
1,
};
static struct cmd_node limit_supply_volt_node = {
LIMIT_SUPPLY_VOLT_NODE,
"%s(limit-supply-volt)# ",
1,
};
static struct cmd_node limit_vswr_node = {
LIMIT_VSWR_NODE,
"%s(limit-vswr)# ",
1,
};
static struct cmd_node limit_supply_pwr_node = {
LIMIT_SUPPLY_PWR_NODE,
"%s(limit-supply-pwr)# ",
1,
};
static struct cmd_node limit_pa_pwr_node = {
LIMIT_PA_PWR_NODE,
"%s(limit-pa-pwr)# ",
1,
};
static struct cmd_node limit_gps_fix_node = {
LIMIT_GPS_FIX_NODE,
"%s(limit-gps-fix)# ",
1,
};
DEFUN(cfg_mgr, cfg_mgr_cmd,
"oc2gbts-mgr",
MGR_STR)
{
vty->node = MGR_NODE;
return CMD_SUCCESS;
}
static void write_volt_limit(struct vty *vty, const char *name,
struct oc2gbts_volt_limit *limit)
{
vty_out(vty, " %s%s", name, VTY_NEWLINE);
vty_out(vty, " threshold warning min %d%s",
limit->thresh_warn_min, VTY_NEWLINE);
vty_out(vty, " threshold critical min %d%s",
limit->thresh_crit_min, VTY_NEWLINE);
}
static void write_vswr_limit(struct vty *vty, const char *name,
struct oc2gbts_vswr_limit *limit)
{
vty_out(vty, " %s%s", name, VTY_NEWLINE);
vty_out(vty, " threshold warning max %d%s",
limit->thresh_warn_max, VTY_NEWLINE);
}
static void write_pwr_limit(struct vty *vty, const char *name,
struct oc2gbts_pwr_limit *limit)
{
vty_out(vty, " %s%s", name, VTY_NEWLINE);
vty_out(vty, " threshold warning max %d%s",
limit->thresh_warn_max, VTY_NEWLINE);
vty_out(vty, " threshold critical max %d%s",
limit->thresh_crit_max, VTY_NEWLINE);
}
static void write_norm_action(struct vty *vty, const char *name, int actions)
{
vty_out(vty, " %s%s", name, VTY_NEWLINE);
vty_out(vty, " %spa-on%s",
(actions & SENSOR_ACT_NORM_PA_ON) ? "" : "no ", VTY_NEWLINE);
vty_out(vty, " %sbts-service-on%s",
(actions & SENSOR_ACT_NORM_BTS_SRV_ON) ? "" : "no ", VTY_NEWLINE);
}
static void write_action(struct vty *vty, const char *name, int actions)
{
vty_out(vty, " %s%s", name, VTY_NEWLINE);
vty_out(vty, " %spa-off%s",
(actions & SENSOR_ACT_PA_OFF) ? "" : "no ", VTY_NEWLINE);
vty_out(vty, " %sbts-service-off%s",
(actions & SENSOR_ACT_BTS_SRV_OFF) ? "" : "no ", VTY_NEWLINE);
}
static int config_write_mgr(struct vty *vty)
{
vty_out(vty, "oc2gbts-mgr%s", VTY_NEWLINE);
write_volt_limit(vty, "limits supply_volt", &s_mgr->volt.supply_volt_limit);
write_pwr_limit(vty, "limits supply_pwr", &s_mgr->pwr.supply_pwr_limit);
write_vswr_limit(vty, "limits vswr", &s_mgr->vswr.vswr_limit);
write_norm_action(vty, "actions normal", s_mgr->state.action_norm);
write_action(vty, "actions warn", s_mgr->state.action_warn);
write_action(vty, "actions critical", s_mgr->state.action_crit);
return CMD_SUCCESS;
}
static int config_write_dummy(struct vty *vty)
{
return CMD_SUCCESS;
}
#define CFG_LIMIT_TEMP(name, expl, switch_to, variable) \
DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \
"limits " #name, \
"Configure Limits\n" expl) \
{ \
vty->node = switch_to; \
vty->index = &s_mgr->temp.variable; \
return CMD_SUCCESS; \
}
CFG_LIMIT_TEMP(supply_temp, "SUPPLY TEMP\n", LIMIT_SUPPLY_TEMP_NODE, supply_temp_limit)
CFG_LIMIT_TEMP(soc_temp, "SOC TEMP\n", LIMIT_SOC_NODE, soc_temp_limit)
CFG_LIMIT_TEMP(fpga_temp, "FPGA TEMP\n", LIMIT_FPGA_NODE, fpga_temp_limit)
CFG_LIMIT_TEMP(rmsdet_temp, "RMSDET TEMP\n", LIMIT_RMSDET_NODE, rmsdet_temp_limit)
CFG_LIMIT_TEMP(ocxo_temp, "OCXO TEMP\n", LIMIT_OCXO_NODE, ocxo_temp_limit)
CFG_LIMIT_TEMP(tx_temp, "TX TEMP\n", LIMIT_TX_TEMP_NODE, tx_temp_limit)
CFG_LIMIT_TEMP(pa_temp, "PA TEMP\n", LIMIT_PA_TEMP_NODE, pa_temp_limit)
#undef CFG_LIMIT_TEMP
#define CFG_LIMIT_VOLT(name, expl, switch_to, variable) \
DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \
"limits " #name, \
"Configure Limits\n" expl) \
{ \
vty->node = switch_to; \
vty->index = &s_mgr->volt.variable; \
return CMD_SUCCESS; \
}
CFG_LIMIT_VOLT(supply_volt, "SUPPLY VOLT\n", LIMIT_SUPPLY_VOLT_NODE, supply_volt_limit)
#undef CFG_LIMIT_VOLT
#define CFG_LIMIT_VSWR(name, expl, switch_to, variable) \
DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \
"limits " #name, \
"Configure Limits\n" expl) \
{ \
vty->node = switch_to; \
vty->index = &s_mgr->vswr.variable; \
return CMD_SUCCESS; \
}
CFG_LIMIT_VSWR(vswr, "VSWR\n", LIMIT_VSWR_NODE, vswr_limit)
#undef CFG_LIMIT_VSWR
#define CFG_LIMIT_PWR(name, expl, switch_to, variable) \
DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \
"limits " #name, \
"Configure Limits\n" expl) \
{ \
vty->node = switch_to; \
vty->index = &s_mgr->pwr.variable; \
return CMD_SUCCESS; \
}
CFG_LIMIT_PWR(supply_pwr, "SUPPLY PWR\n", LIMIT_SUPPLY_PWR_NODE, supply_pwr_limit)
CFG_LIMIT_PWR(pa_pwr, "PA PWR\n", LIMIT_PA_PWR_NODE, pa_pwr_limit)
#undef CFG_LIMIT_PWR
#define CFG_LIMIT_GPS_FIX(name, expl, switch_to, variable) \
DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \
"limits " #name, \
"Configure Limits\n" expl) \
{ \
vty->node = switch_to; \
vty->index = &s_mgr->gps.variable; \
return CMD_SUCCESS; \
}
CFG_LIMIT_GPS_FIX(gps_fix, "GPS FIX\n", LIMIT_GPS_FIX_NODE, gps_fix_limit)
#undef CFG_LIMIT_GPS_FIX
DEFUN(cfg_limit_volt_warn_min, cfg_thresh_volt_warn_min_cmd,
"threshold warning min <0-48000>",
"Threshold to reach\n" "Warning level\n" "Range\n")
{
struct oc2gbts_volt_limit *limit = vty->index;
limit->thresh_warn_min = atoi(argv[0]);
return CMD_SUCCESS;
}
DEFUN(cfg_limit_volt_crit_min, cfg_thresh_volt_crit_min_cmd,
"threshold critical min <0-48000>",
"Threshold to reach\n" "Critical level\n" "Range\n")
{
struct oc2gbts_volt_limit *limit = vty->index;
limit->thresh_crit_min = atoi(argv[0]);
return CMD_SUCCESS;
}
DEFUN(cfg_limit_vswr_warn_max, cfg_thresh_vswr_warn_max_cmd,
"threshold warning max <1000-200000>",
"Threshold to reach\n" "Warning level\n" "Range\n")
{
struct oc2gbts_vswr_limit *limit = vty->index;
limit->thresh_warn_max = atoi(argv[0]);
return CMD_SUCCESS;
}
DEFUN(cfg_limit_vswr_crit_max, cfg_thresh_vswr_crit_max_cmd,
"threshold critical max <1000-200000>",
"Threshold to reach\n" "Warning level\n" "Range\n")
{
struct oc2gbts_vswr_limit *limit = vty->index;
limit->thresh_crit_max = atoi(argv[0]);
return CMD_SUCCESS;
}
DEFUN(cfg_limit_pwr_warn_max, cfg_thresh_pwr_warn_max_cmd,
"threshold warning max <0-200>",
"Threshold to reach\n" "Warning level\n" "Range\n")
{
struct oc2gbts_pwr_limit *limit = vty->index;
limit->thresh_warn_max = atoi(argv[0]);
return CMD_SUCCESS;
}
DEFUN(cfg_limit_pwr_crit_max, cfg_thresh_pwr_crit_max_cmd,
"threshold critical max <0-200>",
"Threshold to reach\n" "Warning level\n" "Range\n")
{
struct oc2gbts_pwr_limit *limit = vty->index;
limit->thresh_crit_max = atoi(argv[0]);
return CMD_SUCCESS;
}
#define CFG_ACTION(name, expl, switch_to, variable) \
DEFUN(cfg_action_##name, cfg_action_##name##_cmd, \
"actions " #name, \
"Configure Actions\n" expl) \
{ \
vty->node = switch_to; \
vty->index = &s_mgr->state.variable; \
return CMD_SUCCESS; \
}
CFG_ACTION(normal, "Normal Actions\n", ACT_NORM_NODE, action_norm)
CFG_ACTION(warn, "Warning Actions\n", ACT_WARN_NODE, action_warn)
CFG_ACTION(critical, "Critical Actions\n", ACT_CRIT_NODE, action_crit)
#undef CFG_ACTION
DEFUN(cfg_action_pa_on, cfg_action_pa_on_cmd,
"pa-on",
"Switch the Power Amplifier on\n")
{
int *action = vty->index;
*action |= SENSOR_ACT_NORM_PA_ON;
return CMD_SUCCESS;
}
DEFUN(cfg_no_action_pa_on, cfg_no_action_pa_on_cmd,
"no pa-on",
NO_STR "Switch the Power Amplifier on\n")
{
int *action = vty->index;
*action &= ~SENSOR_ACT_NORM_PA_ON;
return CMD_SUCCESS;
}
DEFUN(cfg_action_bts_srv_on, cfg_action_bts_srv_on_cmd,
"bts-service-on",
"Start the systemd oc2gbts.service\n")
{
int *action = vty->index;
*action |= SENSOR_ACT_NORM_BTS_SRV_ON;
return CMD_SUCCESS;
}
DEFUN(cfg_no_action_bts_srv_on, cfg_no_action_bts_srv_on_cmd,
"no bts-service-on",
NO_STR "Start the systemd oc2gbts.service\n")
{
int *action = vty->index;
*action &= ~SENSOR_ACT_NORM_BTS_SRV_ON;
return CMD_SUCCESS;
}
DEFUN(cfg_action_pa_off, cfg_action_pa_off_cmd,
"pa-off",
"Switch the Power Amplifier off\n")
{
int *action = vty->index;
*action |= SENSOR_ACT_PA_OFF;
return CMD_SUCCESS;
}
DEFUN(cfg_no_action_pa_off, cfg_no_action_pa_off_cmd,
"no pa-off",
NO_STR "Do not switch off the Power Amplifier\n")
{
int *action = vty->index;
*action &= ~SENSOR_ACT_PA_OFF;
return CMD_SUCCESS;
}
DEFUN(cfg_action_bts_srv_off, cfg_action_bts_srv_off_cmd,
"bts-service-off",
"Stop the systemd oc2gbts.service\n")
{
int *action = vty->index;
*action |= SENSOR_ACT_BTS_SRV_OFF;
return CMD_SUCCESS;
}
DEFUN(cfg_no_action_bts_srv_off, cfg_no_action_bts_srv_off_cmd,
"no bts-service-off",
NO_STR "Stop the systemd oc2gbts.service\n")
{
int *action = vty->index;
*action &= ~SENSOR_ACT_BTS_SRV_OFF;
return CMD_SUCCESS;
}
DEFUN(show_mgr, show_mgr_cmd, "show manager",
SHOW_STR "Display information about the manager")
{
int temp, volt, current, power, vswr;
vty_out(vty, "Warning alarm flags: 0x%08x%s",
s_mgr->oc2gbts_ctrl.warn_flags, VTY_NEWLINE);
vty_out(vty, "Critical alarm flags: 0x%08x%s",
s_mgr->oc2gbts_ctrl.crit_flags, VTY_NEWLINE);
vty_out(vty, "Preventive action retried: %d%s",
s_mgr->alarms.preventive_retry, VTY_NEWLINE);
vty_out(vty, "Temperature control state: %s%s",
oc2gbts_mgr_sensor_get_state(s_mgr->state.state), VTY_NEWLINE);
vty_out(vty, "Current Temperatures%s", VTY_NEWLINE);
oc2gbts_temp_get(OC2GBTS_TEMP_SUPPLY, &temp);
vty_out(vty, " Main Supply : %4.2f Celcius%s",
temp/ 1000.0f,
VTY_NEWLINE);
oc2gbts_temp_get(OC2GBTS_TEMP_SOC, &temp);
vty_out(vty, " SoC : %4.2f Celcius%s",
temp / 1000.0f,
VTY_NEWLINE);
oc2gbts_temp_get(OC2GBTS_TEMP_FPGA, &temp);
vty_out(vty, " FPGA : %4.2f Celcius%s",
temp / 1000.0f,
VTY_NEWLINE);
if (oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) ||
oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
oc2gbts_temp_get(OC2GBTS_TEMP_RMSDET, &temp);
vty_out(vty, " RMSDet : %4.2f Celcius%s",
temp / 1000.0f,
VTY_NEWLINE);
}
oc2gbts_temp_get(OC2GBTS_TEMP_OCXO, &temp);
vty_out(vty, " OCXO : %4.2f Celcius%s",
temp / 1000.0f,
VTY_NEWLINE);
oc2gbts_temp_get(OC2GBTS_TEMP_TX, &temp);
vty_out(vty, " TX : %4.2f Celcius%s",
temp / 1000.0f,
VTY_NEWLINE);
if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) {
oc2gbts_temp_get(OC2GBTS_TEMP_PA, &temp);
vty_out(vty, " Power Amp : %4.2f Celcius%s",
temp / 1000.0f,
VTY_NEWLINE);
}
vty_out(vty, "Power Status%s", VTY_NEWLINE);
oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY,
OC2GBTS_POWER_VOLTAGE, &volt);
oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY,
OC2GBTS_POWER_CURRENT, &current);
oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY,
OC2GBTS_POWER_POWER, &power);
vty_out(vty, " Main Supply : ON [%6.2f Vdc, %4.2f A, %6.2f W]%s",
volt /1000.0f,
current /1000.0f,
power /1000000.0f,
VTY_NEWLINE);
if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
oc2gbts_power_sensor_get(OC2GBTS_POWER_PA,
OC2GBTS_POWER_VOLTAGE, &volt);
oc2gbts_power_sensor_get(OC2GBTS_POWER_PA,
OC2GBTS_POWER_CURRENT, &current);
oc2gbts_power_sensor_get(OC2GBTS_POWER_PA,
OC2GBTS_POWER_POWER, &power);
vty_out(vty, " Power Amp : %s [%6.2f Vdc, %4.2f A, %6.2f W]%s",
oc2gbts_power_get(OC2GBTS_POWER_PA) ? "ON " : "OFF",
volt /1000.0f,
current /1000.0f,
power /1000000.0f,
VTY_NEWLINE);
}
if (oc2gbts_option_get(OC2GBTS_OPTION_PA) &&
oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) &&
oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
vty_out(vty, "VSWR Status%s", VTY_NEWLINE);
oc2gbts_vswr_get(OC2GBTS_VSWR, &vswr);
vty_out(vty, " VSWR : %f %s",
vswr / 1000.0f,
VTY_NEWLINE);
}
return CMD_SUCCESS;
}
DEFUN(show_thresh, show_thresh_cmd, "show thresholds",
SHOW_STR "Display information about the thresholds")
{
vty_out(vty, "Temperature limits (Celsius)%s", VTY_NEWLINE);
vty_out(vty, " Main supply%s", VTY_NEWLINE);
vty_out(vty, " Critical max : %d%s",s_mgr->temp.supply_temp_limit.thresh_crit_max, VTY_NEWLINE);
vty_out(vty, " Warning max : %d%s",s_mgr->temp.supply_temp_limit.thresh_warn_max, VTY_NEWLINE);
vty_out(vty, " Warning min : %d%s",s_mgr->temp.supply_temp_limit.thresh_warn_min, VTY_NEWLINE);
vty_out(vty, " SoC%s", VTY_NEWLINE);
vty_out(vty, " Critical max : %d%s",s_mgr->temp.soc_temp_limit.thresh_crit_max, VTY_NEWLINE);
vty_out(vty, " Warning max : %d%s",s_mgr->temp.soc_temp_limit.thresh_warn_max, VTY_NEWLINE);
vty_out(vty, " Warning min : %d%s",s_mgr->temp.soc_temp_limit.thresh_warn_min, VTY_NEWLINE);
vty_out(vty, " FPGA%s", VTY_NEWLINE);
vty_out(vty, " Critical max : %d%s",s_mgr->temp.fpga_temp_limit.thresh_crit_max, VTY_NEWLINE);
vty_out(vty, " Warning max : %d%s",s_mgr->temp.fpga_temp_limit.thresh_warn_max, VTY_NEWLINE);
vty_out(vty, " Warning min : %d%s",s_mgr->temp.fpga_temp_limit.thresh_warn_min, VTY_NEWLINE);
if (oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) ||
oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
vty_out(vty, " RMSDet%s", VTY_NEWLINE);
vty_out(vty, " Critical max : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_crit_max, VTY_NEWLINE);
vty_out(vty, " Warning max : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_warn_max, VTY_NEWLINE);
vty_out(vty, " Warning min : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_warn_min, VTY_NEWLINE);
}
vty_out(vty, " OCXO%s", VTY_NEWLINE);
vty_out(vty, " Critical max : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_crit_max, VTY_NEWLINE);
vty_out(vty, " Warning max : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_warn_max, VTY_NEWLINE);
vty_out(vty, " Warning min : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_warn_min, VTY_NEWLINE);
vty_out(vty, " TX%s", VTY_NEWLINE);
vty_out(vty, " Critical max : %d%s",s_mgr->temp.tx_temp_limit.thresh_crit_max, VTY_NEWLINE);
vty_out(vty, " Warning max : %d%s",s_mgr->temp.tx_temp_limit.thresh_warn_max, VTY_NEWLINE);
vty_out(vty, " Warning min : %d%s",s_mgr->temp.tx_temp_limit.thresh_warn_min, VTY_NEWLINE);
if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) {
vty_out(vty, " PA%s", VTY_NEWLINE);
vty_out(vty, " Critical max : %d%s",s_mgr->temp.pa_temp_limit.thresh_crit_max, VTY_NEWLINE);
vty_out(vty, " Warning max : %d%s",s_mgr->temp.pa_temp_limit.thresh_warn_max, VTY_NEWLINE);
vty_out(vty, " Warning min : %d%s",s_mgr->temp.pa_temp_limit.thresh_warn_min, VTY_NEWLINE);
}
vty_out(vty, "Power limits%s", VTY_NEWLINE);
vty_out(vty, " Main supply (mV)%s", VTY_NEWLINE);
vty_out(vty, " Critical max : %d%s",s_mgr->volt.supply_volt_limit.thresh_crit_max, VTY_NEWLINE);
vty_out(vty, " Warning max : %d%s",s_mgr->volt.supply_volt_limit.thresh_warn_max, VTY_NEWLINE);
vty_out(vty, " Warning min : %d%s",s_mgr->volt.supply_volt_limit.thresh_warn_min, VTY_NEWLINE);
vty_out(vty, " Critical min : %d%s",s_mgr->volt.supply_volt_limit.thresh_crit_min, VTY_NEWLINE);
vty_out(vty, " Main supply power (W)%s", VTY_NEWLINE);
vty_out(vty, " Critical max : %d%s",s_mgr->pwr.supply_pwr_limit.thresh_crit_max, VTY_NEWLINE);
vty_out(vty, " Warning max : %d%s",s_mgr->pwr.supply_pwr_limit.thresh_warn_max, VTY_NEWLINE);
if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
vty_out(vty, " PA power (W)%s", VTY_NEWLINE);
vty_out(vty, " Critical max : %d%s",s_mgr->pwr.pa_pwr_limit.thresh_crit_max, VTY_NEWLINE);
vty_out(vty, " Warning max : %d%s",s_mgr->pwr.pa_pwr_limit.thresh_warn_max, VTY_NEWLINE);
}
if (oc2gbts_option_get(OC2GBTS_OPTION_PA) &&
oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) &&
oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
vty_out(vty, "VSWR limits%s", VTY_NEWLINE);
vty_out(vty, " TX%s", VTY_NEWLINE);
vty_out(vty, " Critical max : %d%s",s_mgr->vswr.vswr_limit.thresh_crit_max, VTY_NEWLINE);
vty_out(vty, " Warning max : %d%s",s_mgr->vswr.vswr_limit.thresh_warn_max, VTY_NEWLINE);
}
vty_out(vty, "Days since last GPS 3D fix%s", VTY_NEWLINE);
vty_out(vty, " Warning max : %d%s",s_mgr->gps.gps_fix_limit.thresh_warn_max, VTY_NEWLINE);
return CMD_SUCCESS;
}
DEFUN(calibrate_clock, calibrate_clock_cmd,
"calibrate clock",
"Calibration commands\n"
"Calibrate clock against GPS PPS\n")
{
if (oc2gbts_mgr_calib_run(s_mgr) < 0) {
vty_out(vty, "%%Failed to start calibration.%s", VTY_NEWLINE);
return CMD_WARNING;
}
return CMD_SUCCESS;
}
DEFUN(set_led_pattern, set_led_pattern_cmd,
"set led pattern <0-255>",
"Set LED pattern\n"
"Set LED pattern for debugging purpose only. This pattern will be overridden after 60 seconds by LED pattern of actual system state\n")
{
int pattern_id = atoi(argv[0]);
if ((pattern_id < 0) || (pattern_id > BLINK_PATTERN_MAX_ITEM)) {
vty_out(vty, "%%Invalid LED pattern ID. It must be in range of %d..%d %s", 0, BLINK_PATTERN_MAX_ITEM - 1, VTY_NEWLINE);
return CMD_WARNING;
}
led_set(s_mgr, pattern_id);
return CMD_SUCCESS;
}
DEFUN(force_mgr_state, force_mgr_state_cmd,
"force manager state <0-255>",
"Force BTS manager state\n"
"Force BTS manager state for debugging purpose only\n")
{
int state = atoi(argv[0]);
if ((state < 0) || (state > STATE_CRITICAL)) {
vty_out(vty, "%%Invalid BTS manager state. It must be in range of %d..%d %s", 0, STATE_CRITICAL, VTY_NEWLINE);
return CMD_WARNING;
}
s_mgr->state.state = state;
return CMD_SUCCESS;
}
#define LIMIT_TEMP(name, limit, expl, variable, criticity, min_max) \
DEFUN(limit_temp_##name##_##variable, limit_temp_##name##_##variable##_cmd, \
"limit temp " #name " " #criticity " " #min_max " <-200-200>", \
"Limit to reach\n" expl) \
{ \
s_mgr->temp.limit.variable = atoi(argv[0]); \
return CMD_SUCCESS; \
}
LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_warn_max, warning, max)
LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_crit_max, critical, max)
LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_warn_min, warning, min)
LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_warn_max, warning, max)
LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_crit_max, critical, max)
LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_warn_min, warning, min)
LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_warn_max, warning, max)
LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_crit_max, critical, max)
LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_warn_min, warning, min)
LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_warn_max, warning, max)
LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_crit_max, critical, max)
LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_warn_min, warning, min)
LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_warn_max, warning, max)
LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_crit_max, critical, max)
LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_warn_min, warning, min)
LIMIT_TEMP(tx, tx_temp_limit, "TX TEMP\n", thresh_warn_max, warning, max)
LIMIT_TEMP(tx, tx_temp_limit, "TX TEMP\n", thresh_crit_max, critical, max)
LIMIT_TEMP(tx, tx_temp_limit, "TX TEMP\n", thresh_warn_min, warning, min)
LIMIT_TEMP(pa, pa_temp_limit, "PA TEMP\n", thresh_warn_max, warning, max)
LIMIT_TEMP(pa, pa_temp_limit, "PA TEMP\n", thresh_crit_max, critical, max)
LIMIT_TEMP(pa, pa_temp_limit, "PA TEMP\n", thresh_warn_min, warning, min)
#undef LIMIT_TEMP
#define LIMIT_VOLT(name, limit, expl, variable, criticity, min_max) \
DEFUN(limit_volt_##name##_##variable, limit_volt_##name##_##variable##_cmd, \
"limit " #name " " #criticity " " #min_max " <0-48000>", \
"Limit to reach\n" expl) \
{ \
s_mgr->volt.limit.variable = atoi(argv[0]); \
return CMD_SUCCESS; \
}
LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_warn_max, warning, max)
LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_crit_max, critical, max)
LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_warn_min, warning, min)
LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_crit_min, critical, min)
#undef LIMIT_VOLT
#define LIMIT_PWR(name, limit, expl, variable, criticity, min_max) \
DEFUN(limit_pwr_##name##_##variable, limit_pwr_##name##_##variable##_cmd, \
"limit power " #name " " #criticity " " #min_max " <0-200>", \
"Limit to reach\n" expl) \
{ \
s_mgr->pwr.limit.variable = atoi(argv[0]); \
return CMD_SUCCESS; \
}
LIMIT_PWR(supply, supply_pwr_limit, "SUPPLY PWR\n", thresh_warn_max, warning, max)
LIMIT_PWR(supply, supply_pwr_limit, "SUPPLY PWR\n", thresh_crit_max, critical, max)
LIMIT_PWR(pa, pa_pwr_limit, "PA PWR\n", thresh_warn_max, warning, max)
LIMIT_PWR(pa, pa_pwr_limit, "PA PWR\n", thresh_crit_max, critical, max)
#undef LIMIT_PWR
#define LIMIT_VSWR(limit, expl, variable, criticity, min_max) \
DEFUN(limit_vswr_##variable, limit_vswr_##variable##_cmd, \
"limit vswr " #criticity " " #min_max " <1000-200000>", \
"Limit to reach\n" expl) \
{ \
s_mgr->vswr.limit.variable = atoi(argv[0]); \
return CMD_SUCCESS; \
}
LIMIT_VSWR(vswr_limit, "VSWR\n", thresh_warn_max, warning, max)
LIMIT_VSWR(vswr_limit, "VSWR\n", thresh_crit_max, critical, max)
#undef LIMIT_VSWR
#define LIMIT_GPSFIX(limit, expl, variable, criticity, min_max) \
DEFUN(limit_gpsfix_##variable, limit_gpsfix_##variable##_cmd, \
"limit gpsfix " #criticity " " #min_max " <0-365>", \
"Limit to reach\n" expl) \
{ \
s_mgr->gps.limit.variable = atoi(argv[0]); \
return CMD_SUCCESS; \
}
LIMIT_GPSFIX(gps_fix_limit, "GPS FIX\n", thresh_warn_max, warning, max)
#undef LIMIT_GPSFIX
static void register_limit(int limit, uint32_t unit)
{
switch (unit) {
case MGR_LIMIT_TYPE_VOLT:
install_element(limit, &cfg_thresh_volt_warn_min_cmd);
install_element(limit, &cfg_thresh_volt_crit_min_cmd);
break;
case MGR_LIMIT_TYPE_VSWR:
install_element(limit, &cfg_thresh_vswr_warn_max_cmd);
install_element(limit, &cfg_thresh_vswr_crit_max_cmd);
break;
case MGR_LIMIT_TYPE_PWR:
install_element(limit, &cfg_thresh_pwr_warn_max_cmd);
install_element(limit, &cfg_thresh_pwr_crit_max_cmd);
break;
default:
break;
}
}
static void register_normal_action(int act)
{
if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
install_element(act, &cfg_action_pa_on_cmd);
install_element(act, &cfg_no_action_pa_on_cmd);
}
install_element(act, &cfg_action_bts_srv_on_cmd);
install_element(act, &cfg_no_action_bts_srv_on_cmd);
}
static void register_action(int act)
{
if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
install_element(act, &cfg_action_pa_off_cmd);
install_element(act, &cfg_no_action_pa_off_cmd);
}
install_element(act, &cfg_action_bts_srv_off_cmd);
install_element(act, &cfg_no_action_bts_srv_off_cmd);
}
static void register_hidden_commands()
{
install_element(ENABLE_NODE, &limit_temp_supply_thresh_warn_max_cmd);
install_element(ENABLE_NODE, &limit_temp_supply_thresh_crit_max_cmd);
install_element(ENABLE_NODE, &limit_temp_supply_thresh_warn_min_cmd);
install_element(ENABLE_NODE, &limit_temp_soc_thresh_warn_max_cmd);
install_element(ENABLE_NODE, &limit_temp_soc_thresh_crit_max_cmd);
install_element(ENABLE_NODE, &limit_temp_soc_thresh_warn_min_cmd);
install_element(ENABLE_NODE, &limit_temp_fpga_thresh_warn_max_cmd);
install_element(ENABLE_NODE, &limit_temp_fpga_thresh_crit_max_cmd);
install_element(ENABLE_NODE, &limit_temp_fpga_thresh_warn_min_cmd);
install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_warn_max_cmd);
install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_crit_max_cmd);
install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_warn_min_cmd);
install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_warn_max_cmd);
install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_crit_max_cmd);
install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_warn_min_cmd);
install_element(ENABLE_NODE, &limit_temp_tx_thresh_warn_max_cmd);
install_element(ENABLE_NODE, &limit_temp_tx_thresh_crit_max_cmd);
install_element(ENABLE_NODE, &limit_temp_tx_thresh_warn_min_cmd);
if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) {
install_element(ENABLE_NODE, &limit_temp_pa_thresh_warn_max_cmd);
install_element(ENABLE_NODE, &limit_temp_pa_thresh_crit_max_cmd);
install_element(ENABLE_NODE, &limit_temp_pa_thresh_warn_min_cmd);
}
install_element(ENABLE_NODE, &limit_volt_supply_thresh_warn_max_cmd);
install_element(ENABLE_NODE, &limit_volt_supply_thresh_crit_max_cmd);
install_element(ENABLE_NODE, &limit_volt_supply_thresh_warn_min_cmd);
install_element(ENABLE_NODE, &limit_volt_supply_thresh_crit_min_cmd);
install_element(ENABLE_NODE, &limit_pwr_supply_thresh_warn_max_cmd);
install_element(ENABLE_NODE, &limit_pwr_supply_thresh_crit_max_cmd);
if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
install_element(ENABLE_NODE, &limit_pwr_pa_thresh_warn_max_cmd);
install_element(ENABLE_NODE, &limit_pwr_pa_thresh_crit_max_cmd);
}
if (oc2gbts_option_get(OC2GBTS_OPTION_PA) &&
oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) &&
oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
install_element(ENABLE_NODE, &limit_vswr_thresh_warn_max_cmd);
install_element(ENABLE_NODE, &limit_vswr_thresh_crit_max_cmd);
}
install_element(ENABLE_NODE, &limit_gpsfix_thresh_warn_max_cmd);
}
int oc2gbts_mgr_vty_init(void)
{
vty_init(&vty_info);
install_element_ve(&show_mgr_cmd);
install_element_ve(&show_thresh_cmd);
install_element(ENABLE_NODE, &calibrate_clock_cmd);
install_node(&mgr_node, config_write_mgr);
install_element(CONFIG_NODE, &cfg_mgr_cmd);
vty_install_default(MGR_NODE);
/* install the limit nodes */
install_node(&limit_supply_temp_node, config_write_dummy);
install_element(MGR_NODE, &cfg_limit_supply_temp_cmd);
vty_install_default(LIMIT_SUPPLY_TEMP_NODE);
install_node(&limit_soc_node, config_write_dummy);
install_element(MGR_NODE, &cfg_limit_soc_temp_cmd);
vty_install_default(LIMIT_SOC_NODE);
install_node(&limit_fpga_node, config_write_dummy);
install_element(MGR_NODE, &cfg_limit_fpga_temp_cmd);
vty_install_default(LIMIT_FPGA_NODE);
if (oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) ||
oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
install_node(&limit_rmsdet_node, config_write_dummy);
install_element(MGR_NODE, &cfg_limit_rmsdet_temp_cmd);
vty_install_default(LIMIT_RMSDET_NODE);
}
install_node(&limit_ocxo_node, config_write_dummy);
install_element(MGR_NODE, &cfg_limit_ocxo_temp_cmd);
vty_install_default(LIMIT_OCXO_NODE);
install_node(&limit_tx_temp_node, config_write_dummy);
install_element(MGR_NODE, &cfg_limit_tx_temp_cmd);
vty_install_default(LIMIT_TX_TEMP_NODE);
if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) {
install_node(&limit_pa_temp_node, config_write_dummy);
install_element(MGR_NODE, &cfg_limit_pa_temp_cmd);
vty_install_default(LIMIT_PA_TEMP_NODE);
}
install_node(&limit_supply_volt_node, config_write_dummy);
install_element(MGR_NODE, &cfg_limit_supply_volt_cmd);
register_limit(LIMIT_SUPPLY_VOLT_NODE, MGR_LIMIT_TYPE_VOLT);
vty_install_default(LIMIT_SUPPLY_VOLT_NODE);
if (oc2gbts_option_get(OC2GBTS_OPTION_PA) &&
oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) &&
oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
install_node(&limit_vswr_node, config_write_dummy);
install_element(MGR_NODE, &cfg_limit_vswr_cmd);
register_limit(LIMIT_VSWR_NODE, MGR_LIMIT_TYPE_VSWR);
vty_install_default(LIMIT_VSWR_NODE);
}
install_node(&limit_supply_pwr_node, config_write_dummy);
install_element(MGR_NODE, &cfg_limit_supply_pwr_cmd);
register_limit(LIMIT_SUPPLY_PWR_NODE, MGR_LIMIT_TYPE_PWR);
vty_install_default(LIMIT_SUPPLY_PWR_NODE);
if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
install_node(&limit_pa_pwr_node, config_write_dummy);
install_element(MGR_NODE, &cfg_limit_pa_pwr_cmd);
vty_install_default(LIMIT_PA_PWR_NODE);
}
install_node(&limit_gps_fix_node, config_write_dummy);
install_element(MGR_NODE, &cfg_limit_gps_fix_cmd);
vty_install_default(LIMIT_GPS_FIX_NODE);
/* install the normal node */
install_node(&act_norm_node, config_write_dummy);
install_element(MGR_NODE, &cfg_action_normal_cmd);
register_normal_action(ACT_NORM_NODE);
/* install the warning and critical node */
install_node(&act_warn_node, config_write_dummy);
install_element(MGR_NODE, &cfg_action_warn_cmd);
register_action(ACT_WARN_NODE);
vty_install_default(ACT_WARN_NODE);
install_node(&act_crit_node, config_write_dummy);
install_element(MGR_NODE, &cfg_action_critical_cmd);
register_action(ACT_CRIT_NODE);
vty_install_default(ACT_CRIT_NODE);
/* install LED pattern command for debugging purpose */
install_element_ve(&set_led_pattern_cmd);
install_element_ve(&force_mgr_state_cmd);
register_hidden_commands();
return 0;
}
int oc2gbts_mgr_parse_config(struct oc2gbts_mgr_instance *manager)
{
int rc;
s_mgr = manager;
rc = vty_read_config_file(s_mgr->config_file, NULL);
if (rc < 0) {
fprintf(stderr, "Failed to parse the config file: '%s'\n",
s_mgr->config_file);
return rc;
}
return 0;
}

View File

@ -0,0 +1,381 @@
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* Based on sysmoBTS:
* sysmobts_misc.c
* (C) 2012 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>
#include <fcntl.h>
#include <limits.h>
#include <time.h>
#include <sys/signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/application.h>
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/logging.h>
#include "oc2gbts_mgr.h"
#include "btsconfig.h"
#include "oc2gbts_misc.h"
#include "oc2gbts_par.h"
#include "oc2gbts_temp.h"
#include "oc2gbts_power.h"
#include "oc2gbts_bid.h"
/*********************************************************************
* Temperature handling
*********************************************************************/
static const struct {
const char *name;
int has_max;
enum oc2gbts_temp_sensor sensor;
enum oc2gbts_par ee_par;
} temp_data[] = {
{
.name = "supply_temp",
.has_max = 1,
.sensor = OC2GBTS_TEMP_SUPPLY,
.ee_par = OC2GBTS_PAR_TEMP_SUPPLY_MAX,
}, {
.name = "soc_temp",
.has_max = 0,
.sensor = OC2GBTS_TEMP_SOC,
.ee_par = OC2GBTS_PAR_TEMP_SOC_MAX,
}, {
.name = "fpga_temp",
.has_max = 0,
.sensor = OC2GBTS_TEMP_FPGA,
.ee_par = OC2GBTS_PAR_TEMP_FPGA_MAX,
}, {
.name = "rmsdet_temp",
.has_max = 1,
.sensor = OC2GBTS_TEMP_RMSDET,
.ee_par = OC2GBTS_PAR_TEMP_RMSDET_MAX,
}, {
.name = "ocxo_temp",
.has_max = 1,
.sensor = OC2GBTS_TEMP_OCXO,
.ee_par = OC2GBTS_PAR_TEMP_OCXO_MAX,
}, {
.name = "tx_temp",
.has_max = 0,
.sensor = OC2GBTS_TEMP_TX,
.ee_par = OC2GBTS_PAR_TEMP_TX_MAX,
}, {
.name = "pa_temp",
.has_max = 1,
.sensor = OC2GBTS_TEMP_PA,
.ee_par = OC2GBTS_PAR_TEMP_PA_MAX,
}
};
static const struct {
const char *name;
int has_max;
enum oc2gbts_power_source sensor_source;
enum oc2gbts_power_type sensor_type;
enum oc2gbts_par ee_par;
} power_data[] = {
{
.name = "supply_volt",
.has_max = 1,
.sensor_source = OC2GBTS_POWER_SUPPLY,
.sensor_type = OC2GBTS_POWER_VOLTAGE,
.ee_par = OC2GBTS_PAR_VOLT_SUPPLY_MAX,
}, {
.name = "supply_pwr",
.has_max = 1,
.sensor_source = OC2GBTS_POWER_SUPPLY,
.sensor_type = OC2GBTS_POWER_POWER,
.ee_par = OC2GBTS_PAR_PWR_SUPPLY_MAX,
}, {
.name = "pa_pwr",
.has_max = 1,
.sensor_source = OC2GBTS_POWER_PA,
.sensor_type = OC2GBTS_POWER_POWER,
.ee_par = OC2GBTS_PAR_PWR_PA_MAX,
}
};
static const struct {
const char *name;
int has_max;
enum oc2gbts_vswr_sensor sensor;
enum oc2gbts_par ee_par;
} vswr_data[] = {
{
.name = "vswr",
.has_max = 0,
.sensor = OC2GBTS_VSWR,
.ee_par = OC2GBTS_PAR_VSWR_MAX,
}
};
static const struct value_string power_unit_strs[] = {
{ OC2GBTS_POWER_POWER, "W" },
{ OC2GBTS_POWER_VOLTAGE, "V" },
{ 0, NULL }
};
void oc2gbts_check_temp(int no_rom_write)
{
int temp_old[ARRAY_SIZE(temp_data)];
int temp_cur[ARRAY_SIZE(temp_data)];
int i, rc;
for (i = 0; i < ARRAY_SIZE(temp_data); i++) {
int ret = -99;
if (temp_data[i].sensor == OC2GBTS_TEMP_PA &&
!oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP))
continue;
rc = oc2gbts_par_get_int(temp_data[i].ee_par, &ret);
temp_old[i] = ret * 1000;
oc2gbts_temp_get(temp_data[i].sensor, &temp_cur[i]);
if (temp_cur[i] < 0 && temp_cur[i] > -1000) {
LOGP(DTEMP, LOGL_ERROR, "Error reading temperature (%d)\n", temp_data[i].sensor);
continue;
}
LOGP(DTEMP, LOGL_DEBUG, "Current %s temperature: %d.%d C\n",
temp_data[i].name, temp_cur[i]/1000, temp_cur[i]%1000);
if (temp_cur[i] > temp_old[i]) {
LOGP(DTEMP, LOGL_NOTICE, "New maximum %s "
"temperature: %d.%d C\n", temp_data[i].name,
temp_cur[i]/1000, temp_old[i]%1000);
if (!no_rom_write) {
rc = oc2gbts_par_set_int(temp_data[i].ee_par,
temp_cur[i]/1000);
if (rc < 0)
LOGP(DTEMP, LOGL_ERROR, "error writing new %s "
"max temp %d (%s)\n", temp_data[i].name,
rc, strerror(errno));
}
}
}
}
void oc2gbts_check_power(int no_rom_write)
{
int power_old[ARRAY_SIZE(power_data)];
int power_cur[ARRAY_SIZE(power_data)];
int i, rc;
int div_ratio;
for (i = 0; i < ARRAY_SIZE(power_data); i++) {
int ret = 0;
if (power_data[i].sensor_source == OC2GBTS_POWER_PA &&
!oc2gbts_option_get(OC2GBTS_OPTION_PA))
continue;
rc = oc2gbts_par_get_int(power_data[i].ee_par, &ret);
switch(power_data[i].sensor_type) {
case OC2GBTS_POWER_VOLTAGE:
div_ratio = 1000;
break;
case OC2GBTS_POWER_POWER:
div_ratio = 1000000;
break;
default:
div_ratio = 1000;
}
power_old[i] = ret * div_ratio;
oc2gbts_power_sensor_get(power_data[i].sensor_source, power_data[i].sensor_type, &power_cur[i]);
if (power_cur[i] < 0 && power_cur[i] > -1000) {
LOGP(DTEMP, LOGL_ERROR, "Error reading power (%d) (%d)\n", power_data[i].sensor_source, power_data[i].sensor_type);
continue;
}
LOGP(DTEMP, LOGL_DEBUG, "Current %s power: %d.%d %s\n",
power_data[i].name, power_cur[i]/div_ratio, power_cur[i]%div_ratio,
get_value_string(power_unit_strs, power_data[i].sensor_type));
if (power_cur[i] > power_old[i]) {
LOGP(DTEMP, LOGL_NOTICE, "New maximum %s "
"power: %d.%d %s\n", power_data[i].name,
power_cur[i]/div_ratio, power_cur[i]%div_ratio,
get_value_string(power_unit_strs, power_data[i].sensor_type));
if (!no_rom_write) {
rc = oc2gbts_par_set_int(power_data[i].ee_par,
power_cur[i]/div_ratio);
if (rc < 0)
LOGP(DTEMP, LOGL_ERROR, "error writing new %s "
"max power %d (%s)\n", power_data[i].name,
rc, strerror(errno));
}
}
}
}
void oc2gbts_check_vswr(int no_rom_write)
{
int vswr_old[ARRAY_SIZE(vswr_data)];
int vswr_cur[ARRAY_SIZE(vswr_data)];
int i, rc;
for (i = 0; i < ARRAY_SIZE(vswr_data); i++) {
int ret = 0;
if (vswr_data[i].sensor == OC2GBTS_VSWR &&
(!oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) ||
!oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)))
continue;
rc = oc2gbts_par_get_int(vswr_data[i].ee_par, &ret);
vswr_old[i] = ret * 1000;
oc2gbts_vswr_get(vswr_data[i].sensor, &vswr_cur[i]);
if (vswr_cur[i] < 0 && vswr_cur[i] > -1000) {
LOGP(DTEMP, LOGL_ERROR, "Error reading vswr (%d)\n", vswr_data[i].sensor);
continue;
}
LOGP(DTEMP, LOGL_DEBUG, "Current %s vswr: %d.%d\n",
vswr_data[i].name, vswr_cur[i]/1000, vswr_cur[i]%1000);
if (vswr_cur[i] > vswr_old[i]) {
LOGP(DTEMP, LOGL_NOTICE, "New maximum %s "
"vswr: %d.%d C\n", vswr_data[i].name,
vswr_cur[i]/1000, vswr_old[i]%1000);
if (!no_rom_write) {
rc = oc2gbts_par_set_int(vswr_data[i].ee_par,
vswr_cur[i]/1000);
if (rc < 0)
LOGP(DTEMP, LOGL_ERROR, "error writing new %s "
"max vswr %d (%s)\n", vswr_data[i].name,
rc, strerror(errno));
}
}
}
}
/*********************************************************************
* Hours handling
*********************************************************************/
static time_t last_update;
int oc2gbts_update_hours(int no_rom_write)
{
time_t now = time(NULL);
int rc, op_hrs = 0;
/* first time after start of manager program */
if (last_update == 0) {
last_update = now;
rc = oc2gbts_par_get_int(OC2GBTS_PAR_HOURS, &op_hrs);
if (rc < 0) {
LOGP(DTEMP, LOGL_ERROR, "Unable to read "
"operational hours: %d (%s)\n", rc,
strerror(errno));
/* create a new file anyway */
if (!no_rom_write)
rc = oc2gbts_par_set_int(OC2GBTS_PAR_HOURS, op_hrs);
return rc;
}
LOGP(DTEMP, LOGL_INFO, "Total hours of Operation: %u\n",
op_hrs);
return 0;
}
if (now >= last_update + 3600) {
rc = oc2gbts_par_get_int(OC2GBTS_PAR_HOURS, &op_hrs);
if (rc < 0) {
LOGP(DTEMP, LOGL_ERROR, "Unable to read "
"operational hours: %d (%s)\n", rc,
strerror(errno));
return rc;
}
/* number of hours to increase */
op_hrs += (now-last_update)/3600;
LOGP(DTEMP, LOGL_INFO, "Total hours of Operation: %u\n",
op_hrs);
if (!no_rom_write) {
rc = oc2gbts_par_set_int(OC2GBTS_PAR_HOURS, op_hrs);
if (rc < 0)
return rc;
}
last_update = now;
}
return 0;
}
/*********************************************************************
* Firmware reloading
*********************************************************************/
static const char *fw_sysfs[_NUM_FW] = {
[OC2GBTS_FW_DSP] = "/sys/kernel/debug/remoteproc/remoteproc0/recovery",
};
int oc2gbts_firmware_reload(enum oc2gbts_firmware_type type)
{
int fd;
int rc;
switch (type) {
case OC2GBTS_FW_DSP:
fd = open(fw_sysfs[type], O_WRONLY);
if (fd < 0) {
LOGP(DFW, LOGL_ERROR, "unable ot open firmware device %s: %s\n",
fw_sysfs[type], strerror(errno));
close(fd);
return fd;
}
rc = write(fd, "restart", 8);
if (rc < 8) {
LOGP(DFW, LOGL_ERROR, "short write during "
"fw write to %s\n", fw_sysfs[type]);
close(fd);
return -EIO;
}
close(fd);
default:
return -EINVAL;
}
return 0;
}

View File

@ -0,0 +1,17 @@
#ifndef _OC2GBTS_MISC_H
#define _OC2GBTS_MISC_H
#include <stdint.h>
void oc2gbts_check_temp(int no_rom_write);
void oc2gbts_check_power(int no_rom_write);
void oc2gbts_check_vswr(int no_rom_write);
int oc2gbts_update_hours(int no_rom_write);
enum oc2gbts_firmware_type {
OC2GBTS_FW_DSP,
_NUM_FW
};
#endif

View File

@ -0,0 +1,123 @@
/* Helper for netlink */
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* Based on sysmoBTS:
* sysmobts_nl.c
* (C) 2014 by Holger Hans Peter Freyther
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#define NLMSG_TAIL(nmsg) \
((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
/**
* In case one binds to 0.0.0.0/INADDR_ANY and wants to know which source
* address will be used when sending a message this function can be used.
* It will ask the routing code of the kernel for the PREFSRC
*/
int source_for_dest(const struct in_addr *dest, struct in_addr *loc_source)
{
int fd, rc;
struct rtmsg *r;
struct rtattr *rta;
struct {
struct nlmsghdr n;
struct rtmsg r;
char buf[1024];
} req;
memset(&req, 0, sizeof(req));
fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_ROUTE);
if (fd < 0) {
perror("nl socket");
return -1;
}
/* Send a rtmsg and ask for a response */
req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
req.n.nlmsg_type = RTM_GETROUTE;
req.n.nlmsg_seq = 1;
/* Prepare the routing request */
req.r.rtm_family = AF_INET;
/* set the dest */
rta = NLMSG_TAIL(&req.n);
rta->rta_type = RTA_DST;
rta->rta_len = RTA_LENGTH(sizeof(*dest));
memcpy(RTA_DATA(rta), dest, sizeof(*dest));
/* update sizes for dest */
req.r.rtm_dst_len = sizeof(*dest) * 8;
req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + RTA_ALIGN(rta->rta_len);
rc = send(fd, &req, req.n.nlmsg_len, 0);
if (rc != req.n.nlmsg_len) {
perror("short write");
close(fd);
return -2;
}
/* now receive a response and parse it */
rc = recv(fd, &req, sizeof(req), 0);
if (rc <= 0) {
perror("short read");
close(fd);
return -3;
}
if (!NLMSG_OK(&req.n, rc) || req.n.nlmsg_type != RTM_NEWROUTE) {
close(fd);
return -4;
}
r = NLMSG_DATA(&req.n);
rc -= NLMSG_LENGTH(sizeof(*r));
rta = RTM_RTA(r);
while (RTA_OK(rta, rc)) {
if (rta->rta_type != RTA_PREFSRC) {
rta = RTA_NEXT(rta, rc);
continue;
}
/* we are done */
memcpy(loc_source, RTA_DATA(rta), RTA_PAYLOAD(rta));
close(fd);
return 0;
}
close(fd);
return -5;
}

View File

@ -0,0 +1,27 @@
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* Based on sysmoBTS:
* sysmobts_nl.h
* (C) 2014 by Holger Hans Peter Freyther
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#pragma once
struct in_addr;
int source_for_dest(const struct in_addr *dest, struct in_addr *loc_source);

View File

@ -0,0 +1,249 @@
/* oc2gbts - access to hardware related parameters */
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* Based on sysmoBTS:
* sysmobts_par.c
* (C) 2012 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/talloc.h>
#include "oc2gbts_par.h"
const struct value_string oc2gbts_par_names[_NUM_OC2GBTS_PAR+1] = {
{ OC2GBTS_PAR_TEMP_SUPPLY_MAX, "temp-supply-max" },
{ OC2GBTS_PAR_TEMP_SOC_MAX, "temp-soc-max" },
{ OC2GBTS_PAR_TEMP_FPGA_MAX, "temp-fpga-max" },
{ OC2GBTS_PAR_TEMP_RMSDET_MAX, "temp-rmsdet-max" },
{ OC2GBTS_PAR_TEMP_OCXO_MAX, "temp-ocxo-max" },
{ OC2GBTS_PAR_TEMP_TX_MAX, "temp-tx-max" },
{ OC2GBTS_PAR_TEMP_PA_MAX, "temp-pa-max" },
{ OC2GBTS_PAR_VOLT_SUPPLY_MAX, "volt-supply-max" },
{ OC2GBTS_PAR_PWR_SUPPLY_MAX, "pwr-supply-max" },
{ OC2GBTS_PAR_PWR_PA_MAX, "pwr-pa-max" },
{ OC2GBTS_PAR_VSWR_MAX, "vswr-max" },
{ OC2GBTS_PAR_GPS_FIX, "gps-fix" },
{ OC2GBTS_PAR_SERNR, "serial-nr" },
{ OC2GBTS_PAR_HOURS, "hours-running" },
{ OC2GBTS_PAR_BOOTS, "boot-count" },
{ OC2GBTS_PAR_KEY, "key" },
{ 0, NULL }
};
int oc2gbts_par_is_int(enum oc2gbts_par par)
{
switch (par) {
case OC2GBTS_PAR_TEMP_SUPPLY_MAX:
case OC2GBTS_PAR_TEMP_SOC_MAX:
case OC2GBTS_PAR_TEMP_FPGA_MAX:
case OC2GBTS_PAR_TEMP_RMSDET_MAX:
case OC2GBTS_PAR_TEMP_OCXO_MAX:
case OC2GBTS_PAR_TEMP_TX_MAX:
case OC2GBTS_PAR_TEMP_PA_MAX:
case OC2GBTS_PAR_VOLT_SUPPLY_MAX:
case OC2GBTS_PAR_VSWR_MAX:
case OC2GBTS_PAR_SERNR:
case OC2GBTS_PAR_HOURS:
case OC2GBTS_PAR_BOOTS:
case OC2GBTS_PAR_PWR_SUPPLY_MAX:
case OC2GBTS_PAR_PWR_PA_MAX:
return 1;
default:
return 0;
}
}
FILE *oc2gbts_par_get_path(void *ctx, enum oc2gbts_par par, const char* mode)
{
char *fpath;
FILE *fp;
if (par >= _NUM_OC2GBTS_PAR)
return NULL;
fpath = talloc_asprintf(ctx, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par));
if (!fpath)
return NULL;
fp = fopen(fpath, mode);
if (!fp)
fprintf(stderr, "Failed to open %s due to '%s' error\n", fpath, strerror(errno));
talloc_free(fpath);
return fp;
}
int oc2gbts_par_get_int(enum oc2gbts_par par, int *ret)
{
char fpath[PATH_MAX];
FILE *fp;
int rc;
if (par >= _NUM_OC2GBTS_PAR)
return -ENODEV;
snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par));
fpath[sizeof(fpath)-1] = '\0';
fp = fopen(fpath, "r");
if (fp == NULL) {
return -errno;
}
rc = fscanf(fp, "%d", ret);
if (rc != 1) {
fclose(fp);
return -EIO;
}
fclose(fp);
return 0;
}
int oc2gbts_par_set_int(enum oc2gbts_par par, int val)
{
char fpath[PATH_MAX];
FILE *fp;
int rc;
if (par >= _NUM_OC2GBTS_PAR)
return -ENODEV;
snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par));
fpath[sizeof(fpath)-1] = '\0';
fp = fopen(fpath, "w");
if (fp == NULL) {
return -errno;
}
rc = fprintf(fp, "%d", val);
if (rc < 0) {
fclose(fp);
return -EIO;
}
fsync(fp);
fclose(fp);
return 0;
}
int oc2gbts_par_get_buf(enum oc2gbts_par par, uint8_t *buf,
unsigned int size)
{
char fpath[PATH_MAX];
FILE *fp;
int rc;
if (par >= _NUM_OC2GBTS_PAR)
return -ENODEV;
snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par));
fpath[sizeof(fpath)-1] = '\0';
fp = fopen(fpath, "rb");
if (fp == NULL) {
return -errno;
}
rc = fread(buf, 1, size, fp);
fclose(fp);
return rc;
}
int oc2gbts_par_set_buf(enum oc2gbts_par par, const uint8_t *buf,
unsigned int size)
{
char fpath[PATH_MAX];
FILE *fp;
int rc;
if (par >= _NUM_OC2GBTS_PAR)
return -ENODEV;
snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par));
fpath[sizeof(fpath)-1] = '\0';
fp = fopen(fpath, "wb");
if (fp == NULL) {
return -errno;
}
rc = fwrite(buf, 1, size, fp);
fsync(fp);
fclose(fp);
return rc;
}
int oc2gbts_par_get_gps_fix(void *ctx, time_t *ret)
{
FILE *fp;
int rc;
fp = oc2gbts_par_get_path(ctx, OC2GBTS_PAR_GPS_FIX, "r");
if (fp == NULL) {
return -errno;
}
rc = fscanf(fp, "%ld", ret);
if (rc != 1) {
fclose(fp);
return -EIO;
}
fclose(fp);
return 0;
}
int oc2gbts_par_set_gps_fix(void *ctx, time_t val)
{
FILE *fp;
int rc;
fp = oc2gbts_par_get_path(ctx, OC2GBTS_PAR_GPS_FIX, "w");
if (fp == NULL) {
return -errno;
}
rc = fprintf(fp, "%ld", val);
if (rc < 0) {
fclose(fp);
return -EIO;
}
fsync(fp);
fclose(fp);
return 0;
}

View File

@ -0,0 +1,43 @@
#ifndef _OC2GBTS_PAR_H
#define _OC2GBTS_PAR_H
#include <osmocom/core/utils.h>
#define FACTORY_ROM_PATH "/mnt/rom/factory"
#define USER_ROM_PATH "/var/run/oc2gbts-mgr"
#define UPTIME_TMP_PATH "/tmp/uptime"
enum oc2gbts_par {
OC2GBTS_PAR_TEMP_SUPPLY_MAX,
OC2GBTS_PAR_TEMP_SOC_MAX,
OC2GBTS_PAR_TEMP_FPGA_MAX,
OC2GBTS_PAR_TEMP_RMSDET_MAX,
OC2GBTS_PAR_TEMP_OCXO_MAX,
OC2GBTS_PAR_TEMP_TX_MAX,
OC2GBTS_PAR_TEMP_PA_MAX,
OC2GBTS_PAR_VOLT_SUPPLY_MAX,
OC2GBTS_PAR_PWR_SUPPLY_MAX,
OC2GBTS_PAR_PWR_PA_MAX,
OC2GBTS_PAR_VSWR_MAX,
OC2GBTS_PAR_GPS_FIX,
OC2GBTS_PAR_SERNR,
OC2GBTS_PAR_HOURS,
OC2GBTS_PAR_BOOTS,
OC2GBTS_PAR_KEY,
_NUM_OC2GBTS_PAR
};
extern const struct value_string oc2gbts_par_names[_NUM_OC2GBTS_PAR+1];
int oc2gbts_par_get_int(enum oc2gbts_par par, int *ret);
int oc2gbts_par_set_int(enum oc2gbts_par par, int val);
int oc2gbts_par_get_buf(enum oc2gbts_par par, uint8_t *buf,
unsigned int size);
int oc2gbts_par_set_buf(enum oc2gbts_par par, const uint8_t *buf,
unsigned int size);
int oc2gbts_par_is_int(enum oc2gbts_par par);
int oc2gbts_par_get_gps_fix(void *ctx, time_t *ret);
int oc2gbts_par_set_gps_fix(void *ctx, time_t val);
#endif

View File

@ -0,0 +1,177 @@
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include "oc2gbts_power.h"
static const char *power_enable_devs[_NUM_POWER_SOURCES] = {
[OC2GBTS_POWER_PA] = "/var/oc2g/pa-state/pa0/state",
};
static const char *power_sensor_devs[_NUM_POWER_SOURCES] = {
[OC2GBTS_POWER_SUPPLY] = "/var/oc2g/pwr-sense/main-supply/",
[OC2GBTS_POWER_PA] = "/var/oc2g/pwr-sense/pa0/",
};
static const char *power_sensor_type_str[_NUM_POWER_TYPES] = {
[OC2GBTS_POWER_POWER] = "power",
[OC2GBTS_POWER_VOLTAGE] = "voltage",
[OC2GBTS_POWER_CURRENT] = "current",
};
int oc2gbts_power_sensor_get(
enum oc2gbts_power_source source,
enum oc2gbts_power_type type,
int *power)
{
char buf[PATH_MAX];
char pwrstr[10];
int fd, rc;
if (source >= _NUM_POWER_SOURCES)
return -EINVAL;
if (type >= _NUM_POWER_TYPES)
return -EINVAL;
snprintf(buf, sizeof(buf)-1, "%s%s", power_sensor_devs[source], power_sensor_type_str[type]);
buf[sizeof(buf)-1] = '\0';
fd = open(buf, O_RDONLY);
if (fd < 0)
return fd;
rc = read(fd, pwrstr, sizeof(pwrstr));
pwrstr[sizeof(pwrstr)-1] = '\0';
if (rc < 0) {
close(fd);
return rc;
}
if (rc == 0) {
close(fd);
return -EIO;
}
close(fd);
*power = atoi(pwrstr);
return 0;
}
int oc2gbts_power_set(
enum oc2gbts_power_source source,
int en)
{
int fd;
int rc;
if (source != OC2GBTS_POWER_PA) {
return -EINVAL;
}
fd = open(power_enable_devs[source], O_WRONLY);
if (fd < 0) {
return fd;
}
rc = write(fd, en?"1":"0", 2);
close( fd );
if (rc != 2) {
return -1;
}
if (en) usleep(50*1000);
return 0;
}
int oc2gbts_power_get(
enum oc2gbts_power_source source)
{
int fd;
int rc;
int retVal = 0;
char enstr[10];
fd = open(power_enable_devs[source], O_RDONLY);
if (fd < 0) {
return fd;
}
rc = read(fd, enstr, sizeof(enstr));
enstr[rc-1] = '\0';
close(fd);
if (rc < 0) {
return rc;
}
if (rc == 0) {
return -EIO;
}
rc = strcmp(enstr, "enabled");
if(rc == 0) {
retVal = 1;
}
return retVal;
}
static const char *vswr_devs[_NUM_VSWR_SENSORS] = {
[OC2GBTS_VSWR] = "/var/oc2g/vswr/tx0/vswr",
};
int oc2gbts_vswr_get(enum oc2gbts_vswr_sensor sensor, int *vswr)
{
char buf[PATH_MAX];
char vswrstr[8];
int fd, rc;
if (sensor < 0 || sensor >= _NUM_VSWR_SENSORS)
return -EINVAL;
snprintf(buf, sizeof(buf)-1, "%s", vswr_devs[sensor]);
buf[sizeof(buf)-1] = '\0';
fd = open(buf, O_RDONLY);
if (fd < 0)
return fd;
rc = read(fd, vswrstr, sizeof(vswrstr));
vswrstr[sizeof(vswrstr)-1] = '\0';
if (rc < 0) {
close(fd);
return rc;
}
if (rc == 0) {
close(fd);
return -EIO;
}
close(fd);
*vswr = atoi(vswrstr);
return 0;
}

View File

@ -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

View File

@ -0,0 +1,178 @@
/* Systemd service wd notification for OC-2G BTS management daemon */
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "misc/oc2gbts_mgr.h"
#include "misc/oc2gbts_swd.h"
#include <osmocom/core/logging.h>
/* Needed for service watchdog notification */
#include <systemd/sd-daemon.h>
/* This is the period used to verify if all events have been registered to be allowed
to notify the systemd service watchdog
*/
#define SWD_PERIOD 30
static void swd_start(struct oc2gbts_mgr_instance *mgr);
static void swd_process(struct oc2gbts_mgr_instance *mgr);
static void swd_close(struct oc2gbts_mgr_instance *mgr);
static void swd_state_reset(struct oc2gbts_mgr_instance *mgr, int reason);
static int swd_run(struct oc2gbts_mgr_instance *mgr, int from_loop);
static void swd_loop_run(void *_data);
enum swd_state {
SWD_INITIAL,
SWD_IN_PROGRESS,
};
enum swd_result {
SWD_FAIL_START,
SWD_FAIL_NOTIFY,
SWD_SUCCESS,
};
static void swd_start(struct oc2gbts_mgr_instance *mgr)
{
swd_process(mgr);
}
static void swd_process(struct oc2gbts_mgr_instance *mgr)
{
int rc = 0, notify = 0;
/* Did we get all needed conditions ? */
if (mgr->swd.swd_eventmasks == mgr->swd.swd_events) {
/* Ping systemd service wd if enabled */
rc = sd_notify(0, "WATCHDOG=1");
LOGP(DSWD, LOGL_INFO, "Watchdog notification attempt\n");
notify = 1;
}
else {
LOGP(DSWD, LOGL_INFO, "Missing watchdog events: e:0x%016llx,m:0x%016llx\n",mgr->swd.swd_events,mgr->swd.swd_eventmasks);
}
if (rc < 0) {
LOGP(DSWD, LOGL_ERROR,
"Failed to notify system service watchdog: %d\n", rc);
swd_state_reset(mgr, SWD_FAIL_NOTIFY);
return;
}
else {
/* Did we notified the watchdog? */
if (notify) {
mgr->swd.swd_events = 0;
/* Makes sure we really cleared it in case any event was notified at this same moment (it would be lost) */
if (mgr->swd.swd_events != 0)
mgr->swd.swd_events = 0;
}
}
swd_state_reset(mgr, SWD_SUCCESS);
return;
}
static void swd_close(struct oc2gbts_mgr_instance *mgr)
{
}
static void swd_state_reset(struct oc2gbts_mgr_instance *mgr, int outcome)
{
if (mgr->swd.swd_from_loop) {
mgr->swd.swd_timeout.data = mgr;
mgr->swd.swd_timeout.cb = swd_loop_run;
osmo_timer_schedule(&mgr->swd.swd_timeout, SWD_PERIOD, 0);
}
mgr->swd.state = SWD_INITIAL;
swd_close(mgr);
}
static int swd_run(struct oc2gbts_mgr_instance *mgr, int from_loop)
{
if (mgr->swd.state != SWD_INITIAL) {
LOGP(DSWD, LOGL_ERROR, "Swd is already in progress.\n");
return -1;
}
mgr->swd.swd_from_loop = from_loop;
/* From now on everything will be handled from the failure */
mgr->swd.state = SWD_IN_PROGRESS;
swd_start(mgr);
return 0;
}
static void swd_loop_run(void *_data)
{
int rc;
struct oc2gbts_mgr_instance *mgr = _data;
LOGP(DSWD, LOGL_INFO, "Going to check for watchdog notification.\n");
rc = swd_run(mgr, 1);
if (rc != 0) {
swd_state_reset(mgr, SWD_FAIL_START);
}
}
/* 'swd_num_events' configures the number of events to be monitored before notifying the
systemd service watchdog. It must be in the range of [1,64]. Events are notified
through the function 'oc2gbts_swd_event'
*/
int oc2gbts_swd_init(struct oc2gbts_mgr_instance *mgr, int swd_num_events)
{
/* Checks for a valid number of events to validate */
if (swd_num_events < 1 || swd_num_events > 64)
return(-1);
mgr->swd.state = SWD_INITIAL;
mgr->swd.swd_timeout.data = mgr;
mgr->swd.swd_timeout.cb = swd_loop_run;
osmo_timer_schedule(&mgr->swd.swd_timeout, 0, 0);
if (swd_num_events == 64){
mgr->swd.swd_eventmasks = 0xffffffffffffffffULL;
}
else {
mgr->swd.swd_eventmasks = ((1ULL << swd_num_events) - 1);
}
mgr->swd.swd_events = 0;
mgr->swd.num_events = swd_num_events;
return 0;
}
/* Notifies that the specified event 'swd_event' happened correctly;
the value must be in the range of [0,'swd_num_events'[ (see oc2gbts_swd_init).
For example, if 'swd_num_events' was 64, 'swd_event' events are numbered 0 to 63.
WARNING: if this function can be used from multiple threads at the same time,
it must be protected with a kind of mutex to avoid loosing event notification.
*/
int oc2gbts_swd_event(struct oc2gbts_mgr_instance *mgr, enum mgr_swd_events swd_event)
{
/* Checks for a valid specified event (smaller than max possible) */
if ((int)(swd_event) < 0 || (int)(swd_event) >= mgr->swd.num_events)
return(-1);
mgr->swd.swd_events = mgr->swd.swd_events | ((unsigned long long int)(1) << (int)(swd_event));
/* !!! Uncomment following line to debug events notification */
LOGP(DSWD, LOGL_DEBUG,"Swd event notified: %d\n", (int)(swd_event));
return 0;
}

View File

@ -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

View File

@ -0,0 +1,71 @@
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <osmocom/core/utils.h>
#include "oc2gbts_temp.h"
static const char *temp_devs[_NUM_TEMP_SENSORS] = {
[OC2GBTS_TEMP_SUPPLY] = "/var/oc2g/temp/main-supply/temp",
[OC2GBTS_TEMP_SOC] = "/var/oc2g/temp/cpu/temp",
[OC2GBTS_TEMP_FPGA] = "/var/oc2g/temp/fpga/temp",
[OC2GBTS_TEMP_RMSDET] = "/var/oc2g/temp/rmsdet/temp",
[OC2GBTS_TEMP_OCXO] = "/var/oc2g/temp/ocxo/temp",
[OC2GBTS_TEMP_TX] = "/var/oc2g/temp/tx0/temp",
[OC2GBTS_TEMP_PA] = "/var/oc2g/temp/pa0/temp",
};
int oc2gbts_temp_get(enum oc2gbts_temp_sensor sensor, int *temp)
{
char buf[PATH_MAX];
char tempstr[8];
int fd, rc;
if (sensor < 0 || sensor >= _NUM_TEMP_SENSORS)
return -EINVAL;
snprintf(buf, sizeof(buf)-1, "%s", temp_devs[sensor]);
buf[sizeof(buf)-1] = '\0';
fd = open(buf, O_RDONLY);
if (fd < 0)
return fd;
rc = read(fd, tempstr, sizeof(tempstr));
tempstr[sizeof(tempstr)-1] = '\0';
if (rc < 0) {
close(fd);
return rc;
}
if (rc == 0) {
close(fd);
return -EIO;
}
close(fd);
*temp = atoi(tempstr);
return 0;
}

View File

@ -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

View File

@ -0,0 +1,158 @@
/* oc2gbts-util - access to hardware related parameters */
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* Based on sysmoBTS:
* sysmobts_misc.c
* (C) 2012-2013 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>
#include "oc2gbts_par.h"
enum act {
ACT_GET,
ACT_SET,
};
static enum act action;
static char *write_arg;
static int void_warranty;
static void print_help()
{
const struct value_string *par = oc2gbts_par_names;
printf("oc2gbts-util [--void-warranty -r | -w value] param_name\n");
printf("Possible param names:\n");
for (; par->str != NULL; par += 1) {
if (!oc2gbts_par_is_int(par->value))
continue;
printf(" %s\n", par->str);
}
}
static int parse_options(int argc, char **argv)
{
while (1) {
int option_idx = 0, c;
static const struct option long_options[] = {
{ "help", 0, 0, 'h' },
{ "read", 0, 0, 'r' },
{ "void-warranty", 0, 0, 1000},
{ "write", 1, 0, 'w' },
{ 0, 0, 0, 0 }
};
c = getopt_long(argc, argv, "rw:h",
long_options, &option_idx);
if (c == -1)
break;
switch (c) {
case 'r':
action = ACT_GET;
break;
case 'w':
action = ACT_SET;
write_arg = optarg;
break;
case 'h':
print_help();
return -1;
break;
case 1000:
printf("Will void warranty on write.\n");
void_warranty = 1;
break;
default:
return -1;
}
}
return 0;
}
int main(int argc, char **argv)
{
const char *parname;
enum oc2gbts_par par;
int rc, val;
rc = parse_options(argc, argv);
if (rc < 0)
exit(2);
if (optind >= argc) {
fprintf(stderr, "You must specify the parameter name\n");
exit(2);
}
parname = argv[optind];
rc = get_string_value(oc2gbts_par_names, parname);
if (rc < 0) {
fprintf(stderr, "`%s' is not a valid parameter\n", parname);
exit(2);
} else
par = rc;
switch (action) {
case ACT_GET:
rc = oc2gbts_par_get_int(par, &val);
if (rc < 0) {
fprintf(stderr, "Error %d\n", rc);
goto err;
}
printf("%d\n", val);
break;
case ACT_SET:
rc = oc2gbts_par_get_int(par, &val);
if (rc < 0) {
fprintf(stderr, "Error %d\n", rc);
goto err;
}
if (val != 0xFFFF && val != 0xFF && val != 0xFFFFFFFF && !void_warranty) {
fprintf(stderr, "Parameter is already set!\r\n");
goto err;
}
rc = oc2gbts_par_set_int(par, atoi(write_arg));
if (rc < 0) {
fprintf(stderr, "Error %d\n", rc);
goto err;
}
printf("Success setting %s=%d\n", parname,
atoi(write_arg));
break;
default:
fprintf(stderr, "Unsupported action\n");
goto err;
}
exit(0);
err:
exit(1);
}

361
src/osmo-bts-oc2g/oc2gbts.c Normal file
View File

@ -0,0 +1,361 @@
/* NuRAN Wireless OC-2G L1 API related definitions */
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
* based on:
* sysmobts.c
* (C) 2011 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <nrw/oc2g/oc2g.h>
#include <nrw/oc2g/gsml1const.h>
#include <nrw/oc2g/gsml1dbg.h>
#include "oc2gbts.h"
enum l1prim_type oc2gbts_get_l1prim_type(GsmL1_PrimId_t id)
{
switch (id) {
case GsmL1_PrimId_MphInitReq: return L1P_T_REQ;
case GsmL1_PrimId_MphCloseReq: return L1P_T_REQ;
case GsmL1_PrimId_MphConnectReq: return L1P_T_REQ;
case GsmL1_PrimId_MphDisconnectReq: return L1P_T_REQ;
case GsmL1_PrimId_MphActivateReq: return L1P_T_REQ;
case GsmL1_PrimId_MphDeactivateReq: return L1P_T_REQ;
case GsmL1_PrimId_MphConfigReq: return L1P_T_REQ;
case GsmL1_PrimId_MphMeasureReq: return L1P_T_REQ;
case GsmL1_PrimId_MphInitCnf: return L1P_T_CONF;
case GsmL1_PrimId_MphCloseCnf: return L1P_T_CONF;
case GsmL1_PrimId_MphConnectCnf: return L1P_T_CONF;
case GsmL1_PrimId_MphDisconnectCnf: return L1P_T_CONF;
case GsmL1_PrimId_MphActivateCnf: return L1P_T_CONF;
case GsmL1_PrimId_MphDeactivateCnf: return L1P_T_CONF;
case GsmL1_PrimId_MphConfigCnf: return L1P_T_CONF;
case GsmL1_PrimId_MphMeasureCnf: return L1P_T_CONF;
case GsmL1_PrimId_PhEmptyFrameReq: return L1P_T_REQ;
case GsmL1_PrimId_PhDataReq: return L1P_T_REQ;
case GsmL1_PrimId_MphTimeInd: return L1P_T_IND;
case GsmL1_PrimId_MphSyncInd: return L1P_T_IND;
case GsmL1_PrimId_PhConnectInd: return L1P_T_IND;
case GsmL1_PrimId_PhReadyToSendInd: return L1P_T_IND;
case GsmL1_PrimId_PhDataInd: return L1P_T_IND;
case GsmL1_PrimId_PhRaInd: return L1P_T_IND;
default: return L1P_T_INVALID;
}
}
const struct value_string oc2gbts_l1prim_names[GsmL1_PrimId_NUM+1] = {
{ GsmL1_PrimId_MphInitReq, "MPH-INIT.req" },
{ GsmL1_PrimId_MphCloseReq, "MPH-CLOSE.req" },
{ GsmL1_PrimId_MphConnectReq, "MPH-CONNECT.req" },
{ GsmL1_PrimId_MphDisconnectReq,"MPH-DISCONNECT.req" },
{ GsmL1_PrimId_MphActivateReq, "MPH-ACTIVATE.req" },
{ GsmL1_PrimId_MphDeactivateReq,"MPH-DEACTIVATE.req" },
{ GsmL1_PrimId_MphConfigReq, "MPH-CONFIG.req" },
{ GsmL1_PrimId_MphMeasureReq, "MPH-MEASURE.req" },
{ GsmL1_PrimId_MphInitCnf, "MPH-INIT.conf" },
{ GsmL1_PrimId_MphCloseCnf, "MPH-CLOSE.conf" },
{ GsmL1_PrimId_MphConnectCnf, "MPH-CONNECT.conf" },
{ GsmL1_PrimId_MphDisconnectCnf,"MPH-DISCONNECT.conf" },
{ GsmL1_PrimId_MphActivateCnf, "MPH-ACTIVATE.conf" },
{ GsmL1_PrimId_MphDeactivateCnf,"MPH-DEACTIVATE.conf" },
{ GsmL1_PrimId_MphConfigCnf, "MPH-CONFIG.conf" },
{ GsmL1_PrimId_MphMeasureCnf, "MPH-MEASURE.conf" },
{ GsmL1_PrimId_MphTimeInd, "MPH-TIME.ind" },
{ GsmL1_PrimId_MphSyncInd, "MPH-SYNC.ind" },
{ GsmL1_PrimId_PhEmptyFrameReq, "PH-EMPTY_FRAME.req" },
{ GsmL1_PrimId_PhDataReq, "PH-DATA.req" },
{ GsmL1_PrimId_PhConnectInd, "PH-CONNECT.ind" },
{ GsmL1_PrimId_PhReadyToSendInd,"PH-READY_TO_SEND.ind" },
{ GsmL1_PrimId_PhDataInd, "PH-DATA.ind" },
{ GsmL1_PrimId_PhRaInd, "PH-RA.ind" },
{ 0, NULL }
};
GsmL1_PrimId_t oc2gbts_get_l1prim_conf(GsmL1_PrimId_t id)
{
switch (id) {
case GsmL1_PrimId_MphInitReq: return GsmL1_PrimId_MphInitCnf;
case GsmL1_PrimId_MphCloseReq: return GsmL1_PrimId_MphCloseCnf;
case GsmL1_PrimId_MphConnectReq: return GsmL1_PrimId_MphConnectCnf;
case GsmL1_PrimId_MphDisconnectReq: return GsmL1_PrimId_MphDisconnectCnf;
case GsmL1_PrimId_MphActivateReq: return GsmL1_PrimId_MphActivateCnf;
case GsmL1_PrimId_MphDeactivateReq: return GsmL1_PrimId_MphDeactivateCnf;
case GsmL1_PrimId_MphConfigReq: return GsmL1_PrimId_MphConfigCnf;
case GsmL1_PrimId_MphMeasureReq: return GsmL1_PrimId_MphMeasureCnf;
default: return -1; // Weak
}
}
enum l1prim_type oc2gbts_get_sysprim_type(Oc2g_PrimId_t id)
{
switch (id) {
case Oc2g_PrimId_SystemInfoReq: return L1P_T_REQ;
case Oc2g_PrimId_SystemInfoCnf: return L1P_T_CONF;
case Oc2g_PrimId_SystemFailureInd: return L1P_T_IND;
case Oc2g_PrimId_ActivateRfReq: return L1P_T_REQ;
case Oc2g_PrimId_ActivateRfCnf: return L1P_T_CONF;
case Oc2g_PrimId_DeactivateRfReq: return L1P_T_REQ;
case Oc2g_PrimId_DeactivateRfCnf: return L1P_T_CONF;
case Oc2g_PrimId_SetTraceFlagsReq: return L1P_T_REQ;
case Oc2g_PrimId_Layer1ResetReq: return L1P_T_REQ;
case Oc2g_PrimId_Layer1ResetCnf: return L1P_T_CONF;
case Oc2g_PrimId_SetCalibTblReq: return L1P_T_REQ;
case Oc2g_PrimId_SetCalibTblCnf: return L1P_T_CONF;
case Oc2g_PrimId_MuteRfReq: return L1P_T_REQ;
case Oc2g_PrimId_MuteRfCnf: return L1P_T_CONF;
case Oc2g_PrimId_SetRxAttenReq: return L1P_T_REQ;
case Oc2g_PrimId_SetRxAttenCnf: return L1P_T_CONF;
case Oc2g_PrimId_IsAliveReq: return L1P_T_REQ;
case Oc2g_PrimId_IsAliveCnf: return L1P_T_CONF;
case Oc2g_PrimId_SetMaxCellSizeReq: return L1P_T_REQ;
case Oc2g_PrimId_SetMaxCellSizeCnf: return L1P_T_CONF;
case Oc2g_PrimId_SetC0IdleSlotPowerReductionReq: return L1P_T_REQ;
case Oc2g_PrimId_SetC0IdleSlotPowerReductionCnf: return L1P_T_CONF;
default: return L1P_T_INVALID;
}
}
const struct value_string oc2gbts_sysprim_names[Oc2g_PrimId_NUM+1] = {
{ Oc2g_PrimId_SystemInfoReq, "SYSTEM-INFO.req" },
{ Oc2g_PrimId_SystemInfoCnf, "SYSTEM-INFO.conf" },
{ Oc2g_PrimId_SystemFailureInd, "SYSTEM-FAILURE.ind" },
{ Oc2g_PrimId_ActivateRfReq, "ACTIVATE-RF.req" },
{ Oc2g_PrimId_ActivateRfCnf, "ACTIVATE-RF.conf" },
{ Oc2g_PrimId_DeactivateRfReq, "DEACTIVATE-RF.req" },
{ Oc2g_PrimId_DeactivateRfCnf, "DEACTIVATE-RF.conf" },
{ Oc2g_PrimId_SetTraceFlagsReq, "SET-TRACE-FLAGS.req" },
{ Oc2g_PrimId_Layer1ResetReq, "LAYER1-RESET.req" },
{ Oc2g_PrimId_Layer1ResetCnf, "LAYER1-RESET.conf" },
{ Oc2g_PrimId_SetCalibTblReq, "SET-CALIB.req" },
{ Oc2g_PrimId_SetCalibTblCnf, "SET-CALIB.cnf" },
{ Oc2g_PrimId_MuteRfReq, "MUTE-RF.req" },
{ Oc2g_PrimId_MuteRfCnf, "MUTE-RF.cnf" },
{ Oc2g_PrimId_SetRxAttenReq, "SET-RX-ATTEN.req" },
{ Oc2g_PrimId_SetRxAttenCnf, "SET-RX-ATTEN-CNF.cnf" },
{ Oc2g_PrimId_IsAliveReq, "IS-ALIVE.req" },
{ Oc2g_PrimId_IsAliveCnf, "IS-ALIVE-CNF.cnf" },
{ Oc2g_PrimId_SetMaxCellSizeReq, "SET-MAX-CELL-SIZE.req" },
{ Oc2g_PrimId_SetMaxCellSizeCnf, "SET-MAX-CELL-SIZE.cnf" },
{ Oc2g_PrimId_SetC0IdleSlotPowerReductionReq, "SET-C0-IDLE-PWR-RED.req" },
{ Oc2g_PrimId_SetC0IdleSlotPowerReductionCnf, "SET-C0-IDLE-PWR-RED.cnf" },
{ 0, NULL }
};
Oc2g_PrimId_t oc2gbts_get_sysprim_conf(Oc2g_PrimId_t id)
{
switch (id) {
case Oc2g_PrimId_SystemInfoReq: return Oc2g_PrimId_SystemInfoCnf;
case Oc2g_PrimId_ActivateRfReq: return Oc2g_PrimId_ActivateRfCnf;
case Oc2g_PrimId_DeactivateRfReq: return Oc2g_PrimId_DeactivateRfCnf;
case Oc2g_PrimId_Layer1ResetReq: return Oc2g_PrimId_Layer1ResetCnf;
case Oc2g_PrimId_SetCalibTblReq: return Oc2g_PrimId_SetCalibTblCnf;
case Oc2g_PrimId_MuteRfReq: return Oc2g_PrimId_MuteRfCnf;
case Oc2g_PrimId_SetRxAttenReq: return Oc2g_PrimId_SetRxAttenCnf;
case Oc2g_PrimId_IsAliveReq: return Oc2g_PrimId_IsAliveCnf;
case Oc2g_PrimId_SetMaxCellSizeReq: return Oc2g_PrimId_SetMaxCellSizeCnf;
case Oc2g_PrimId_SetC0IdleSlotPowerReductionReq: return Oc2g_PrimId_SetC0IdleSlotPowerReductionCnf;
default: return -1; // Weak
}
}
const struct value_string oc2gbts_l1sapi_names[GsmL1_Sapi_NUM+1] = {
{ GsmL1_Sapi_Idle, "IDLE" },
{ GsmL1_Sapi_Fcch, "FCCH" },
{ GsmL1_Sapi_Sch, "SCH" },
{ GsmL1_Sapi_Sacch, "SACCH" },
{ GsmL1_Sapi_Sdcch, "SDCCH" },
{ GsmL1_Sapi_Bcch, "BCCH" },
{ GsmL1_Sapi_Pch, "PCH" },
{ GsmL1_Sapi_Agch, "AGCH" },
{ GsmL1_Sapi_Cbch, "CBCH" },
{ GsmL1_Sapi_Rach, "RACH" },
{ GsmL1_Sapi_TchF, "TCH/F" },
{ GsmL1_Sapi_FacchF, "FACCH/F" },
{ GsmL1_Sapi_TchH, "TCH/H" },
{ GsmL1_Sapi_FacchH, "FACCH/H" },
{ GsmL1_Sapi_Nch, "NCH" },
{ GsmL1_Sapi_Pdtch, "PDTCH" },
{ GsmL1_Sapi_Pacch, "PACCH" },
{ GsmL1_Sapi_Pbcch, "PBCCH" },
{ GsmL1_Sapi_Pagch, "PAGCH" },
{ GsmL1_Sapi_Ppch, "PPCH" },
{ GsmL1_Sapi_Pnch, "PNCH" },
{ GsmL1_Sapi_Ptcch, "PTCCH" },
{ GsmL1_Sapi_Prach, "PRACH" },
{ 0, NULL }
};
const struct value_string oc2gbts_l1status_names[GSML1_STATUS_NUM+1] = {
{ GsmL1_Status_Success, "Success" },
{ GsmL1_Status_Generic, "Generic error" },
{ GsmL1_Status_NoMemory, "Not enough memory" },
{ GsmL1_Status_Timeout, "Timeout" },
{ GsmL1_Status_InvalidParam, "Invalid parameter" },
{ GsmL1_Status_Busy, "Resource busy" },
{ GsmL1_Status_NoRessource, "No more resources" },
{ GsmL1_Status_Uninitialized, "Trying to use uninitialized resource" },
{ GsmL1_Status_NullInterface, "Trying to call a NULL interface" },
{ GsmL1_Status_NullFctnPtr, "Trying to call a NULL function ptr" },
{ GsmL1_Status_BadCrc, "Bad CRC" },
{ GsmL1_Status_BadUsf, "Bad USF" },
{ GsmL1_Status_InvalidCPS, "Invalid CPS field" },
{ GsmL1_Status_UnexpectedBurst, "Unexpected burst" },
{ GsmL1_Status_UnavailCodec, "AMR codec is unavailable" },
{ GsmL1_Status_CriticalError, "Critical error" },
{ GsmL1_Status_OverheatError, "Overheat error" },
{ GsmL1_Status_DeviceError, "Device error" },
{ GsmL1_Status_FacchError, "FACCH / TCH order error" },
{ GsmL1_Status_AlreadyDeactivated, "Lchan already deactivated" },
{ GsmL1_Status_TxBurstFifoOvrn, "FIFO overrun" },
{ GsmL1_Status_TxBurstFifoUndr, "FIFO underrun" },
{ GsmL1_Status_NotSynchronized, "Not synchronized" },
{ GsmL1_Status_Unsupported, "Unsupported feature" },
{ GsmL1_Status_ClockError, "System clock error" },
{ 0, NULL }
};
const struct value_string oc2gbts_tracef_names[29] = {
{ DBG_DEBUG, "DEBUG" },
{ DBG_L1WARNING, "L1_WARNING" },
{ DBG_ERROR, "ERROR" },
{ DBG_L1RXMSG, "L1_RX_MSG" },
{ DBG_L1RXMSGBYTE, "L1_RX_MSG_BYTE" },
{ DBG_L1TXMSG, "L1_TX_MSG" },
{ DBG_L1TXMSGBYTE, "L1_TX_MSG_BYTE" },
{ DBG_MPHCNF, "MPH_CNF" },
{ DBG_MPHIND, "MPH_IND" },
{ DBG_MPHREQ, "MPH_REQ" },
{ DBG_PHIND, "PH_IND" },
{ DBG_PHREQ, "PH_REQ" },
{ DBG_PHYRF, "PHY_RF" },
{ DBG_PHYRFMSGBYTE, "PHY_MSG_BYTE" },
{ DBG_MODE, "MODE" },
{ DBG_TDMAINFO, "TDMA_INFO" },
{ DBG_BADCRC, "BAD_CRC" },
{ DBG_PHINDBYTE, "PH_IND_BYTE" },
{ DBG_PHREQBYTE, "PH_REQ_BYTE" },
{ DBG_DEVICEMSG, "DEVICE_MSG" },
{ DBG_RACHINFO, "RACH_INFO" },
{ DBG_LOGCHINFO, "LOG_CH_INFO" },
{ DBG_MEMORY, "MEMORY" },
{ DBG_PROFILING, "PROFILING" },
{ DBG_TESTCOMMENT, "TEST_COMMENT" },
{ DBG_TEST, "TEST" },
{ DBG_STATUS, "STATUS" },
{ 0, NULL }
};
const struct value_string oc2gbts_tracef_docs[29] = {
{ DBG_DEBUG, "Debug Region" },
{ DBG_L1WARNING, "L1 Warning Region" },
{ DBG_ERROR, "Error Region" },
{ DBG_L1RXMSG, "L1_RX_MSG Region" },
{ DBG_L1RXMSGBYTE, "L1_RX_MSG_BYTE Region" },
{ DBG_L1TXMSG, "L1_TX_MSG Region" },
{ DBG_L1TXMSGBYTE, "L1_TX_MSG_BYTE Region" },
{ DBG_MPHCNF, "MphConfirmation Region" },
{ DBG_MPHIND, "MphIndication Region" },
{ DBG_MPHREQ, "MphRequest Region" },
{ DBG_PHIND, "PhIndication Region" },
{ DBG_PHREQ, "PhRequest Region" },
{ DBG_PHYRF, "PhyRF Region" },
{ DBG_PHYRFMSGBYTE, "PhyRF Message Region" },
{ DBG_MODE, "Mode Region" },
{ DBG_TDMAINFO, "TDMA Info Region" },
{ DBG_BADCRC, "Bad CRC Region" },
{ DBG_PHINDBYTE, "PH_IND_BYTE" },
{ DBG_PHREQBYTE, "PH_REQ_BYTE" },
{ DBG_DEVICEMSG, "Device Message Region" },
{ DBG_RACHINFO, "RACH Info" },
{ DBG_LOGCHINFO, "LOG_CH_INFO" },
{ DBG_MEMORY, "Memory Region" },
{ DBG_PROFILING, "Profiling Region" },
{ DBG_TESTCOMMENT, "Test Comments" },
{ DBG_TEST, "Test Region" },
{ DBG_STATUS, "Status Region" },
{ 0, NULL }
};
const struct value_string oc2gbts_tch_pl_names[] = {
{ GsmL1_TchPlType_NA, "N/A" },
{ GsmL1_TchPlType_Fr, "FR" },
{ GsmL1_TchPlType_Hr, "HR" },
{ GsmL1_TchPlType_Efr, "EFR" },
{ GsmL1_TchPlType_Amr, "AMR(IF2)" },
{ GsmL1_TchPlType_Amr_SidBad, "AMR(SID BAD)" },
{ GsmL1_TchPlType_Amr_Onset, "AMR(ONSET)" },
{ GsmL1_TchPlType_Amr_Ratscch, "AMR(RATSCCH)" },
{ GsmL1_TchPlType_Amr_SidUpdateInH, "AMR(SID_UPDATE INH)" },
{ GsmL1_TchPlType_Amr_SidFirstP1, "AMR(SID_FIRST P1)" },
{ GsmL1_TchPlType_Amr_SidFirstP2, "AMR(SID_FIRST P2)" },
{ GsmL1_TchPlType_Amr_SidFirstInH, "AMR(SID_FIRST INH)" },
{ GsmL1_TchPlType_Amr_RatscchMarker, "AMR(RATSCCH MARK)" },
{ GsmL1_TchPlType_Amr_RatscchData, "AMR(RATSCCH DATA)" },
{ 0, NULL }
};
const struct value_string oc2gbts_dir_names[] = {
{ GsmL1_Dir_TxDownlink, "TxDL" },
{ GsmL1_Dir_TxUplink, "TxUL" },
{ GsmL1_Dir_RxUplink, "RxUL" },
{ GsmL1_Dir_RxDownlink, "RxDL" },
{ GsmL1_Dir_TxDownlink|GsmL1_Dir_RxUplink, "BOTH" },
{ 0, NULL }
};
const struct value_string oc2gbts_chcomb_names[] = {
{ GsmL1_LogChComb_0, "dummy" },
{ GsmL1_LogChComb_I, "tch_f" },
{ GsmL1_LogChComb_II, "tch_h" },
{ GsmL1_LogChComb_IV, "ccch" },
{ GsmL1_LogChComb_V, "ccch_sdcch4" },
{ GsmL1_LogChComb_VII, "sdcch8" },
{ GsmL1_LogChComb_XIII, "pdtch" },
{ 0, NULL }
};
const uint8_t pdch_msu_size[_NUM_PDCH_CS] = {
[PDCH_CS_1] = 23,
[PDCH_CS_2] = 34,
[PDCH_CS_3] = 40,
[PDCH_CS_4] = 54,
[PDCH_MCS_1] = 27,
[PDCH_MCS_2] = 33,
[PDCH_MCS_3] = 42,
[PDCH_MCS_4] = 49,
[PDCH_MCS_5] = 60,
[PDCH_MCS_6] = 78,
[PDCH_MCS_7] = 118,
[PDCH_MCS_8] = 142,
[PDCH_MCS_9] = 154
};
const struct value_string oc2gbts_rsl_ho_causes[] = {
{ IPAC_HO_RQD_CAUSE_L_RXLEV_UL_H, "L_RXLEV_UL_H" },
{ IPAC_HO_RQD_CAUSE_L_RXLEV_DL_H, "L_RXLEV_DL_H" },
{ IPAC_HO_RQD_CAUSE_L_RXQUAL_UL_H, "L_RXQUAL_UL_H" },
{ IPAC_HO_RQD_CAUSE_L_RXQUAL_DL_H, "L_RXQUAL_DL_H" },
{ IPAC_HO_RQD_CAUSE_RXLEV_UL_IH, "RXLEV_UL_IH" },
{ IPAC_HO_RQD_CAUSE_RXLEV_DL_IH, "RXLEV_DL_IH" },
{ IPAC_HO_RQD_CAUSE_MAX_MS_RANGE, "MAX_MS_RANGE" },
{ IPAC_HO_RQD_CAUSE_POWER_BUDGET, "POWER_BUDGET" },
{ IPAC_HO_RQD_CAUSE_ENQUIRY, "ENQUIRY" },
{ IPAC_HO_RQD_CAUSE_ENQUIRY_FAILED, "ENQUIRY_FAILED" },
{ 0, NULL }
};

View File

@ -0,0 +1,92 @@
#ifndef OC2GBTS_H
#define OC2GBTS_H
#include <stdlib.h>
#include <osmocom/core/utils.h>
#include <osmocom/gsm/protocol/gsm_08_58.h>
#include <nrw/oc2g/oc2g.h>
#include <nrw/oc2g/gsml1const.h>
/*
* Depending on the firmware version either GsmL1_Prim_t or Oc2g_Prim_t
* is the bigger struct. For earlier firmware versions the GsmL1_Prim_t was the
* bigger struct.
*/
#define OC2GBTS_PRIM_SIZE \
(OSMO_MAX(sizeof(Oc2g_Prim_t), sizeof(GsmL1_Prim_t)) + 128)
enum l1prim_type {
L1P_T_INVALID, /* this must be 0 to detect uninitialized elements */
L1P_T_REQ,
L1P_T_CONF,
L1P_T_IND,
};
enum oc2g_pedestal_mode{
OC2G_PEDESTAL_OFF = 0,
OC2G_PEDESTAL_ON,
};
enum oc2g_led_control_mode{
OC2G_LED_CONTROL_BTS = 0,
OC2G_LED_CONTROL_EXT,
};
enum oc2g_auto_pwr_adjust_mode{
OC2G_TX_PWR_ADJ_NONE = 0,
OC2G_TX_PWR_ADJ_AUTO,
};
enum l1prim_type oc2gbts_get_l1prim_type(GsmL1_PrimId_t id);
const struct value_string oc2gbts_l1prim_names[GsmL1_PrimId_NUM+1];
GsmL1_PrimId_t oc2gbts_get_l1prim_conf(GsmL1_PrimId_t id);
enum l1prim_type oc2gbts_get_sysprim_type(Oc2g_PrimId_t id);
const struct value_string oc2gbts_sysprim_names[Oc2g_PrimId_NUM+1];
Oc2g_PrimId_t oc2gbts_get_sysprim_conf(Oc2g_PrimId_t id);
const struct value_string oc2gbts_l1sapi_names[GsmL1_Sapi_NUM+1];
const struct value_string oc2gbts_l1status_names[GSML1_STATUS_NUM+1];
const struct value_string oc2gbts_tracef_names[29];
const struct value_string oc2gbts_tracef_docs[29];
const struct value_string oc2gbts_tch_pl_names[15];
const struct value_string oc2gbts_clksrc_names[10];
const struct value_string oc2gbts_dir_names[6];
const struct value_string oc2gbts_rsl_ho_causes[IPAC_HO_RQD_CAUSE_MAX];
enum pdch_cs {
PDCH_CS_1,
PDCH_CS_2,
PDCH_CS_3,
PDCH_CS_4,
PDCH_MCS_1,
PDCH_MCS_2,
PDCH_MCS_3,
PDCH_MCS_4,
PDCH_MCS_5,
PDCH_MCS_6,
PDCH_MCS_7,
PDCH_MCS_8,
PDCH_MCS_9,
_NUM_PDCH_CS
};
const uint8_t pdch_msu_size[_NUM_PDCH_CS];
/* OC2G default parameters */
#define OC2G_BTS_MAX_CELL_SIZE_DEFAULT 166 /* 166 qbits is default value */
#define OC2G_BTS_PEDESTAL_MODE_DEFAULT 0 /* Unused TS is off by default */
#define OC2G_BTS_LED_CTRL_MODE_DEFAULT 0 /* LED is controlled by BTS by default */
#define OC2G_BTS_DSP_ALIVE_TMR_DEFAULT 5 /* Default DSP alive timer is 5 seconds */
#define OC2G_BTS_TX_PWR_ADJ_DEFAULT 0 /* Default Tx power auto adjustment is none */
#define OC2G_BTS_TX_RED_PWR_8PSK_DEFAULT 0 /* Default 8-PSK maximum power level is 0 dB */
#define OC2G_BTS_RTP_DRIFT_THRES_DEFAULT 0 /* Default RTP drift threshold is 0 ms (disabled) */
#define OC2G_BTS_TX_C0_IDLE_RED_PWR_DEFAULT 0 /* Default C0 idle slot reduction power level is 0 dB */
#endif /* OC2GBTS_H */

View File

@ -0,0 +1,733 @@
/* VTY interface for NuRAN Wireless OC-2G */
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
* Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org>
*
* Based on sysmoBTS:
* (C) 2011 by Harald Welte <laforge@gnumonks.org>
* (C) 2012,2013 by Holger Hans Peter Freyther
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <stdint.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/select.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/gsm/tlv.h>
#include <osmocom/vty/vty.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/misc.h>
#include <osmocom/ctrl/control_cmd.h>
#include <osmo-bts/signal.h>
#include <osmo-bts/oml.h>
#include <osmo-bts/bts.h>
#include <osmo-bts/gsm_data.h>
#include <osmo-bts/phy_link.h>
#include <osmo-bts/logging.h>
#include <osmo-bts/bts_model.h>
#include <osmo-bts/vty.h>
#include <osmo-bts/rsl.h>
#include "oc2gbts.h"
#include "l1_if.h"
#include "utils.h"
extern int lchan_activate(struct gsm_lchan *lchan);
extern int rsl_tx_preproc_meas_res(struct gsm_lchan *lchan);
#define TRX_STR "Transceiver related commands\n" "TRX number\n"
#define SHOW_TRX_STR \
SHOW_STR \
TRX_STR
#define DSP_TRACE_F_STR "DSP Trace Flag\n"
static struct gsm_bts *vty_bts;
static const struct value_string oc2g_pedestal_mode_strs[] = {
{ OC2G_PEDESTAL_OFF, "off" },
{ OC2G_PEDESTAL_ON, "on" },
{ 0, NULL }
};
static const struct value_string oc2g_led_mode_strs[] = {
{ OC2G_LED_CONTROL_BTS, "bts" },
{ OC2G_LED_CONTROL_EXT, "external" },
{ 0, NULL }
};
static const struct value_string oc2g_auto_adj_pwr_strs[] = {
{ OC2G_TX_PWR_ADJ_NONE, "none" },
{ OC2G_TX_PWR_ADJ_AUTO, "auto" },
{ 0, NULL }
};
/* configuration */
DEFUN(cfg_phy_cal_path, cfg_phy_cal_path_cmd,
"trx-calibration-path PATH",
"Set the path name to TRX calibration data\n" "Path name\n")
{
struct phy_instance *pinst = vty->index;
if (pinst->u.oc2g.calib_path)
talloc_free(pinst->u.oc2g.calib_path);
pinst->u.oc2g.calib_path = talloc_strdup(pinst, argv[0]);
return CMD_SUCCESS;
}
DEFUN(cfg_phy_dsp_trace_f, cfg_phy_dsp_trace_f_cmd,
"HIDDEN", TRX_STR)
{
struct phy_instance *pinst = vty->index;
unsigned int flag;
flag = get_string_value(oc2gbts_tracef_names, argv[1]);
pinst->u.oc2g.dsp_trace_f |= ~flag;
return CMD_SUCCESS;
}
DEFUN(cfg_phy_no_dsp_trace_f, cfg_phy_no_dsp_trace_f_cmd,
"HIDDEN", NO_STR TRX_STR)
{
struct phy_instance *pinst = vty->index;
unsigned int flag;
flag = get_string_value(oc2gbts_tracef_names, argv[1]);
pinst->u.oc2g.dsp_trace_f &= ~flag;
return CMD_SUCCESS;
}
/* runtime */
DEFUN(show_dsp_trace_f, show_dsp_trace_f_cmd,
"show trx <0-0> dsp-trace-flags",
SHOW_TRX_STR "Display the current setting of the DSP trace flags")
{
int trx_nr = atoi(argv[0]);
struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
struct oc2gl1_hdl *fl1h;
int i;
if (!trx)
return CMD_WARNING;
fl1h = trx_oc2gl1_hdl(trx);
vty_out(vty, "Oc2g L1 DSP trace flags:%s", VTY_NEWLINE);
for (i = 0; i < ARRAY_SIZE(oc2gbts_tracef_names); i++) {
const char *endis;
if (oc2gbts_tracef_names[i].value == 0 &&
oc2gbts_tracef_names[i].str == NULL)
break;
if (fl1h->dsp_trace_f & oc2gbts_tracef_names[i].value)
endis = "enabled";
else
endis = "disabled";
vty_out(vty, "DSP Trace %-15s %s%s",
oc2gbts_tracef_names[i].str, endis,
VTY_NEWLINE);
}
return CMD_SUCCESS;
}
DEFUN(dsp_trace_f, dsp_trace_f_cmd, "HIDDEN", TRX_STR)
{
int phy_nr = atoi(argv[0]);
struct phy_instance *pinst;
struct oc2gl1_hdl *fl1h;
unsigned int flag ;
pinst = vty_get_phy_instance(vty, phy_nr, 0);
if (!pinst)
return CMD_WARNING;
fl1h = pinst->u.oc2g.hdl;
flag = get_string_value(oc2gbts_tracef_names, argv[1]);
l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f | flag);
return CMD_SUCCESS;
}
DEFUN(no_dsp_trace_f, no_dsp_trace_f_cmd, "HIDDEN", NO_STR TRX_STR)
{
int phy_nr = atoi(argv[0]);
struct phy_instance *pinst;
struct oc2gl1_hdl *fl1h;
unsigned int flag ;
pinst = vty_get_phy_instance(vty, phy_nr, 0);
if (!pinst)
return CMD_WARNING;
fl1h = pinst->u.oc2g.hdl;
flag = get_string_value(oc2gbts_tracef_names, argv[1]);
l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f & ~flag);
return CMD_SUCCESS;
}
DEFUN(show_sys_info, show_sys_info_cmd,
"show phy <0-0> instance <0-0> system-information",
SHOW_TRX_STR "Display information about system\n")
{
int phy_nr = atoi(argv[0]);
int inst_nr = atoi(argv[1]);
struct phy_link *plink = phy_link_by_num(phy_nr);
struct phy_instance *pinst;
struct oc2gl1_hdl *fl1h;
int i;
if (!plink) {
vty_out(vty, "Cannot find PHY link %u%s",
phy_nr, VTY_NEWLINE);
return CMD_WARNING;
}
pinst = phy_instance_by_num(plink, inst_nr);
if (!plink) {
vty_out(vty, "Cannot find PHY instance %u%s",
phy_nr, VTY_NEWLINE);
return CMD_WARNING;
}
fl1h = pinst->u.oc2g.hdl;
vty_out(vty, "DSP Version: %u.%u.%u, FPGA Version: %u.%u.%u%s",
fl1h->hw_info.dsp_version[0],
fl1h->hw_info.dsp_version[1],
fl1h->hw_info.dsp_version[2],
fl1h->hw_info.fpga_version[0],
fl1h->hw_info.fpga_version[1],
fl1h->hw_info.fpga_version[2], VTY_NEWLINE);
vty_out(vty, "GSM Band Support: ");
for (i = 0; i < sizeof(fl1h->hw_info.band_support); i++) {
if (fl1h->hw_info.band_support & (1 << i))
vty_out(vty, "%s ", gsm_band_name(1 << i));
}
vty_out(vty, "%s", VTY_NEWLINE);
vty_out(vty, "Min Tx Power: %d dBm%s", fl1h->phy_inst->u.oc2g.minTxPower, VTY_NEWLINE);
vty_out(vty, "Max Tx Power: %d dBm%s", fl1h->phy_inst->u.oc2g.maxTxPower, VTY_NEWLINE);
return CMD_SUCCESS;
}
DEFUN(activate_lchan, activate_lchan_cmd,
"trx <0-0> <0-7> (activate|deactivate) <0-7>",
TRX_STR
"Timeslot number\n"
"Activate Logical Channel\n"
"Deactivate Logical Channel\n"
"Logical Channel Number\n" )
{
int trx_nr = atoi(argv[0]);
int ts_nr = atoi(argv[1]);
int lchan_nr = atoi(argv[3]);
struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
if (!strcmp(argv[2], "activate"))
lchan_activate(lchan);
else
lchan_deactivate(lchan);
return CMD_SUCCESS;
}
DEFUN(set_tx_power, set_tx_power_cmd,
"trx nr <0-1> tx-power <-110-100>",
TRX_STR
"TRX number \n"
"Set transmit power (override BSC)\n"
"Transmit power in dBm\n")
{
int trx_nr = atoi(argv[0]);
int power = atoi(argv[1]);
struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
power_ramp_start(trx, to_mdB(power), 1);
return CMD_SUCCESS;
}
DEFUN(loopback, loopback_cmd,
"trx <0-0> <0-7> loopback <0-1>",
TRX_STR
"Timeslot number\n"
"Set TCH loopback\n"
"Logical Channel Number\n")
{
int trx_nr = atoi(argv[0]);
int ts_nr = atoi(argv[1]);
int lchan_nr = atoi(argv[2]);
struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
lchan->loopback = 1;
return CMD_SUCCESS;
}
DEFUN(no_loopback, no_loopback_cmd,
"no trx <0-0> <0-7> loopback <0-1>",
NO_STR TRX_STR
"Timeslot number\n"
"Set TCH loopback\n"
"Logical Channel Number\n")
{
int trx_nr = atoi(argv[0]);
int ts_nr = atoi(argv[1]);
int lchan_nr = atoi(argv[2]);
struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
lchan->loopback = 0;
return CMD_SUCCESS;
}
DEFUN(cfg_trx_nominal_power, cfg_trx_nominal_power_cmd,
"nominal-tx-power <0-40>",
"Set the nominal transmit output power in dBm\n"
"Nominal transmit output power level in dBm\n")
{
int nominal_power = atoi(argv[0]);
struct gsm_bts_trx *trx = vty->index;
if (( nominal_power > 40 ) || ( nominal_power < 0 )) {
vty_out(vty, "Nominal Tx power level must be between 0 and 40 dBm (%d) %s",
nominal_power, VTY_NEWLINE);
return CMD_WARNING;
}
trx->nominal_power = nominal_power;
trx->power_params.trx_p_max_out_mdBm = to_mdB(nominal_power);
return CMD_SUCCESS;
}
DEFUN(cfg_phy_max_cell_size, cfg_phy_max_cell_size_cmd,
"max-cell-size <0-166>",
"Set the maximum cell size in qbits\n")
{
struct phy_instance *pinst = vty->index;
int cell_size = (uint8_t)atoi(argv[0]);
if (( cell_size > 166 ) || ( cell_size < 0 )) {
vty_out(vty, "Max cell size must be between 0 and 166 qbits (%d) %s",
cell_size, VTY_NEWLINE);
return CMD_WARNING;
}
pinst->u.oc2g.max_cell_size = (uint8_t)cell_size;
return CMD_SUCCESS;
}
DEFUN(cfg_phy_pedestal_mode, cfg_phy_pedestal_mode_cmd,
"pedestal-mode (on|off)",
"Set unused time-slot transmission in pedestal mode\n"
"Transmission pedestal mode can be (off, on)\n")
{
struct phy_instance *pinst = vty->index;
int val = get_string_value(oc2g_pedestal_mode_strs, argv[0]);
if((val < OC2G_PEDESTAL_OFF) || (val > OC2G_PEDESTAL_ON)) {
vty_out(vty, "Invalid unused time-slot transmission mode %d%s", val, VTY_NEWLINE);
return CMD_WARNING;
}
pinst->u.oc2g.pedestal_mode = (uint8_t)val;
return CMD_SUCCESS;
}
DEFUN(cfg_phy_dsp_alive_timer, cfg_phy_dsp_alive_timer_cmd,
"dsp-alive-period <0-60>",
"Set DSP alive timer period in second\n")
{
struct phy_instance *pinst = vty->index;
uint8_t period = (uint8_t)atoi(argv[0]);
if (( period > 60 ) || ( period < 0 )) {
vty_out(vty, "DSP heart beat alive timer period must be between 0 and 60 seconds (%d) %s",
period, VTY_NEWLINE);
return CMD_WARNING;
}
pinst->u.oc2g.dsp_alive_period = period;
return CMD_SUCCESS;
}
DEFUN(cfg_phy_auto_tx_pwr_adj, cfg_phy_auto_tx_pwr_adj_cmd,
"pwr-adj-mode (none|auto)",
"Set output power adjustment mode\n")
{
struct phy_instance *pinst = vty->index;
int val = get_string_value(oc2g_auto_adj_pwr_strs, argv[0]);
if((val < OC2G_TX_PWR_ADJ_NONE) || (val > OC2G_TX_PWR_ADJ_AUTO)) {
vty_out(vty, "Invalid output power adjustment mode %d%s", val, VTY_NEWLINE);
return CMD_WARNING;
}
pinst->u.oc2g.tx_pwr_adj_mode = (uint8_t)val;
return CMD_SUCCESS;
}
DEFUN(cfg_phy_tx_red_pwr_8psk, cfg_phy_tx_red_pwr_8psk_cmd,
"tx-red-pwr-8psk <0-40>",
"Set reduction output power for 8-PSK scheme in dB unit\n")
{
struct phy_instance *pinst = vty->index;
int val = atoi(argv[0]);
if ((val > 40) || (val < 0)) {
vty_out(vty, "Reduction Tx power level must be between 0 and 40 dB (%d) %s",
val, VTY_NEWLINE);
return CMD_WARNING;
}
pinst->u.oc2g.tx_pwr_red_8psk = (uint8_t)val;
return CMD_SUCCESS;
}
DEFUN(cfg_phy_c0_idle_red_pwr, cfg_phy_c0_idle_red_pwr_cmd,
"c0-idle-red-pwr <0-40>",
"Set reduction output power for C0 idle slot in dB unit\n")
{
struct phy_instance *pinst = vty->index;
int val = atoi(argv[0]);
if ((val > 40) || (val < 0)) {
vty_out(vty, "Reduction Tx power level must be between 0 and 40 dB (%d) %s",
val, VTY_NEWLINE);
return CMD_WARNING;
}
pinst->u.oc2g.tx_c0_idle_pwr_red = (uint8_t)val;
return CMD_SUCCESS;
}
DEFUN(trigger_ho_cause, trigger_ho_cause_cmd, "HIDDEN", TRX_STR)
{
struct gsm_network *net = gsmnet_from_vty(vty);
struct gsm_bts *bts;
struct gsm_bts_trx *trx;
struct gsm_bts_trx_ts *ts;
struct gsm_lchan *lchan;
int trx_nr, ts_nr, lchan_nr;
uint8_t ho_cause;
uint8_t old_ho_cause;
/* get BTS pointer */
bts = gsm_bts_num(net, 0);
if (!bts) {
vty_out(vty, "Can not get BTS node %s", VTY_NEWLINE);
return CMD_WARNING;
}
/* get TRX pointer */
if (argc >= 1) {
trx_nr = atoi(argv[0]);
if (trx_nr >= bts->num_trx) {
vty_out(vty, "%% can't find TRX %s%s", argv[0],
VTY_NEWLINE);
return CMD_WARNING;
}
trx = gsm_bts_trx_num(bts, trx_nr);
}
/* get TS pointer */
if (argc >= 2) {
ts_nr = atoi(argv[1]);
if (ts_nr >= TRX_NR_TS) {
vty_out(vty, "%% can't find TS %s%s", argv[1],
VTY_NEWLINE);
return CMD_WARNING;
}
ts = &trx->ts[ts_nr];
}
/* get lchan pointer */
if (argc >= 3) {
lchan_nr = atoi(argv[2]);
if (lchan_nr >= TS_MAX_LCHAN) {
vty_out(vty, "%% can't find LCHAN %s%s", argv[2],
VTY_NEWLINE);
return CMD_WARNING;
}
lchan = &ts->lchan[lchan_nr];
}
/* get HO cause */
if (argc >= 4) {
ho_cause = get_string_value(oc2gbts_rsl_ho_causes, argv[3]);
if (ho_cause >= IPAC_HO_RQD_CAUSE_MAX) {
vty_out(vty, "%% can't find valid HO cause %s%s", argv[3],
VTY_NEWLINE);
return CMD_WARNING;
}
} else {
vty_out(vty, "%% HO cause is not provided %s", VTY_NEWLINE);
return CMD_WARNING;
}
/* TODO(oramadan) MERGE
/* store recorded HO causes * /
old_ho_cause = lchan->meas_preproc.rec_ho_causes;
/* Apply new HO causes * /
lchan->meas_preproc.rec_ho_causes = 1 << (ho_cause - 1);
/* Send measuremnt report to BSC * /
rsl_tx_preproc_meas_res(lchan);
/* restore HO cause * /
lchan->meas_preproc.rec_ho_causes = old_ho_cause;
*/
return CMD_SUCCESS;
}
/* TODO(oramadan) MERGE
DEFUN(cfg_bts_rtp_drift_threshold, cfg_bts_rtp_drift_threshold_cmd,
"rtp-drift-threshold <0-10000>",
"RTP parameters\n"
"RTP timestamp drift threshold in ms\n")
{
struct gsm_bts *bts = vty->index;
struct gsm_bts_role_bts *btsb = bts_role_bts(bts);
btsb->oc2g.rtp_drift_thres_ms = (unsigned int) atoi(argv[0]);
return CMD_SUCCESS;
}
*/
void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts)
{
/* TODO(oramadan) MERGE
struct gsm_bts_role_bts *btsb = bts_role_bts(bts);
vty_out(vty, " led-control-mode %s%s",
get_value_string(oc2g_led_mode_strs, btsb->oc2g.led_ctrl_mode), VTY_NEWLINE);
vty_out(vty, " rtp-drift-threshold %d%s",
btsb->oc2g.rtp_drift_thres_ms, VTY_NEWLINE);
*/
}
void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx)
{
vty_out(vty, " nominal-tx-power %d%s", trx->nominal_power,VTY_NEWLINE);
}
void bts_model_config_write_phy(struct vty *vty, struct phy_link *plink)
{
}
void bts_model_config_write_phy_inst(struct vty *vty, struct phy_instance *pinst)
{
int i;
for (i = 0; i < 32; i++) {
if (pinst->u.oc2g.dsp_trace_f & (1 << i)) {
const char *name;
name = get_value_string(oc2gbts_tracef_names, (1 << i));
vty_out(vty, " dsp-trace-flag %s%s", name,
VTY_NEWLINE);
}
}
if (pinst->u.oc2g.calib_path)
vty_out(vty, " trx-calibration-path %s%s",
pinst->u.oc2g.calib_path, VTY_NEWLINE);
vty_out(vty, " max-cell-size %d%s",
pinst->u.oc2g.max_cell_size, VTY_NEWLINE);
vty_out(vty, " pedestal-mode %s%s",
get_value_string(oc2g_pedestal_mode_strs, pinst->u.oc2g.pedestal_mode) , VTY_NEWLINE);
vty_out(vty, " dsp-alive-period %d%s",
pinst->u.oc2g.dsp_alive_period, VTY_NEWLINE);
vty_out(vty, " pwr-adj-mode %s%s",
get_value_string(oc2g_auto_adj_pwr_strs, pinst->u.oc2g.tx_pwr_adj_mode), VTY_NEWLINE);
vty_out(vty, " tx-red-pwr-8psk %d%s",
pinst->u.oc2g.tx_pwr_red_8psk, VTY_NEWLINE);
vty_out(vty, " c0-idle-red-pwr %d%s",
pinst->u.oc2g.tx_c0_idle_pwr_red, VTY_NEWLINE);
}
int bts_model_vty_init(struct gsm_bts *bts)
{
vty_bts = bts;
/* runtime-patch the command strings with debug levels */
dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, oc2gbts_tracef_names,
"phy <0-1> dsp-trace-flag (",
"|",")", VTY_DO_LOWER);
dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, oc2gbts_tracef_docs,
TRX_STR DSP_TRACE_F_STR,
"\n", "", 0);
no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, oc2gbts_tracef_names,
"no phy <0-1> dsp-trace-flag (",
"|",")", VTY_DO_LOWER);
no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, oc2gbts_tracef_docs,
NO_STR TRX_STR DSP_TRACE_F_STR,
"\n", "", 0);
cfg_phy_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts,
oc2gbts_tracef_names,
"dsp-trace-flag (",
"|",")", VTY_DO_LOWER);
cfg_phy_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts,
oc2gbts_tracef_docs,
DSP_TRACE_F_STR,
"\n", "", 0);
cfg_phy_no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts,
oc2gbts_tracef_names,
"no dsp-trace-flag (",
"|",")", VTY_DO_LOWER);
cfg_phy_no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts,
oc2gbts_tracef_docs,
NO_STR DSP_TRACE_F_STR,
"\n", "", 0);
trigger_ho_cause_cmd.string = vty_cmd_string_from_valstr(bts,
oc2gbts_rsl_ho_causes,
"trigger-ho-cause trx <0-1> ts <0-7> lchan <0-1> cause (",
"|",")", VTY_DO_LOWER);
install_element_ve(&show_dsp_trace_f_cmd);
install_element_ve(&show_sys_info_cmd);
install_element_ve(&dsp_trace_f_cmd);
install_element_ve(&no_dsp_trace_f_cmd);
install_element(ENABLE_NODE, &activate_lchan_cmd);
install_element(ENABLE_NODE, &set_tx_power_cmd);
install_element(ENABLE_NODE, &loopback_cmd);
install_element(ENABLE_NODE, &no_loopback_cmd);
install_element(ENABLE_NODE, &trigger_ho_cause_cmd);
install_element(BTS_NODE, &cfg_bts_auto_band_cmd);
install_element(BTS_NODE, &cfg_bts_no_auto_band_cmd);
/* TODO(oramadan) MERGE
install_element(BTS_NODE, &cfg_bts_rtp_drift_threshold_cmd);
*/
install_element(TRX_NODE, &cfg_trx_nominal_power_cmd);
install_element(PHY_INST_NODE, &cfg_phy_dsp_trace_f_cmd);
install_element(PHY_INST_NODE, &cfg_phy_no_dsp_trace_f_cmd);
install_element(PHY_INST_NODE, &cfg_phy_cal_path_cmd);
install_element(PHY_INST_NODE, &cfg_phy_pedestal_mode_cmd);
install_element(PHY_INST_NODE, &cfg_phy_max_cell_size_cmd);
install_element(PHY_INST_NODE, &cfg_phy_dsp_alive_timer_cmd);
install_element(PHY_INST_NODE, &cfg_phy_auto_tx_pwr_adj_cmd);
install_element(PHY_INST_NODE, &cfg_phy_tx_red_pwr_8psk_cmd);
install_element(PHY_INST_NODE, &cfg_phy_c0_idle_red_pwr_cmd);
return 0;
}
/* TODO(oramadan) MERGE
/* OC2G BTS control interface * /
CTRL_CMD_DEFINE_WO_NOVRF(oc2g_oml_alert, "oc2g-oml-alert");
static int set_oc2g_oml_alert(struct ctrl_cmd *cmd, void *data)
{
struct gsm_bts *bts = cmd->node;
int cause = atoi(cmd->value);
char *saveptr = NULL;
alarm_sig_data.mo = &bts->mo;
cause = atoi(strtok_r(cmd->value, ",", &saveptr));
alarm_sig_data.event_serverity = (cause >> 8) & 0x0F;
alarm_sig_data.add_text = strtok_r(NULL, "\n", &saveptr);
memcpy(alarm_sig_data.spare, &cause, sizeof(int));
LOGP(DLCTRL, LOGL_NOTICE, "BTS received MGR alarm cause=%d, text=%s\n", cause, alarm_sig_data.add_text);
/* dispatch OML alarm signal * /
osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_MGR_ALARM, &alarm_sig_data);
/* return with same alarm cause to MGR rather than OK string* /
cmd->reply = talloc_asprintf(cmd, "%d", cause);
return CTRL_CMD_REPLY;
}
CTRL_CMD_DEFINE_WO_NOVRF(oc2g_oml_ceased, "oc2g-oml-ceased");
static int set_oc2g_oml_ceased(struct ctrl_cmd *cmd, void *data)
{
struct gsm_bts *bts = cmd->node;
int cause = atoi(cmd->value);
char *saveptr = NULL;
alarm_sig_data.mo = &bts->mo;
cause = atoi(strtok_r(cmd->value, ",", &saveptr));
alarm_sig_data.add_text = strtok_r(NULL, "\n", &saveptr);
memcpy(alarm_sig_data.spare, &cause, sizeof(int));
LOGP(DLCTRL, LOGL_NOTICE, "BTS received MGR ceased alarm cause=%d, text=%s\n", cause, alarm_sig_data.add_text);
/* dispatch OML alarm signal * /
osmo_signal_dispatch(SS_NM, S_NM_OML_BTS_MGR_CEASED_ALARM, &alarm_sig_data);
*/
/* return with same alarm cause to MGR rather than OK string* /
cmd->reply = talloc_asprintf(cmd, "%d", cause);
return CTRL_CMD_REPLY;
}
*/
int bts_model_ctrl_cmds_install(struct gsm_bts *bts)
{
int rc = 0;
/* TODO(oramadan) MERGE
rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_oc2g_oml_alert);
rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_oc2g_oml_ceased);
*/
return 0;
}

2078
src/osmo-bts-oc2g/oml.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,132 @@
/* Beginnings of an OML router */
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* Based on sysmoBTS:
* (C) 2014 by sysmocom s.f.m.c. GmbH
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "oml_router.h"
#include <osmo-bts/bts.h>
#include <osmo-bts/logging.h>
#include <osmo-bts/oml.h>
#include <osmo-bts/msg_utils.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/select.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
static int oml_router_read_cb(struct osmo_fd *fd, unsigned int what)
{
struct msgb *msg;
int rc;
msg = oml_msgb_alloc();
if (!msg) {
LOGP(DL1C, LOGL_ERROR, "Failed to allocate oml msgb.\n");
return -1;
}
rc = recv(fd->fd, msg->tail, msg->data_len, 0);
if (rc <= 0) {
close(fd->fd);
osmo_fd_unregister(fd);
fd->fd = -1;
goto err;
}
msg->l1h = msgb_put(msg, rc);
rc = msg_verify_ipa_structure(msg);
if (rc < 0) {
LOGP(DL1C, LOGL_ERROR,
"OML Router: Invalid IPA message rc(%d)\n", rc);
goto err;
}
rc = msg_verify_oml_structure(msg);
if (rc < 0) {
LOGP(DL1C, LOGL_ERROR,
"OML Router: Invalid OML message rc(%d)\n", rc);
goto err;
}
/* todo dispatch message */
err:
msgb_free(msg);
return -1;
}
static int oml_router_accept_cb(struct osmo_fd *accept_fd, unsigned int what)
{
int fd;
struct osmo_fd *read_fd = (struct osmo_fd *) accept_fd->data;
/* Accept only one connection at a time. De-register it */
if (read_fd->fd > -1) {
LOGP(DL1C, LOGL_NOTICE,
"New OML router connection. Closing old one.\n");
close(read_fd->fd);
osmo_fd_unregister(read_fd);
read_fd->fd = -1;
}
fd = accept(accept_fd->fd, NULL, NULL);
if (fd < 0) {
LOGP(DL1C, LOGL_ERROR, "Failed to accept. errno: %s.\n",
strerror(errno));
return -1;
}
read_fd->fd = fd;
if (osmo_fd_register(read_fd) != 0) {
LOGP(DL1C, LOGL_ERROR, "Registering the read fd failed.\n");
close(fd);
read_fd->fd = -1;
return -1;
}
return 0;
}
int oml_router_init(struct gsm_bts *bts, const char *path,
struct osmo_fd *accept_fd, struct osmo_fd *read_fd)
{
int rc;
memset(accept_fd, 0, sizeof(*accept_fd));
memset(read_fd, 0, sizeof(*read_fd));
accept_fd->cb = oml_router_accept_cb;
accept_fd->data = read_fd;
read_fd->cb = oml_router_read_cb;
read_fd->data = bts;
read_fd->when = BSC_FD_READ;
read_fd->fd = -1;
rc = osmo_sock_unix_init_ofd(accept_fd, SOCK_SEQPACKET, 0,
path,
OSMO_SOCK_F_BIND | OSMO_SOCK_F_NONBLOCK);
return rc;
}

View File

@ -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);

545
src/osmo-bts-oc2g/tch.c Normal file
View File

@ -0,0 +1,545 @@
/* Traffic channel support for NuRAN Wireless OC-2G BTS L1 */
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* Based on sysmoBTS:
* (C) 2011-2012 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/select.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/bits.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmo-bts/logging.h>
#include <osmo-bts/bts.h>
#include <osmo-bts/gsm_data.h>
#include <osmo-bts/msg_utils.h>
#include <osmo-bts/measurement.h>
#include <osmo-bts/amr.h>
#include <osmo-bts/l1sap.h>
#include <osmo-bts/dtx_dl_amr_fsm.h>
#include <nrw/oc2g/oc2g.h>
#include <nrw/oc2g/gsml1prim.h>
#include <nrw/oc2g/gsml1const.h>
#include <nrw/oc2g/gsml1types.h>
#include "oc2gbts.h"
#include "l1_if.h"
static struct msgb *l1_to_rtppayload_fr(uint8_t *l1_payload, uint8_t payload_len,
struct gsm_lchan *lchan)
{
struct msgb *msg;
uint8_t *cur;
msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP");
if (!msg)
return NULL;
/* new L1 can deliver bits like we need them */
cur = msgb_put(msg, GSM_FR_BYTES);
memcpy(cur, l1_payload, GSM_FR_BYTES);
lchan_set_marker(osmo_fr_check_sid(l1_payload, payload_len), lchan);
return msg;
}
/*! \brief convert GSM-FR from RTP payload to L1 format
* \param[out] l1_payload payload part of L1 buffer
* \param[in] rtp_payload pointer to RTP payload data
* \param[in] payload_len length of \a rtp_payload
* \returns number of \a l1_payload bytes filled
*/
static int rtppayload_to_l1_fr(uint8_t *l1_payload, const uint8_t *rtp_payload,
unsigned int payload_len)
{
/* new L1 can deliver bits like we need them */
memcpy(l1_payload, rtp_payload, GSM_FR_BYTES);
return GSM_FR_BYTES;
}
static struct msgb *l1_to_rtppayload_efr(uint8_t *l1_payload,
uint8_t payload_len,
struct gsm_lchan *lchan)
{
struct msgb *msg;
uint8_t *cur;
msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP");
if (!msg)
return NULL;
/* new L1 can deliver bits like we need them */
cur = msgb_put(msg, GSM_EFR_BYTES);
memcpy(cur, l1_payload, GSM_EFR_BYTES);
enum osmo_amr_type ft;
enum osmo_amr_quality bfi;
uint8_t cmr;
int8_t sti, cmi;
osmo_amr_rtp_dec(l1_payload, payload_len, &cmr, &cmi, &ft, &bfi, &sti);
lchan_set_marker(ft == AMR_GSM_EFR_SID, lchan);
return msg;
}
static int rtppayload_to_l1_efr(uint8_t *l1_payload, const uint8_t *rtp_payload,
unsigned int payload_len)
{
memcpy(l1_payload, rtp_payload, payload_len);
return payload_len;
}
static struct msgb *l1_to_rtppayload_hr(uint8_t *l1_payload, uint8_t payload_len,
struct gsm_lchan *lchan)
{
struct msgb *msg;
uint8_t *cur;
msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP");
if (!msg)
return NULL;
if (payload_len != GSM_HR_BYTES) {
LOGP(DL1P, LOGL_ERROR, "L1 HR frame length %u != expected %u\n",
payload_len, GSM_HR_BYTES);
return NULL;
}
cur = msgb_put(msg, GSM_HR_BYTES);
memcpy(cur, l1_payload, GSM_HR_BYTES);
lchan_set_marker(osmo_hr_check_sid(l1_payload, payload_len), lchan);
return msg;
}
/*! \brief convert GSM-FR from RTP payload to L1 format
* \param[out] l1_payload payload part of L1 buffer
* \param[in] rtp_payload pointer to RTP payload data
* \param[in] payload_len length of \a rtp_payload
* \returns number of \a l1_payload bytes filled
*/
static int rtppayload_to_l1_hr(uint8_t *l1_payload, const uint8_t *rtp_payload,
unsigned int payload_len)
{
if (payload_len != GSM_HR_BYTES) {
LOGP(DL1P, LOGL_ERROR, "RTP HR frame length %u != expected %u\n",
payload_len, GSM_HR_BYTES);
return 0;
}
memcpy(l1_payload, rtp_payload, GSM_HR_BYTES);
return GSM_HR_BYTES;
}
static struct msgb *l1_to_rtppayload_amr(uint8_t *l1_payload, uint8_t payload_len,
struct gsm_lchan *lchan)
{
struct msgb *msg;
uint8_t amr_if2_len = payload_len - 2;
uint8_t *cur;
msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP");
if (!msg)
return NULL;
cur = msgb_put(msg, amr_if2_len);
memcpy(cur, l1_payload+2, amr_if2_len);
/*
* Audiocode's MGW doesn't like receiving CMRs that are not
* the same as the previous one. This means we need to patch
* the content here.
*/
if ((cur[0] & 0xF0) == 0xF0)
cur[0]= lchan->tch.last_cmr << 4;
else
lchan->tch.last_cmr = cur[0] >> 4;
return msg;
}
/*! \brief convert AMR from RTP payload to L1 format
* \param[out] l1_payload payload part of L1 buffer
* \param[in] rtp_payload pointer to RTP payload data
* \param[in] payload_len length of \a rtp_payload
* \returns number of \a l1_payload bytes filled
*/
static int rtppayload_to_l1_amr(uint8_t *l1_payload, const uint8_t *rtp_payload,
uint8_t payload_len, uint8_t ft)
{
memcpy(l1_payload, rtp_payload, payload_len);
return payload_len;
}
#define RTP_MSGB_ALLOC_SIZE 512
/*! \brief function for incoming RTP via TCH.req
* \param[in] rtp_pl buffer containing RTP payload
* \param[in] rtp_pl_len length of \a rtp_pl
* \param[in] use_cache Use cached payload instead of parsing RTP
* \param[in] marker RTP header Marker bit (indicates speech onset)
* \returns 0 if encoding result can be sent further to L1 without extra actions
* positive value if data is ready AND extra actions are required
* negative value otherwise (no data for L1 encoded)
*
* This function prepares a msgb with a L1 PH-DATA.req primitive and
* queues it into lchan->dl_tch_queue.
*
* Note that the actual L1 primitive header is not fully initialized
* yet, as things like the frame number, etc. are unknown at the time we
* pre-fill the primtive.
*/
int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len,
const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn,
bool use_cache, bool marker)
{
uint8_t *payload_type;
uint8_t *l1_payload, ft;
int rc = 0;
bool is_sid = false;
DEBUGP(DRTP, "%s RTP IN: %s\n", gsm_lchan_name(lchan),
osmo_hexdump(rtp_pl, rtp_pl_len));
payload_type = &data[0];
l1_payload = &data[1];
switch (lchan->tch_mode) {
case GSM48_CMODE_SPEECH_V1:
if (lchan->type == GSM_LCHAN_TCH_F) {
*payload_type = GsmL1_TchPlType_Fr;
rc = rtppayload_to_l1_fr(l1_payload,
rtp_pl, rtp_pl_len);
if (rc && lchan->ts->trx->bts->dtxd)
is_sid = osmo_fr_check_sid(rtp_pl, rtp_pl_len);
} else{
*payload_type = GsmL1_TchPlType_Hr;
rc = rtppayload_to_l1_hr(l1_payload,
rtp_pl, rtp_pl_len);
if (rc && lchan->ts->trx->bts->dtxd)
is_sid = osmo_hr_check_sid(rtp_pl, rtp_pl_len);
}
if (is_sid)
dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, -1);
break;
case GSM48_CMODE_SPEECH_EFR:
*payload_type = GsmL1_TchPlType_Efr;
rc = rtppayload_to_l1_efr(l1_payload, rtp_pl,
rtp_pl_len);
/* FIXME: detect and save EFR SID */
break;
case GSM48_CMODE_SPEECH_AMR:
if (use_cache) {
*payload_type = GsmL1_TchPlType_Amr;
rtppayload_to_l1_amr(l1_payload, lchan->tch.dtx.cache,
lchan->tch.dtx.len, ft);
*len = lchan->tch.dtx.len + 1;
return 0;
}
rc = dtx_dl_amr_fsm_step(lchan, rtp_pl, rtp_pl_len, fn,
l1_payload, marker, len, &ft);
if (rc < 0)
return rc;
if (!dtx_dl_amr_enabled(lchan)) {
*payload_type = GsmL1_TchPlType_Amr;
rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
ft);
return 0;
}
/* DTX DL-specific logic below: */
switch (lchan->tch.dtx.dl_amr_fsm->state) {
case ST_ONSET_V:
*payload_type = GsmL1_TchPlType_Amr_Onset;
dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0);
*len = 3;
return 1;
case ST_VOICE:
*payload_type = GsmL1_TchPlType_Amr;
rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
ft);
return 0;
case ST_SID_F1:
if (lchan->type == GSM_LCHAN_TCH_H) { /* AMR HR */
*payload_type = GsmL1_TchPlType_Amr_SidFirstP1;
rtppayload_to_l1_amr(l1_payload + 2, rtp_pl,
rtp_pl_len, ft);
return 0;
}
/* AMR FR */
*payload_type = GsmL1_TchPlType_Amr;
rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
ft);
return 0;
case ST_SID_F2:
*payload_type = GsmL1_TchPlType_Amr;
rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
ft);
return 0;
case ST_F1_INH_V:
*payload_type = GsmL1_TchPlType_Amr_SidFirstInH;
*len = 3;
dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0);
return 1;
case ST_U_INH_V:
*payload_type = GsmL1_TchPlType_Amr_SidUpdateInH;
*len = 3;
dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0);
return 1;
case ST_SID_U:
case ST_U_NOINH:
return -EAGAIN;
case ST_FACCH:
return -EBADMSG;
default:
LOGP(DRTP, LOGL_ERROR, "Unhandled DTX DL AMR FSM state "
"%d\n", lchan->tch.dtx.dl_amr_fsm->state);
return -EINVAL;
}
break;
default:
/* we don't support CSD modes */
rc = -1;
break;
}
if (rc < 0) {
LOGP(DRTP, LOGL_ERROR, "%s unable to parse RTP payload\n",
gsm_lchan_name(lchan));
return -EBADMSG;
}
*len = rc + 1;
DEBUGP(DRTP, "%s RTP->L1: %s\n", gsm_lchan_name(lchan),
osmo_hexdump(data, *len));
return 0;
}
static int is_recv_only(uint8_t speech_mode)
{
return (speech_mode & 0xF0) == (1 << 4);
}
/*! \brief receive a traffic L1 primitive for a given lchan */
int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg)
{
GsmL1_Prim_t *l1p = msgb_l1prim(l1p_msg);
GsmL1_PhDataInd_t *data_ind = &l1p->u.phDataInd;
uint8_t *payload, payload_type, payload_len, sid_first[9] = { 0 };
struct msgb *rmsg = NULL;
struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)];
if (is_recv_only(lchan->abis_ip.speech_mode))
return -EAGAIN;
if (data_ind->msgUnitParam.u8Size < 1) {
LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "chan_nr %d Rx Payload size 0\n", chan_nr);
/* Push empty payload to upper layers */
rmsg = msgb_alloc_headroom(256, 128, "L1P-to-RTP");
return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn,
data_ind->measParam.fBer * 10000,
data_ind->measParam.fLinkQuality * 10);
}
payload_type = data_ind->msgUnitParam.u8Buffer[0];
payload = data_ind->msgUnitParam.u8Buffer + 1;
payload_len = data_ind->msgUnitParam.u8Size - 1;
/* clear RTP marker if the marker has previously sent */
if (!lchan->tch.dtx.is_speech_resume)
lchan->rtp_tx_marker = false;
switch (payload_type) {
case GsmL1_TchPlType_Fr:
case GsmL1_TchPlType_Efr:
if (lchan->type != GSM_LCHAN_TCH_F)
goto err_payload_match;
break;
case GsmL1_TchPlType_Hr:
if (lchan->type != GSM_LCHAN_TCH_H)
goto err_payload_match;
break;
case GsmL1_TchPlType_Amr:
if (lchan->type != GSM_LCHAN_TCH_H &&
lchan->type != GSM_LCHAN_TCH_F)
goto err_payload_match;
break;
case GsmL1_TchPlType_Amr_Onset:
if (lchan->type != GSM_LCHAN_TCH_H &&
lchan->type != GSM_LCHAN_TCH_F)
goto err_payload_match;
/* according to 3GPP TS 26.093 ONSET frames precede the first
speech frame of a speech burst - set the marker for next RTP
frame */
lchan->tch.dtx.is_speech_resume = true;
lchan->rtp_tx_marker = true;
break;
case GsmL1_TchPlType_Amr_SidFirstP1:
if (lchan->type != GSM_LCHAN_TCH_H)
goto err_payload_match;
LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P1 from L1 "
"(%d bytes)\n", payload_len);
break;
case GsmL1_TchPlType_Amr_SidFirstP2:
if (lchan->type != GSM_LCHAN_TCH_H)
goto err_payload_match;
LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P2 from L1 "
"(%d bytes)\n", payload_len);
break;
case GsmL1_TchPlType_Amr_SidFirstInH:
if (lchan->type != GSM_LCHAN_TCH_H)
goto err_payload_match;
lchan->tch.dtx.is_speech_resume = true;
lchan->rtp_tx_marker = true;
LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_INH from L1 "
"(%d bytes)\n", payload_len);
break;
case GsmL1_TchPlType_Amr_SidUpdateInH:
if (lchan->type != GSM_LCHAN_TCH_H)
goto err_payload_match;
lchan->tch.dtx.is_speech_resume = true;
lchan->rtp_tx_marker = true;
LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_UPDATE_INH from L1 "
"(%d bytes)\n", payload_len);
break;
default:
LOGPFN(DL1P, LOGL_NOTICE, data_ind->u32Fn, "%s Rx Payload Type %s is unsupported\n",
gsm_lchan_name(lchan),
get_value_string(oc2gbts_tch_pl_names, payload_type));
break;
}
LOGP(DL1P, LOGL_DEBUG, "%s %s lchan->rtp_tx_marker = %s, len=%u\n",
gsm_lchan_name(lchan),
get_value_string(oc2gbts_tch_pl_names, payload_type),
lchan->rtp_tx_marker ? "true" : "false",
payload_len);
switch (payload_type) {
case GsmL1_TchPlType_Fr:
rmsg = l1_to_rtppayload_fr(payload, payload_len, lchan);
break;
case GsmL1_TchPlType_Hr:
rmsg = l1_to_rtppayload_hr(payload, payload_len, lchan);
break;
case GsmL1_TchPlType_Efr:
rmsg = l1_to_rtppayload_efr(payload, payload_len, lchan);
break;
case GsmL1_TchPlType_Amr:
rmsg = l1_to_rtppayload_amr(payload, payload_len, lchan);
break;
case GsmL1_TchPlType_Amr_SidFirstP1:
memcpy(sid_first, payload, payload_len);
int len = osmo_amr_rtp_enc(sid_first, 0, AMR_SID, AMR_GOOD);
if (len < 0)
return 0;
rmsg = l1_to_rtppayload_amr(sid_first, len, lchan);
break;
}
if (rmsg)
return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn,
data_ind->measParam.fBer * 10000,
data_ind->measParam.fLinkQuality * 10);
return 0;
err_payload_match:
LOGPFN(DL1P, LOGL_ERROR, data_ind->u32Fn, "%s Rx Payload Type %s incompatible with lchan\n",
gsm_lchan_name(lchan), get_value_string(oc2gbts_tch_pl_names, payload_type));
return -EINVAL;
}
struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan, uint32_t fn)
{
struct msgb *msg;
GsmL1_Prim_t *l1p;
GsmL1_PhDataReq_t *data_req;
GsmL1_MsgUnitParam_t *msu_param;
uint8_t *payload_type;
uint8_t *l1_payload;
int rc;
msg = l1p_msgb_alloc();
if (!msg)
return NULL;
l1p = msgb_l1prim(msg);
data_req = &l1p->u.phDataReq;
msu_param = &data_req->msgUnitParam;
payload_type = &msu_param->u8Buffer[0];
l1_payload = &msu_param->u8Buffer[1];
switch (lchan->tch_mode) {
case GSM48_CMODE_SPEECH_AMR:
if (lchan->type == GSM_LCHAN_TCH_H &&
dtx_dl_amr_enabled(lchan)) {
/* we have to explicitly handle sending SID FIRST P2 for
AMR HR in here */
*payload_type = GsmL1_TchPlType_Amr_SidFirstP2;
rc = dtx_dl_amr_fsm_step(lchan, NULL, 0, fn, l1_payload,
false, &(msu_param->u8Size),
NULL);
if (rc == 0)
return msg;
}
*payload_type = GsmL1_TchPlType_Amr;
break;
case GSM48_CMODE_SPEECH_V1:
if (lchan->type == GSM_LCHAN_TCH_F)
*payload_type = GsmL1_TchPlType_Fr;
else
*payload_type = GsmL1_TchPlType_Hr;
break;
case GSM48_CMODE_SPEECH_EFR:
*payload_type = GsmL1_TchPlType_Efr;
break;
default:
msgb_free(msg);
return NULL;
}
rc = repeat_last_sid(lchan, l1_payload, fn);
if (!rc) {
msgb_free(msg);
return NULL;
}
msu_param->u8Size = rc;
return msg;
}

114
src/osmo-bts-oc2g/utils.c Normal file
View File

@ -0,0 +1,114 @@
/* Helper utilities that are used in OMLs */
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* Based on sysmoBTS:
* (C) 2011-2013 by Harald Welte <laforge@gnumonks.org>
* (C) 2013 by Holger Hans Peter Freyther
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "utils.h"
#include <osmo-bts/bts.h>
#include <osmo-bts/gsm_data.h>
#include <osmo-bts/logging.h>
#include "oc2gbts.h"
#include "l1_if.h"
int band_oc2g2osmo(GsmL1_FreqBand_t band)
{
switch (band) {
case GsmL1_FreqBand_850:
return GSM_BAND_850;
case GsmL1_FreqBand_900:
return GSM_BAND_900;
case GsmL1_FreqBand_1800:
return GSM_BAND_1800;
case GsmL1_FreqBand_1900:
return GSM_BAND_1900;
default:
return -1;
}
}
static int band_osmo2oc2g(struct gsm_bts_trx *trx, enum gsm_band osmo_band)
{
struct oc2gl1_hdl *fl1h = trx_oc2gl1_hdl(trx);
/* check if the TRX hardware actually supports the given band */
if (!(fl1h->hw_info.band_support & osmo_band))
return -1;
/* if yes, convert from osmcoom style band definition to L1 band */
switch (osmo_band) {
case GSM_BAND_850:
return GsmL1_FreqBand_850;
case GSM_BAND_900:
return GsmL1_FreqBand_900;
case GSM_BAND_1800:
return GsmL1_FreqBand_1800;
case GSM_BAND_1900:
return GsmL1_FreqBand_1900;
default:
return -1;
}
}
/**
* Select the band that matches the ARFCN. In general the ARFCNs
* for GSM1800 and GSM1900 overlap and one needs to specify the
* rightband. When moving between GSM900/GSM1800 and GSM850/1900
* modifying the BTS configuration is a bit annoying. The auto-band
* configuration allows to ease with this transition.
*/
int oc2gbts_select_oc2g_band(struct gsm_bts_trx *trx, uint16_t arfcn)
{
enum gsm_band band;
struct gsm_bts *bts = trx->bts;
if (!bts->auto_band)
return band_osmo2oc2g(trx, bts->band);
/*
* We need to check what will happen now.
*/
band = gsm_arfcn2band(arfcn);
/* if we are already on the right band return */
if (band == bts->band)
return band_osmo2oc2g(trx, bts->band);
/* Check if it is GSM1800/GSM1900 */
if (band == GSM_BAND_1800 && bts->band == GSM_BAND_1900)
return band_osmo2oc2g(trx, bts->band);
/*
* Now to the actual autobauding. We just want DCS/DCS and
* PCS/PCS for PCS we check for 850/1800 though
*/
if ((band == GSM_BAND_900 && bts->band == GSM_BAND_1800)
|| (band == GSM_BAND_1800 && bts->band == GSM_BAND_900)
|| (band == GSM_BAND_850 && bts->band == GSM_BAND_1900))
return band_osmo2oc2g(trx, band);
if (band == GSM_BAND_1800 && bts->band == GSM_BAND_850)
return band_osmo2oc2g(trx, GSM_BAND_1900);
/* give up */
return -1;
}

13
src/osmo-bts-oc2g/utils.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef _UTILS_H
#define _UTILS_H
#include <stdint.h>
#include "oc2gbts.h"
struct gsm_bts_trx;
int band_oc2g2osmo(GsmL1_FreqBand_t band);
int oc2gbts_select_oc2g_band(struct gsm_bts_trx *trx, uint16_t arfcn);
#endif