Add OC-2G osmo-bts-oc2g target

This commit is contained in:
Omar Ramadan
2018-10-01 13:22:49 -07:00
committed by Omar Ramadan
parent 4886f718b0
commit 15d1e01352
46 changed files with 13350 additions and 0 deletions

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) $(LIBSYSTEMD_CFLAGS)
COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_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,464 @@
/* 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);
/*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);
/*
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");
/*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

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

View File

@@ -0,0 +1,144 @@
#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 <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;
uint32_t hLayer1; /* handle to the L1 instance in the DSP */
uint32_t dsp_trace_f; /* currently operational DSP trace flags */
struct llist_head wlc_list;
struct 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,319 @@
/* 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 <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));
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;
}

View File

@@ -0,0 +1,250 @@
/* 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);
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()
{
char rev_maj, rev_min;
int model;
static char model_name[64] = {0, };
snprintf(model_name, sizeof(model_name), "Open Cellular 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);
}
printf(model_name);
}
int bts_model_handle_options(int argc, char **argv)
{
int num_errors = 0;
while (1) {
int option_idx = 0, c;
static const struct option long_options[] = {
{ "dsp-trace", 1, 0, 'p' },
{ "hw-version", 0, 0, 'w' },
{ "pcu-direct", 0, 0, 'M' },
{ 0, 0, 0, 0 }
};
c = getopt_long(argc, argv, "p:wM",
long_options, &option_idx);
if (c == -1)
break;
switch (c) {
case 'p':
dsp_trace = strtoul(optarg, NULL, 16);
break;
case 'M':
pcu_direct = 1;
break;
case 'w':
print_hwversion();
exit(0);
break;
default:
num_errors++;
break;
}
}
return num_errors;
}
void bts_model_abis_close(struct gsm_bts *bts)
{
/* 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,148 @@
/* 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;
}
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);
}

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,751 @@
/* 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 "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-1> instance <0-0> system-information",
SHOW_TRX_STR "Display information about system\n")
{
int phy_nr = atoi(argv[0]);
int inst_nr = atoi(argv[1]);
struct phy_link *plink = phy_link_by_num(phy_nr);
struct phy_instance *pinst;
struct 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;
}
/* TODO(oramadan) MERGE
DEFUN(cfg_bts_led_mode, cfg_bts_led_mode_cmd,
"led-control-mode (bts|external)",
"Set LED controlled by BTS or external software\n"
"LED can be controlled by (bts, external)\n")
{
struct gsm_bts *bts = vty->index;
struct gsm_bts_role_bts *btsb = bts_role_bts(bts);
int val = get_string_value(oc2g_led_mode_strs, argv[0]);
if((val < OC2G_LED_CONTROL_BTS) || (val > OC2G_LED_CONTROL_EXT)) {
vty_out(vty, "Invalid LED control mode %d%s", val, VTY_NEWLINE);
return CMD_WARNING;
}
btsb->oc2g.led_ctrl_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_led_mode_cmd);
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 rc;
}

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

View File

@@ -0,0 +1,546 @@
/* 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 <osmocom/trau/osmo_ortp.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, "L1C-to-RTP");
if (!msg)
return NULL;
/* new L1 can deliver bits like we need them */
cur = msgb_put(msg, GSM_FR_BYTES);
memcpy(cur, l1_payload, GSM_FR_BYTES);
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, "L1C-to-RTP");
if (!msg)
return NULL;
/* new L1 can deliver bits like we need them */
cur = msgb_put(msg, GSM_EFR_BYTES);
memcpy(cur, l1_payload, GSM_EFR_BYTES);
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, "L1C-to-RTP");
if (!msg)
return NULL;
if (payload_len != GSM_HR_BYTES) {
LOGP(DL1C, LOGL_ERROR, "L1 HR frame length %u != expected %u\n",
payload_len, GSM_HR_BYTES);
return NULL;
}
cur = msgb_put(msg, GSM_HR_BYTES);
memcpy(cur, l1_payload, GSM_HR_BYTES);
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(DL1C, LOGL_ERROR, "RTP HR frame length %u != expected %u\n",
payload_len, GSM_HR_BYTES);
return 0;
}
memcpy(l1_payload, rtp_payload, GSM_HR_BYTES);
return GSM_HR_BYTES;
}
static struct msgb *l1_to_rtppayload_amr(uint8_t *l1_payload, uint8_t payload_len,
struct gsm_lchan *lchan)
{
struct msgb *msg;
uint8_t amr_if2_len = payload_len - 2;
uint8_t *cur;
msg = msgb_alloc_headroom(1024, 128, "L1C-to-RTP");
if (!msg)
return NULL;
cur = msgb_put(msg, amr_if2_len);
memcpy(cur, l1_payload+2, amr_if2_len);
/*
* Audiocode's MGW doesn't like receiving CMRs that are not
* the same as the previous one. This means we need to patch
* the content here.
*/
if ((cur[0] & 0xF0) == 0xF0)
cur[0]= lchan->tch.last_cmr << 4;
else
lchan->tch.last_cmr = cur[0] >> 4;
return msg;
}
/*! \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) {
LOGP(DL1P, LOGL_DEBUG, "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;
LOGP(DL1C, LOGL_DEBUG, "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;
LOGP(DL1C, LOGL_DEBUG, "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;
LOGP(DL1C, LOGL_DEBUG, "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;
LOGP(DL1C, LOGL_DEBUG, "DTX: received SID_UPDATE_INH from L1 "
"(%d bytes)\n", payload_len);
break;
default:
LOGP(DL1C, LOGL_NOTICE, "%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:
LOGP(DL1C, LOGL_ERROR, "%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;
}

View File

@@ -0,0 +1,115 @@
/* 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;
}

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