LC15: Add initial support for the NuRAN Wireless Litecell 1.5

This commit adds basic support for the Litecell 1.5. Multi-TRX is not
supported yet. Instead, multiple instances of the BTS can be launched
using command line parameter -n <HW_TRX_NR> to specify if TRX 1 or
2 must be used by the bts. Note that only TRX 1 opens a connection to
the PCU. Full support for GPRS on both TRX will come at the same time
than the multi-TRX support.

The BTS manager has been adapted to match the new hardware but otherwise
it has not been improved or changed compared to the one used on the
SuperFemto/Litecell (sysmobts).
This commit is contained in:
Yves Godin 2015-11-12 08:32:07 -05:00 committed by Harald Welte
parent 5a945dad0c
commit 2a711887b7
41 changed files with 9535 additions and 0 deletions

View File

@ -68,6 +68,14 @@ if test "$enable_octphy" = "yes" ; then
CPPFLAGS=$oldCPPFLAGS
fi
AC_MSG_CHECKING([whether to enable NuRAN Wireless Litecell 1.5 hardware support])
AC_ARG_ENABLE(litecell15-bts,
AC_HELP_STRING([--enable-litecell15-bts],
[enable code for NuRAN Wireless Litecell15 bts [default=no]]),
[enable_litecell15_bts="yes"],[enable_litecell15_bts="no"])
AC_MSG_RESULT([$enable_litecell15_bts])
AM_CONDITIONAL(ENABLE_LC15BTS, test "x$enable_litecell15_bts" = "xyes")
# We share gsm_data.h with OpenBSC and need to be pointed to the source
# directory of OpenBSC for now.
AC_ARG_WITH([openbsc],
@ -101,6 +109,7 @@ AC_OUTPUT(
src/Makefile
src/common/Makefile
src/osmo-bts-sysmo/Makefile
src/osmo-bts-litecell15/Makefile
src/osmo-bts-trx/Makefile
src/osmo-bts-octphy/Makefile
include/Makefile

View File

@ -11,3 +11,7 @@ endif
if ENABLE_OCTPHY
SUBDIRS += osmo-bts-octphy
endif
if ENABLE_LC15BTS
SUBDIRS += osmo-bts-litecell15
endif

View File

@ -0,0 +1,33 @@
AUTOMAKE_OPTIONS = subdir-objects
AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(OPENBSC_INCDIR)
AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS)
COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) -lortp
EXTRA_DIST = misc/lc15bts_mgr.h misc/lc15bts_misc.h misc/lc15bts_par.h \
misc/lc15bts_temp.h misc/lc15bts_power.h misc/lc15bts_clock.h \
misc/lc15bts_bid.h misc/lc15bts_nl.h femtobts.h hw_misc.h \
l1_if.h l1_transp.h utils.h oml_router.h
bin_PROGRAMS = lc15bts lc15bts-mgr lc15bts-util
COMMON_SOURCES = main.c lc15bts.c l1_if.c oml.c lc15bts_vty.c tch.c hw_misc.c calib_file.c \
utils.c misc/lc15bts_par.c misc/lc15bts_bid.c oml_router.c
lc15bts_SOURCES = $(COMMON_SOURCES) l1_transp_hw.c
lc15bts_LDADD = $(top_builddir)/src/common/libbts.a $(COMMON_LDADD)
lc15bts_mgr_SOURCES = \
misc/lc15bts_mgr.c misc/lc15bts_misc.c \
misc/lc15bts_par.c misc/lc15bts_nl.c \
misc/lc15bts_temp.c misc/lc15bts_power.c \
misc/lc15bts_clock.c misc/lc15bts_bid.c \
misc/lc15bts_mgr_vty.c \
misc/lc15bts_mgr_nl.c \
misc/lc15bts_mgr_temp.c \
misc/lc15bts_mgr_calib.c
lc15bts_mgr_LDADD = $(LIBGPS_LIBS) $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCTRL_LIBS) $(top_builddir)/src/common/libbts.a
lc15bts_util_SOURCES = misc/lc15bts_util.c misc/lc15bts_par.c
lc15bts_util_LDADD = $(LIBOSMOCORE_LIBS)

View File

@ -0,0 +1,256 @@
/* NuRAN Wireless Litecell 1.5 BTS L1 calibration file routines*/
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* Based on sysmoBTS:
* (C) 2012 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <limits.h>
#include <errno.h>
#include <osmocom/core/utils.h>
#include <osmo-bts/gsm_data.h>
#include <osmo-bts/logging.h>
#include <nrw/litecell15/litecell15.h>
#include <nrw/litecell15/gsml1const.h>
#include "l1_if.h"
#include "lc15bts.h"
#include "utils.h"
struct calib_file_desc {
const char *fname;
int rx;
int trx;
int rxpath;
};
static const struct calib_file_desc calib_files[] = {
{
.fname = "calib_rx1a.conf",
.rx = 1,
.trx = 1,
.rxpath = 0,
}, {
.fname = "calib_rx1b.conf",
.rx = 1,
.trx = 1,
.rxpath = 1,
}, {
.fname = "calib_rx2a.conf",
.rx = 1,
.trx = 2,
.rxpath = 0,
}, {
.fname = "calib_rx2b.conf",
.rx = 1,
.trx = 2,
.rxpath = 1,
}, {
.fname = "calib_tx1.conf",
.rx = 0,
.trx = 1,
}, {
.fname = "calib_tx2.conf",
.rx = 0,
.trx = 2,
},
};
static int calib_file_send(struct lc15l1_hdl *fl1h,
const struct calib_file_desc *desc);
/* determine next calibration file index based on supported bands */
static int get_next_calib_file_idx(struct lc15l1_hdl *fl1h, int last_idx)
{
int i;
for (i = last_idx+1; i < ARRAY_SIZE(calib_files); i++) {
if (calib_files[i].trx == fl1h->hw_info.trx_nr)
return i;
}
return -1;
}
static int calib_file_open(struct lc15l1_hdl *fl1h,
const struct calib_file_desc *desc)
{
struct calib_send_state *st = &fl1h->st;
char fname[PATH_MAX];
if (st->fp) {
LOGP(DL1C, LOGL_NOTICE, "L1 calibration file was left opened !!\n");
fclose(st->fp);
st->fp = NULL;
}
fname[0] = '\0';
snprintf(fname, sizeof(fname)-1, "%s/%s", fl1h->calib_path, desc->fname);
fname[sizeof(fname)-1] = '\0';
st->fp = fopen(fname, "rb");
if (!st->fp) {
LOGP(DL1C, LOGL_ERROR,
"Failed to open '%s' for calibration data.\n", fname);
return -1;
}
return 0;
}
static int calib_file_close(struct lc15l1_hdl *fl1h)
{
struct calib_send_state *st = &fl1h->st;
if (st->fp) {
fclose(st->fp);
st->fp = NULL;
}
return 0;
}
/* iteratively download the calibration data into the L1 */
static int calib_send_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
void *data);
/* send a chunk of calibration tabledata for a single specified file */
static int calib_file_send_next_chunk(struct lc15l1_hdl *fl1h)
{
struct calib_send_state *st = &fl1h->st;
Litecell15_Prim_t *prim;
struct msgb *msg;
size_t n;
msg = sysp_msgb_alloc();
prim = msgb_sysprim(msg);
prim->id = Litecell15_PrimId_SetCalibTblReq;
prim->u.setCalibTblReq.offset = (uint32_t)ftell(st->fp);
n = fread(prim->u.setCalibTblReq.u8Data, 1,
sizeof(prim->u.setCalibTblReq.u8Data), st->fp);
prim->u.setCalibTblReq.length = n;
if (n == 0) {
/* The table data has been completely sent and acknowledged */
LOGP(DL1C, LOGL_NOTICE, "L1 calibration table %s loaded\n",
calib_files[st->last_file_idx].fname);
calib_file_close(fl1h);
msgb_free(msg);
/* Send the next one if any */
st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx);
if (st->last_file_idx >= 0) {
return calib_file_send(fl1h,
&calib_files[st->last_file_idx]);
}
LOGP(DL1C, LOGL_INFO, "L1 calibration table loading complete!\n");
return 0;
}
return l1if_req_compl(fl1h, msg, calib_send_compl_cb, NULL);
}
/* send the calibration table for a single specified file */
static int calib_file_send(struct lc15l1_hdl *fl1h,
const struct calib_file_desc *desc)
{
struct calib_send_state *st = &fl1h->st;
int rc;
rc = calib_file_open(fl1h, desc);
if (rc < 0) {
/* still, we'd like to continue trying to load
* calibration for all other bands */
st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx);
if (st->last_file_idx >= 0)
return calib_file_send(fl1h,
&calib_files[st->last_file_idx]);
LOGP(DL1C, LOGL_INFO, "L1 calibration table loading complete!\n");
return 0;
}
return calib_file_send_next_chunk(fl1h);
}
/* completion callback after every SetCalibTbl is confirmed */
static int calib_send_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg,
void *data)
{
struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
struct calib_send_state *st = &fl1h->st;
Litecell15_Prim_t *prim = msgb_sysprim(l1_msg);
if (prim->u.setCalibTblCnf.status != GsmL1_Status_Success) {
LOGP(DL1C, LOGL_ERROR, "L1 rejected calibration table\n");
msgb_free(l1_msg);
calib_file_close(fl1h);
/* Skip this one and try the next one */
st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx);
if (st->last_file_idx >= 0) {
return calib_file_send(fl1h,
&calib_files[st->last_file_idx]);
}
LOGP(DL1C, LOGL_INFO, "L1 calibration table loading complete!\n");
return 0;
}
msgb_free(l1_msg);
/* Keep sending the calibration file data */
return calib_file_send_next_chunk(fl1h);
}
int calib_load(struct lc15l1_hdl *fl1h)
{
int rc;
struct calib_send_state *st = &fl1h->st;
if (!fl1h->calib_path) {
LOGP(DL1C, LOGL_ERROR, "Calibration file path not specified\n");
return -1;
}
rc = get_next_calib_file_idx(fl1h, -1);
if (rc < 0) {
return -1;
}
st->last_file_idx = rc;
return calib_file_send(fl1h, &calib_files[st->last_file_idx]);
}

View File

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

View File

@ -0,0 +1,13 @@
#ifndef _HW_MISC_H
#define _HW_MISC_H
enum lc15bts_led_color {
LED_OFF,
LED_RED,
LED_GREEN,
LED_ORANGE,
};
int lc15bts_led_set(enum lc15bts_led_color c);
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,121 @@
#ifndef _L1_IF_H
#define _L1_IF_H
#include <osmocom/core/select.h>
#include <osmocom/core/write_queue.h>
#include <osmocom/core/gsmtap_util.h>
#include <osmocom/core/timer.h>
#include <osmocom/gsm/gsm_utils.h>
#include <nrw/litecell15/gsml1prim.h>
enum {
MQ_SYS_READ,
MQ_L1_READ,
MQ_TCH_READ,
MQ_PDTCH_READ,
_NUM_MQ_READ
};
enum {
MQ_SYS_WRITE,
MQ_L1_WRITE,
MQ_TCH_WRITE,
MQ_PDTCH_WRITE,
_NUM_MQ_WRITE
};
struct calib_send_state {
FILE *fp;
const char *path;
int last_file_idx;
};
struct lc15l1_hdl {
struct gsm_time gsm_time;
uint32_t hLayer1; /* handle to the L1 instance in the DSP */
uint32_t dsp_trace_f;
uint8_t clk_use_eeprom;
int clk_cal;
uint8_t clk_src;
float min_qual_rach;
float min_qual_norm;
char *calib_path;
struct llist_head wlc_list;
void *priv; /* user reference */
struct osmo_timer_list alive_timer;
unsigned int alive_prim_cnt;
struct osmo_fd read_ofd[_NUM_MQ_READ]; /* osmo file descriptors */
struct osmo_wqueue write_q[_NUM_MQ_WRITE];
struct {
/* from DSP/FPGA after L1 Init */
uint8_t dsp_version[3];
uint8_t fpga_version[3];
uint32_t band_support;
uint8_t ver_major;
uint8_t ver_minor;
uint8_t trx_nr; // 1 or 2
} hw_info;
struct calib_send_state st;
uint8_t last_rf_mute[8];
};
#define msgb_l1prim(msg) ((GsmL1_Prim_t *)(msg)->l1h)
#define msgb_sysprim(msg) ((Litecell15_Prim_t *)(msg)->l1h)
typedef int l1if_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, void *data);
/* send a request primitive to the L1 and schedule completion call-back */
int l1if_req_compl(struct lc15l1_hdl *fl1h, struct msgb *msg,
l1if_compl_cb *cb, void *cb_data);
int l1if_gsm_req_compl(struct lc15l1_hdl *fl1h, struct msgb *msg,
l1if_compl_cb *cb, void *cb_data);
struct lc15l1_hdl *l1if_open(void *priv, int trx_nr);
int l1if_close(struct lc15l1_hdl *hdl);
int l1if_reset(struct lc15l1_hdl *hdl);
int l1if_activate_rf(struct lc15l1_hdl *hdl, int on);
int l1if_set_trace_flags(struct lc15l1_hdl *hdl, uint32_t flags);
int l1if_set_txpower(struct lc15l1_hdl *fl1h, float tx_power);
int l1if_mute_rf(struct lc15l1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb);
struct msgb *l1p_msgb_alloc(void);
struct msgb *sysp_msgb_alloc(void);
uint32_t l1if_lchan_to_hLayer(struct gsm_lchan *lchan);
struct gsm_lchan *l1if_hLayer_to_lchan(struct gsm_bts_trx *trx, uint32_t hLayer);
/* tch.c */
void l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len,
const uint8_t *rtp_pl, unsigned int rtp_pl_len);
int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg);
int l1if_tch_fill(struct gsm_lchan *lchan, uint8_t *l1_buffer);
struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan);
/* ciphering */
int l1if_set_ciphering(struct lc15l1_hdl *fl1h,
struct gsm_lchan *lchan,
int dir_downlink);
/* channel control */
int l1if_rsl_chan_act(struct gsm_lchan *lchan);
int l1if_rsl_chan_rel(struct gsm_lchan *lchan);
int l1if_rsl_chan_mod(struct gsm_lchan *lchan);
int l1if_rsl_deact_sacch(struct gsm_lchan *lchan);
int l1if_rsl_mode_modify(struct gsm_lchan *lchan);
/* calibration loading */
int calib_load(struct lc15l1_hdl *fl1h);
/* public helpers for test */
int bts_check_for_ciph_cmd(struct lc15l1_hdl *fl1h,
struct msgb *msg, struct gsm_lchan *lchan);
inline int l1if_ms_pwr_ctrl(struct gsm_lchan *lchan, const int uplink_target,
const uint8_t ms_power, const float rxLevel);
#endif /* _L1_IF_H */

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 lc15l1_hdl *fl1h, struct msgb *msg);
int l1if_handle_sysprim(struct lc15l1_hdl *fl1h, struct msgb *msg);
/* functions exported by a transport */
int l1if_transport_open(int q, struct lc15l1_hdl *fl1h);
int l1if_transport_close(int q, struct lc15l1_hdl *fl1h);
#endif /* _L1_TRANSP_H */

View File

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

View File

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

View File

@ -0,0 +1,64 @@
#ifndef LC15BTS_H
#define LC15BTS_H
#include <stdlib.h>
#include <osmocom/core/utils.h>
#include <nrw/litecell15/litecell15.h>
#include <nrw/litecell15/gsml1const.h>
/*
* Depending on the firmware version either GsmL1_Prim_t or Litecell15_Prim_t
* is the bigger struct. For earlier firmware versions the GsmL1_Prim_t was the
* bigger struct.
*/
#define LC15BTS_PRIM_SIZE \
(OSMO_MAX(sizeof(Litecell15_Prim_t), sizeof(GsmL1_Prim_t)) + 128)
enum l1prim_type {
L1P_T_INVALID, /* this must be 0 to detect uninitialized elements */
L1P_T_REQ,
L1P_T_CONF,
L1P_T_IND,
};
enum l1prim_type lc15bts_get_l1prim_type(GsmL1_PrimId_t id);
const struct value_string lc15bts_l1prim_names[GsmL1_PrimId_NUM+1];
GsmL1_PrimId_t lc15bts_get_l1prim_conf(GsmL1_PrimId_t id);
enum l1prim_type lc15bts_get_sysprim_type(Litecell15_PrimId_t id);
const struct value_string lc15bts_sysprim_names[Litecell15_PrimId_NUM+1];
Litecell15_PrimId_t lc15bts_get_sysprim_conf(Litecell15_PrimId_t id);
const struct value_string lc15bts_l1sapi_names[GsmL1_Sapi_NUM+1];
const struct value_string lc15bts_l1status_names[GSML1_STATUS_NUM+1];
const struct value_string lc15bts_tracef_names[29];
const struct value_string lc15bts_tracef_docs[29];
const struct value_string lc15bts_tch_pl_names[15];
const struct value_string lc15bts_clksrc_names[10];
const struct value_string lc15bts_dir_names[6];
enum pdch_cs {
PDCH_CS_1,
PDCH_CS_2,
PDCH_CS_3,
PDCH_CS_4,
PDCH_MCS_1,
PDCH_MCS_2,
PDCH_MCS_3,
PDCH_MCS_4,
PDCH_MCS_5,
PDCH_MCS_6,
PDCH_MCS_7,
PDCH_MCS_8,
PDCH_MCS_9,
_NUM_PDCH_CS
};
const uint8_t pdch_msu_size[_NUM_PDCH_CS];
#endif /* LC15BTS_H */

View File

@ -0,0 +1,400 @@
/* VTY interface for NuRAN Wireless Litecell 1.5 */
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* Based on sysmoBTS:
* (C) 2011 by Harald Welte <laforge@gnumonks.org>
* (C) 2012,2013 by Holger Hans Peter Freyther
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <stdint.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/select.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/gsm/tlv.h>
#include <osmocom/vty/vty.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/misc.h>
#include <osmo-bts/gsm_data.h>
#include <osmo-bts/logging.h>
#include <osmo-bts/vty.h>
#include "lc15bts.h"
#include "l1_if.h"
#include "utils.h"
extern int lchan_activate(struct gsm_lchan *lchan);
extern int lchan_deactivate(struct gsm_lchan *lchan);
#define TRX_STR "Transceiver related commands\n" "TRX number\n"
#define SHOW_TRX_STR \
SHOW_STR \
TRX_STR
#define DSP_TRACE_F_STR "DSP Trace Flag\n"
static struct gsm_bts *vty_bts;
/* configuration */
DEFUN(cfg_bts_auto_band, cfg_bts_auto_band_cmd,
"auto-band",
"Automatically select band for ARFCN based on configured band\n")
{
struct gsm_bts *bts = vty->index;
struct gsm_bts_role_bts *btsb = bts_role_bts(bts);
btsb->auto_band = 1;
return CMD_SUCCESS;
}
DEFUN(cfg_bts_no_auto_band, cfg_bts_no_auto_band_cmd,
"no auto-band",
NO_STR "Automatically select band for ARFCN based on configured band\n")
{
struct gsm_bts *bts = vty->index;
struct gsm_bts_role_bts *btsb = bts_role_bts(bts);
btsb->auto_band = 0;
return CMD_SUCCESS;
}
DEFUN(cfg_trx_cal_path, cfg_trx_cal_path_cmd,
"trx-calibration-path PATH",
"Set the path name to TRX calibration data\n" "Path name\n")
{
struct gsm_bts_trx *trx = vty->index;
struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
if (fl1h->calib_path)
talloc_free(fl1h->calib_path);
fl1h->calib_path = talloc_strdup(fl1h, argv[0]);
return CMD_SUCCESS;
}
DEFUN(cfg_trx_min_qual_rach, cfg_trx_min_qual_rach_cmd,
"min-qual-rach <-100-100>",
"Set the minimum quality level of RACH burst to be accpeted\n"
"C/I level in tenth of dB\n")
{
struct gsm_bts_trx *trx = vty->index;
struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
fl1h->min_qual_rach = strtof(argv[0], NULL) / 10.0f;
return CMD_SUCCESS;
}
DEFUN(cfg_trx_min_qual_norm, cfg_trx_min_qual_norm_cmd,
"min-qual-norm <-100-100>",
"Set the minimum quality level of normal burst to be accpeted\n"
"C/I level in tenth of dB\n")
{
struct gsm_bts_trx *trx = vty->index;
struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
fl1h->min_qual_norm = strtof(argv[0], NULL) / 10.0f;
return CMD_SUCCESS;
}
DEFUN(cfg_trx_nominal_power, cfg_trx_nominal_power_cmd,
"nominal-tx-power <0-100>",
"Set the nominal transmit output power in dBm\n"
"Nominal transmit output power level in dBm\n")
{
struct gsm_bts_trx *trx = vty->index;
trx->nominal_power = atoi(argv[0]);
return CMD_SUCCESS;
}
/* runtime */
DEFUN(show_dsp_trace_f, show_dsp_trace_f_cmd,
"show trx <0-0> dsp-trace-flags",
SHOW_TRX_STR "Display the current setting of the DSP trace flags")
{
int trx_nr = atoi(argv[0]);
struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
struct lc15l1_hdl *fl1h;
int i;
if (!trx)
return CMD_WARNING;
fl1h = trx_lc15l1_hdl(trx);
vty_out(vty, "Litecell15 L1 DSP trace flags:%s", VTY_NEWLINE);
for (i = 0; i < ARRAY_SIZE(lc15bts_tracef_names); i++) {
const char *endis;
if (lc15bts_tracef_names[i].value == 0 &&
lc15bts_tracef_names[i].str == NULL)
break;
if (fl1h->dsp_trace_f & lc15bts_tracef_names[i].value)
endis = "enabled";
else
endis = "disabled";
vty_out(vty, "DSP Trace %-15s %s%s",
lc15bts_tracef_names[i].str, endis,
VTY_NEWLINE);
}
return CMD_SUCCESS;
}
DEFUN(dsp_trace_f, dsp_trace_f_cmd, "HIDDEN", TRX_STR)
{
int trx_nr = atoi(argv[0]);
struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
struct lc15l1_hdl *fl1h;
unsigned int flag ;
if (!trx) {
vty_out(vty, "Cannot find TRX number %u%s",
trx_nr, VTY_NEWLINE);
return CMD_WARNING;
}
fl1h = trx_lc15l1_hdl(trx);
flag = get_string_value(lc15bts_tracef_names, argv[1]);
l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f | flag);
return CMD_SUCCESS;
}
DEFUN(no_dsp_trace_f, no_dsp_trace_f_cmd, "HIDDEN", NO_STR TRX_STR)
{
int trx_nr = atoi(argv[0]);
struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
struct lc15l1_hdl *fl1h;
unsigned int flag ;
if (!trx) {
vty_out(vty, "Cannot find TRX number %u%s",
trx_nr, VTY_NEWLINE);
return CMD_WARNING;
}
fl1h = trx_lc15l1_hdl(trx);
flag = get_string_value(lc15bts_tracef_names, argv[1]);
l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f & ~flag);
return CMD_SUCCESS;
}
DEFUN(show_sys_info, show_sys_info_cmd,
"show trx <0-0> system-information",
SHOW_TRX_STR "Display information about system\n")
{
int trx_nr = atoi(argv[0]);
struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
struct lc15l1_hdl *fl1h;
int i;
if (!trx) {
vty_out(vty, "Cannot find TRX number %u%s",
trx_nr, VTY_NEWLINE);
return CMD_WARNING;
}
fl1h = trx_lc15l1_hdl(trx);
vty_out(vty, "DSP Version: %u.%u.%u, FPGA Version: %u.%u.%u%s",
fl1h->hw_info.dsp_version[0],
fl1h->hw_info.dsp_version[1],
fl1h->hw_info.dsp_version[2],
fl1h->hw_info.fpga_version[0],
fl1h->hw_info.fpga_version[1],
fl1h->hw_info.fpga_version[2], VTY_NEWLINE);
vty_out(vty, "GSM Band Support: ");
for (i = 0; i < sizeof(fl1h->hw_info.band_support); i++) {
if (fl1h->hw_info.band_support & (1 << i))
vty_out(vty, "%s ", gsm_band_name(1 << i));
}
vty_out(vty, "%s", VTY_NEWLINE);
return CMD_SUCCESS;
}
DEFUN(activate_lchan, activate_lchan_cmd,
"trx <0-0> <0-7> (activate|deactivate) <0-7>",
TRX_STR
"Timeslot number\n"
"Activate Logical Channel\n"
"Deactivate Logical Channel\n"
"Logical Channel Number\n" )
{
int trx_nr = atoi(argv[0]);
int ts_nr = atoi(argv[1]);
int lchan_nr = atoi(argv[3]);
struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
if (!strcmp(argv[2], "activate"))
lchan_activate(lchan);
else
lchan_deactivate(lchan);
return CMD_SUCCESS;
}
DEFUN(set_tx_power, set_tx_power_cmd,
"trx <0-0> tx-power <-110-100>",
TRX_STR
"Set transmit power (override BSC)\n"
"Transmit power in dBm\n")
{
int trx_nr = atoi(argv[0]);
int power = atoi(argv[1]);
struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
power_ramp_start(trx, to_mdB(power), 1);
return CMD_SUCCESS;
}
DEFUN(loopback, loopback_cmd,
"trx <0-0> <0-7> loopback <0-1>",
TRX_STR
"Timeslot number\n"
"Set TCH loopback\n"
"Logical Channel Number\n")
{
int trx_nr = atoi(argv[0]);
int ts_nr = atoi(argv[1]);
int lchan_nr = atoi(argv[2]);
struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
lchan->loopback = 1;
return CMD_SUCCESS;
}
DEFUN(no_loopback, no_loopback_cmd,
"no trx <0-0> <0-7> loopback <0-1>",
NO_STR TRX_STR
"Timeslot number\n"
"Set TCH loopback\n"
"Logical Channel Number\n")
{
int trx_nr = atoi(argv[0]);
int ts_nr = atoi(argv[1]);
int lchan_nr = atoi(argv[2]);
struct gsm_bts_trx *trx = gsm_bts_trx_num(vty_bts, trx_nr);
struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
struct gsm_lchan *lchan = &ts->lchan[lchan_nr];
lchan->loopback = 0;
return CMD_SUCCESS;
}
void bts_model_config_write_bts(struct vty *vty, struct gsm_bts *bts)
{
struct gsm_bts_role_bts *btsb = bts_role_bts(bts);
if (btsb->auto_band)
vty_out(vty, " auto-band%s", VTY_NEWLINE);
}
void bts_model_config_write_trx(struct vty *vty, struct gsm_bts_trx *trx)
{
struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx);
if (fl1h->clk_use_eeprom)
vty_out(vty, " clock-calibration eeprom%s", VTY_NEWLINE);
else
vty_out(vty, " clock-calibration %d%s", fl1h->clk_cal,
VTY_NEWLINE);
if (fl1h->calib_path)
vty_out(vty, " trx-calibration-path %s%s",
fl1h->calib_path, VTY_NEWLINE);
vty_out(vty, " min-qual-rach %.0f%s", fl1h->min_qual_rach * 10.0f,
VTY_NEWLINE);
vty_out(vty, " min-qual-norm %.0f%s", fl1h->min_qual_norm * 10.0f,
VTY_NEWLINE);
if (trx->nominal_power != lc15bts_get_nominal_power(trx))
vty_out(vty, " nominal-tx-power %d%s", trx->nominal_power,
VTY_NEWLINE);
}
int bts_model_vty_init(struct gsm_bts *bts)
{
vty_bts = bts;
/* runtime-patch the command strings with debug levels */
dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, lc15bts_tracef_names,
"trx <0-0> dsp-trace-flag (",
"|",")", VTY_DO_LOWER);
dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, lc15bts_tracef_docs,
TRX_STR DSP_TRACE_F_STR,
"\n", "", 0);
no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(bts, lc15bts_tracef_names,
"no trx <0-0> dsp-trace-flag (",
"|",")", VTY_DO_LOWER);
no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(bts, lc15bts_tracef_docs,
NO_STR TRX_STR DSP_TRACE_F_STR,
"\n", "", 0);
install_element_ve(&show_dsp_trace_f_cmd);
install_element_ve(&show_sys_info_cmd);
install_element_ve(&dsp_trace_f_cmd);
install_element_ve(&no_dsp_trace_f_cmd);
install_element(ENABLE_NODE, &activate_lchan_cmd);
install_element(ENABLE_NODE, &set_tx_power_cmd);
install_element(ENABLE_NODE, &loopback_cmd);
install_element(ENABLE_NODE, &no_loopback_cmd);
install_element(BTS_NODE, &cfg_bts_auto_band_cmd);
install_element(BTS_NODE, &cfg_bts_no_auto_band_cmd);
install_element(TRX_NODE, &cfg_trx_cal_path_cmd);
install_element(TRX_NODE, &cfg_trx_min_qual_rach_cmd);
install_element(TRX_NODE, &cfg_trx_min_qual_norm_cmd);
install_element(TRX_NODE, &cfg_trx_nominal_power_cmd);
return 0;
}

View File

@ -0,0 +1,431 @@
/* Main program for NuRAN Wireless Litecell 1.5 BTS */
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* Based on sysmoBTS:
* (C) 2011-2013 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <getopt.h>
#include <limits.h>
#include <sys/signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sched.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/application.h>
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/logging.h>
#include <osmocom/vty/ports.h>
#include <osmocom/core/gsmtap_util.h>
#include <osmocom/core/gsmtap.h>
#include <osmo-bts/gsm_data.h>
#include <osmo-bts/logging.h>
#include <osmo-bts/abis.h>
#include <osmo-bts/bts.h>
#include <osmo-bts/vty.h>
#include <osmo-bts/bts_model.h>
#include <osmo-bts/pcu_if.h>
#include <osmo-bts/control_if.h>
#include <osmo-bts/l1sap.h>
/*NTQD: Change how rx_nr is handle in multi-trx*/
#define LC15BTS_RF_LOCK_PATH(NR) ((((NR))==1) ? "/var/lock/bts_rf_lock1" : "/var/lock/bts_rf_lock2")
#define LC15BTS_PID_FILE(NR) ((((NR))==1) ? "osmo-bts1" : "osmo-bts2")
#include "utils.h"
#include "l1_if.h"
#include "hw_misc.h"
#include "oml_router.h"
#include "misc/lc15bts_bid.h"
int pcu_direct = 0;
static const char *config_file = "lc15bts.cfg";
static int daemonize = 0;
static unsigned int dsp_trace = 0x71c00020;
static int rt_prio = -1;
static char *gsmtap_ip = 0;
static int trx_nr = 1;
int bts_model_init(struct gsm_bts *bts)
{
struct lc15l1_hdl *fl1h;
int rc;
fl1h = l1if_open(bts->c0, trx_nr);
if (!fl1h) {
LOGP(DL1C, LOGL_FATAL, "Cannot open L1 Interface\n");
return -EIO;
}
fl1h->dsp_trace_f = dsp_trace;
bts->c0->role_bts.l1h = fl1h;
rc = lc15bts_get_nominal_power(bts->c0);
if (rc < 0) {
LOGP(DL1C, LOGL_NOTICE, "Cannot determine nominal "
"transmit power. Assuming 37dBm.\n");
rc = 37;
}
bts->c0->nominal_power = rc;
bts->c0->power_params.trx_p_max_out_mdBm = to_mdB(rc);
bts_model_vty_init(bts);
return 0;
}
int bts_model_oml_estab(struct gsm_bts *bts)
{
struct lc15l1_hdl *fl1h = bts->c0->role_bts.l1h;
l1if_reset(fl1h);
return 0;
}
void bts_update_status(enum bts_global_status which, int on)
{
static uint64_t states = 0;
uint64_t old_states = states;
int led_rf_active_on;
if (on)
states |= (1ULL << which);
else
states &= ~(1ULL << which);
led_rf_active_on =
(states & (1ULL << BTS_STATUS_RF_ACTIVE)) &&
!(states & (1ULL << BTS_STATUS_RF_MUTE));
LOGP(DL1C, LOGL_INFO,
"Set global status #%d to %d (%04llx -> %04llx), LEDs: ACT %d\n",
which, on,
(long long)old_states, (long long)states,
led_rf_active_on);
lc15bts_led_set(led_rf_active_on ? LED_GREEN : LED_OFF);
}
static void print_help()
{
printf( "Some useful options:\n"
" -h --help this text\n"
" -d --debug MASK Enable debugging (e.g. -d DRSL:DOML:DLAPDM)\n"
" -D --daemonize For the process into a background daemon\n"
" -c --config-file Specify the filename of the config file\n"
" -s --disable-color Don't use colors in stderr log output\n"
" -T --timestamp Prefix every log line with a timestamp\n"
" -V --version Print version information and exit\n"
" -e --log-level Set a global log-level\n"
" -p --dsp-trace Set DSP trace flags\n"
" -r --realtime PRIO Use SCHED_RR with the specified priority\n"
" -w --hw-version Print the targeted HW Version\n"
" -M --pcu-direct Force PCU to access message queue for "
"PDCH dchannel directly\n"
" -i --gsmtap-ip The destination IP used for GSMTAP.\n"
" -n --hw-trx-nr Hardware TRX number <1-2>\n"
);
}
static void print_hwversion()
{
int rev;
int model;
static char model_name[64] = {0, };
snprintf(model_name, sizeof(model_name), "NuRAN Litecell 1.5 BTS");
rev = lc15bts_rev_get();
if (rev >= 0) {
snprintf(model_name, sizeof(model_name), "%s Rev %c",
model_name, (char)rev);
}
model = lc15bts_model_get();
if (model >= 0) {
snprintf(model_name, sizeof(model_name), "%s (%05X)",
model_name, model);
}
printf(model_name);
}
/* FIXME: finally get some option parsing code into libosmocore */
static void handle_options(int argc, char **argv)
{
while (1) {
int option_idx = 0, c;
static const struct option long_options[] = {
/* FIXME: all those are generic Osmocom app options */
{ "help", 0, 0, 'h' },
{ "debug", 1, 0, 'd' },
{ "daemonize", 0, 0, 'D' },
{ "config-file", 1, 0, 'c' },
{ "disable-color", 0, 0, 's' },
{ "timestamp", 0, 0, 'T' },
{ "version", 0, 0, 'V' },
{ "log-level", 1, 0, 'e' },
{ "dsp-trace", 1, 0, 'p' },
{ "hw-version", 0, 0, 'w' },
{ "pcu-direct", 0, 0, 'M' },
{ "realtime", 1, 0, 'r' },
{ "gsmtap-ip", 1, 0, 'i' },
{ "hw-trx-nr", 1, 0, 'n' },
{ 0, 0, 0, 0 }
};
c = getopt_long(argc, argv, "hc:d:Dc:sTVe:p:w:Mr:n:",
long_options, &option_idx);
if (c == -1)
break;
switch (c) {
case 'h':
print_help();
exit(0);
break;
case 's':
log_set_use_color(osmo_stderr_target, 0);
break;
case 'd':
log_parse_category_mask(osmo_stderr_target, optarg);
break;
case 'D':
daemonize = 1;
break;
case 'c':
config_file = optarg;
break;
case 'T':
log_set_print_timestamp(osmo_stderr_target, 1);
break;
case 'M':
pcu_direct = 1;
break;
case 'V':
print_version(1);
exit(0);
break;
case 'e':
log_set_log_level(osmo_stderr_target, atoi(optarg));
break;
case 'p':
dsp_trace = strtoul(optarg, NULL, 16);
break;
case 'w':
print_hwversion();
exit(0);
break;
case 'r':
rt_prio = atoi(optarg);
break;
case 'i':
gsmtap_ip = optarg;
break;
case 'n':
trx_nr = atoi(optarg);
break;
default:
break;
}
}
}
static struct gsm_bts *bts;
static void signal_handler(int signal)
{
fprintf(stderr, "signal %u received\n", signal);
switch (signal) {
case SIGINT:
//osmo_signal_dispatch(SS_GLOBAL, S_GLOBAL_SHUTDOWN, NULL);
bts_shutdown(bts, "SIGINT");
break;
case SIGABRT:
case SIGUSR1:
case SIGUSR2:
talloc_report_full(tall_bts_ctx, stderr);
break;
default:
break;
}
}
static int write_pid_file(char *procname)
{
FILE *outf;
char tmp[PATH_MAX+1];
snprintf(tmp, sizeof(tmp)-1, "/var/run/%s.pid", procname);
tmp[PATH_MAX-1] = '\0';
outf = fopen(tmp, "w");
if (!outf)
return -1;
fprintf(outf, "%d\n", getpid());
fclose(outf);
return 0;
}
extern int lc15bts_ctrlif_inst_cmds(void);
int main(int argc, char **argv)
{
struct stat st;
struct sched_param param;
struct gsm_bts_role_bts *btsb;
struct e1inp_line *line;
void *tall_msgb_ctx;
struct osmo_fd accept_fd, read_fd;
int vty_port;
int rc;
tall_bts_ctx = talloc_named_const(NULL, 1, "lc15BTS context");
tall_msgb_ctx = talloc_named_const(tall_bts_ctx, 1, "msgb");
msgb_set_talloc_ctx(tall_msgb_ctx);
bts_log_init(NULL);
bts = gsm_bts_alloc(tall_bts_ctx);
vty_init(&bts_vty_info);
e1inp_vty_init();
bts_vty_init(bts, &bts_log_info);
handle_options(argc, argv);
/* enable realtime priority for us */
if (rt_prio != -1) {
memset(&param, 0, sizeof(param));
param.sched_priority = rt_prio;
rc = sched_setscheduler(getpid(), SCHED_RR, &param);
if (rc != 0) {
fprintf(stderr, "Setting SCHED_RR priority(%d) failed: %s\n",
param.sched_priority, strerror(errno));
exit(1);
}
}
if (gsmtap_ip) {
gsmtap = gsmtap_source_init(gsmtap_ip, GSMTAP_UDP_PORT, 1);
if (!gsmtap) {
fprintf(stderr, "Failed during gsmtap_init()\n");
exit(1);
}
gsmtap_source_add_sink(gsmtap);
}
if (bts_init(bts) < 0) {
fprintf(stderr, "unable to open bts\n");
exit(1);
}
btsb = bts_role_bts(bts);
btsb->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3);
abis_init(bts);
rc = vty_read_config_file(config_file, NULL);
if (rc < 0) {
fprintf(stderr, "Failed to parse the config file: '%s'\n",
config_file);
exit(1);
}
if (stat(LC15BTS_RF_LOCK_PATH(trx_nr), &st) == 0) {
LOGP(DL1C, LOGL_NOTICE, "Not starting BTS due to RF_LOCK file present\n");
exit(23);
}
write_pid_file(LC15BTS_PID_FILE(trx_nr));
bts_controlif_setup(bts);
vty_port = (trx_nr == 1) ? OSMO_VTY_PORT_BTS : (OSMO_VTY_PORT_BTS + 1000);
rc = telnet_init(tall_bts_ctx, NULL, vty_port);
if (rc < 0) {
fprintf(stderr, "Error initializing telnet\n");
exit(1);
}
/* NTQD: For testing, we only support PCU on the first TRX */
if (trx_nr == 1) {
if (pcu_sock_init()) {
fprintf(stderr, "PCU L1 socket failed\n");
exit(1);
}
}
signal(SIGINT, &signal_handler);
//signal(SIGABRT, &signal_handler);
signal(SIGUSR1, &signal_handler);
signal(SIGUSR2, &signal_handler);
osmo_init_ignore_signals();
rc = oml_router_init(bts, OML_ROUTER_PATH, &accept_fd, &read_fd);
if (rc < 0) {
fprintf(stderr, "Error creating the OML router: %s rc=%d\n",
OML_ROUTER_PATH, rc);
exit(1);
}
if (!btsb->bsc_oml_host) {
fprintf(stderr, "Cannot start BTS without knowing BSC OML IP\n");
exit(1);
}
line = abis_open(bts, btsb->bsc_oml_host, "lc15BTS");
if (!line) {
fprintf(stderr, "unable to connect to BSC\n");
exit(2);
}
if (daemonize) {
rc = osmo_daemonize();
if (rc < 0) {
perror("Error during daemonize");
exit(1);
}
}
while (1) {
log_reset_context();
osmo_select_main(0);
}
}
void bts_model_abis_close(struct gsm_bts *bts)
{
/* for now, we simply terminate the program and re-spawn */
bts_shutdown(bts, "Abis close");
}

View File

@ -0,0 +1,139 @@
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include "lc15bts_bid.h"
#define BOARD_REV_SYSFS "/sys/devices/0.lc15/revision"
#define BOARD_OPT_SYSFS "/sys/devices/0.lc15/option"
static const int option_type_mask[_NUM_OPTION_TYPES] = {
[LC15BTS_OPTION_OCXO] = 0x07,
[LC15BTS_OPTION_FPGA] = 0x03,
[LC15BTS_OPTION_PA] = 0x01,
[LC15BTS_OPTION_BAND] = 0x03,
[LC15BTS_OPTION_TX_ISO_BYP] = 0x01,
[LC15BTS_OPTION_RX_DUP_BYP] = 0x01,
[LC15BTS_OPTION_RX_PB_BYP] = 0x01,
[LC15BTS_OPTION_RX_DIV] = 0x01,
[LC15BTS_OPTION_RX1A] = 0x01,
[LC15BTS_OPTION_RX1B] = 0x01,
[LC15BTS_OPTION_RX2A] = 0x01,
[LC15BTS_OPTION_RX2B] = 0x01,
[LC15BTS_OPTION_DDR_32B] = 0x01,
[LC15BTS_OPTION_DDR_ECC] = 0x01,
[LC15BTS_OPTION_LOG_DET] = 0x01,
[LC15BTS_OPTION_DUAL_LOG_DET] = 0x01,
};
static const int option_type_shift[_NUM_OPTION_TYPES] = {
[LC15BTS_OPTION_OCXO] = 0,
[LC15BTS_OPTION_FPGA] = 3,
[LC15BTS_OPTION_PA] = 5,
[LC15BTS_OPTION_BAND] = 6,
[LC15BTS_OPTION_TX_ISO_BYP] = 8,
[LC15BTS_OPTION_RX_DUP_BYP] = 9,
[LC15BTS_OPTION_RX_PB_BYP] = 10,
[LC15BTS_OPTION_RX_DIV] = 11,
[LC15BTS_OPTION_RX1A] = 12,
[LC15BTS_OPTION_RX1B] = 13,
[LC15BTS_OPTION_RX2A] = 14,
[LC15BTS_OPTION_RX2B] = 15,
[LC15BTS_OPTION_DDR_32B] = 16,
[LC15BTS_OPTION_DDR_ECC] = 17,
[LC15BTS_OPTION_LOG_DET] = 18,
[LC15BTS_OPTION_DUAL_LOG_DET] = 19,
};
static int board_rev = -1;
static int board_option = -1;
int lc15bts_rev_get(void)
{
FILE *fp;
char rev;
if (board_rev != -1) {
return board_rev;
}
fp = fopen(BOARD_REV_SYSFS, "r");
if (fp == NULL) return -1;
if (fscanf(fp, "%c", &rev) != 1) {
fclose( fp );
return -1;
}
fclose(fp);
board_rev = rev;
return board_rev;
}
int lc15bts_model_get(void)
{
FILE *fp;
int opt;
if (board_option == -1) {
fp = fopen(BOARD_OPT_SYSFS, "r");
if (fp == NULL) {
return -1;
}
if (fscanf(fp, "%X", &opt) != 1) {
fclose( fp );
return -1;
}
fclose(fp);
board_option = opt;
}
return board_option;
}
int lc15bts_option_get(enum lc15bts_option_type type)
{
int rc;
int option;
if (type >= _NUM_OPTION_TYPES) {
return -EINVAL;
}
if (board_option == -1) {
rc = lc15bts_model_get();
if (rc < 0) return rc;
}
option = (board_option >> option_type_shift[type])
& option_type_mask[type];
return option;
}

View File

@ -0,0 +1,51 @@
#ifndef _LC15BTS_BOARD_H
#define _LC15BTS_BOARD_H
#include <stdint.h>
enum lc15bts_option_type {
LC15BTS_OPTION_OCXO,
LC15BTS_OPTION_FPGA,
LC15BTS_OPTION_PA,
LC15BTS_OPTION_BAND,
LC15BTS_OPTION_TX_ISO_BYP,
LC15BTS_OPTION_RX_DUP_BYP,
LC15BTS_OPTION_RX_PB_BYP,
LC15BTS_OPTION_RX_DIV,
LC15BTS_OPTION_RX1A,
LC15BTS_OPTION_RX1B,
LC15BTS_OPTION_RX2A,
LC15BTS_OPTION_RX2B,
LC15BTS_OPTION_DDR_32B,
LC15BTS_OPTION_DDR_ECC,
LC15BTS_OPTION_LOG_DET,
LC15BTS_OPTION_DUAL_LOG_DET,
_NUM_OPTION_TYPES
};
enum lc15bts_ocxo_type {
LC15BTS_OCXO_BILAY_NVG45AV2072,
LC15BTS_OCXO_TAITIEN_NJ26M003,
_NUM_OCXO_TYPES
};
enum lc15bts_fpga_type {
LC15BTS_FPGA_35T,
LC15BTS_FPGA_50T,
LC15BTS_FPGA_75T,
LC15BTS_FPGA_100T,
_NUM_FPGA_TYPES
};
enum lc15bts_gsm_band {
LC15BTS_BAND_850,
LC15BTS_BAND_900,
LC15BTS_BAND_1800,
LC15BTS_BAND_1900,
};
int lc15bts_rev_get(void);
int lc15bts_model_get(void);
int lc15bts_option_get(enum lc15bts_option_type type);
#endif

View File

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

View File

@ -0,0 +1,16 @@
#ifndef _LC15BTS_CLOCK_H
#define _LC15BTS_CLOCK_H
int lc15bts_clock_err_open(void);
void lc15bts_clock_err_close(void);
int lc15bts_clock_err_reset(void);
int lc15bts_clock_err_get(int *fault, int *error_ppt,
int *accuracy_ppq, int *interval_sec);
int lc15bts_clock_dac_open(void);
void lc15bts_clock_dac_close(void);
int lc15bts_clock_dac_get(int *dac_value);
int lc15bts_clock_dac_set(int dac_value);
int lc15bts_clock_dac_save(void);
#endif

View File

@ -0,0 +1,297 @@
/* Main program for NuRAN Wireless Litecell 1.5 BTS management daemon */
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* Based on sysmoBTS:
* sysmobts_mgr.c
* (C) 2012 by Harald Welte <laforge@gnumonks.org>
* (C) 2014 by Holger Hans Peter Freyther
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <getopt.h>
#include <limits.h>
#include <sys/signal.h>
#include <sys/stat.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/application.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/msgb.h>
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/logging.h>
#include <osmocom/vty/ports.h>
#include "misc/lc15bts_misc.h"
#include "misc/lc15bts_mgr.h"
#include "misc/lc15bts_par.h"
#include "misc/lc15bts_bid.h"
#include "misc/lc15bts_power.h"
static int no_rom_write = 0;
static int daemonize = 0;
void *tall_mgr_ctx;
/* every 6 hours means 365*4 = 1460 rom writes per year (max) */
#define TEMP_TIMER_SECS (6 * 3600)
/* every 1 hours means 365*24 = 8760 rom writes per year (max) */
#define HOURS_TIMER_SECS (1 * 3600)
/* the initial state */
static struct lc15bts_mgr_instance manager = {
.config_file = "lc15bts-mgr.cfg",
.temp = {
.supply_limit = {
.thresh_warn = 60,
.thresh_crit = 78,
},
.soc_limit = {
.thresh_warn = 60,
.thresh_crit = 78,
},
.fpga_limit = {
.thresh_warn = 60,
.thresh_crit = 78,
},
.memory_limit = {
.thresh_warn = 60,
.thresh_crit = 78,
},
.tx1_limit = {
.thresh_warn = 60,
.thresh_crit = 78,
},
.tx2_limit = {
.thresh_warn = 60,
.thresh_crit = 78,
},
.pa1_limit = {
.thresh_warn = 60,
.thresh_crit = 78,
},
.pa2_limit = {
.thresh_warn = 60,
.thresh_crit = 78,
},
.action_warn = 0,
.action_crit = TEMP_ACT_PA1_OFF | TEMP_ACT_PA2_OFF,
.state = STATE_NORMAL,
}
};
static struct osmo_timer_list temp_timer;
static void check_temp_timer_cb(void *unused)
{
lc15bts_check_temp(no_rom_write);
osmo_timer_schedule(&temp_timer, TEMP_TIMER_SECS, 0);
}
static struct osmo_timer_list hours_timer;
static void hours_timer_cb(void *unused)
{
lc15bts_update_hours(no_rom_write);
osmo_timer_schedule(&hours_timer, HOURS_TIMER_SECS, 0);
}
static void print_help(void)
{
printf("lc15bts-mgr [-nsD] [-d cat]\n");
printf(" -n Do not write to ROM\n");
printf(" -s Disable color\n");
printf(" -d CAT enable debugging\n");
printf(" -D daemonize\n");
printf(" -c Specify the filename of the config file\n");
}
static int parse_options(int argc, char **argv)
{
int opt;
while ((opt = getopt(argc, argv, "nhsd:c:")) != -1) {
switch (opt) {
case 'n':
no_rom_write = 1;
break;
case 'h':
print_help();
return -1;
case 's':
log_set_use_color(osmo_stderr_target, 0);
break;
case 'd':
log_parse_category_mask(osmo_stderr_target, optarg);
break;
case 'D':
daemonize = 1;
break;
case 'c':
manager.config_file = optarg;
break;
default:
return -1;
}
}
return 0;
}
static void signal_handler(int signal)
{
fprintf(stderr, "signal %u received\n", signal);
switch (signal) {
case SIGINT:
lc15bts_check_temp(no_rom_write);
lc15bts_update_hours(no_rom_write);
exit(0);
break;
case SIGABRT:
case SIGUSR1:
case SIGUSR2:
talloc_report_full(tall_mgr_ctx, stderr);
break;
default:
break;
}
}
static struct log_info_cat mgr_log_info_cat[] = {
[DTEMP] = {
.name = "DTEMP",
.description = "Temperature monitoring",
.color = "\033[1;35m",
.enabled = 1, .loglevel = LOGL_INFO,
},
[DFW] = {
.name = "DFW",
.description = "Firmware management",
.color = "\033[1;36m",
.enabled = 1, .loglevel = LOGL_INFO,
},
[DFIND] = {
.name = "DFIND",
.description = "ipaccess-find handling",
.color = "\033[1;37m",
.enabled = 1, .loglevel = LOGL_INFO,
},
[DCALIB] = {
.name = "DCALIB",
.description = "Calibration handling",
.color = "\033[1;37m",
.enabled = 1, .loglevel = LOGL_INFO,
},
};
static const struct log_info mgr_log_info = {
.cat = mgr_log_info_cat,
.num_cat = ARRAY_SIZE(mgr_log_info_cat),
};
static int mgr_log_init(void)
{
osmo_init_logging(&mgr_log_info);
return 0;
}
int main(int argc, char **argv)
{
void *tall_msgb_ctx;
int rc;
tall_mgr_ctx = talloc_named_const(NULL, 1, "bts manager");
tall_msgb_ctx = talloc_named_const(tall_mgr_ctx, 1, "msgb");
msgb_set_talloc_ctx(tall_msgb_ctx);
mgr_log_init();
osmo_init_ignore_signals();
signal(SIGINT, &signal_handler);
signal(SIGUSR1, &signal_handler);
signal(SIGUSR2, &signal_handler);
rc = parse_options(argc, argv);
if (rc < 0)
exit(2);
lc15bts_mgr_vty_init();
logging_vty_add_cmds(&mgr_log_info);
rc = lc15bts_mgr_parse_config(&manager);
if (rc < 0) {
LOGP(DFIND, LOGL_FATAL, "Cannot parse config file\n");
exit(1);
}
rc = telnet_init(tall_msgb_ctx, NULL, OSMO_VTY_PORT_BTSMGR);
if (rc < 0) {
fprintf(stderr, "Error initializing telnet\n");
exit(1);
}
/* start temperature check timer */
temp_timer.cb = check_temp_timer_cb;
check_temp_timer_cb(NULL);
/* start operational hours timer */
hours_timer.cb = hours_timer_cb;
hours_timer_cb(NULL);
/* Enable the PAs */
rc = lc15bts_power_set(LC15BTS_POWER_PA1, 1);
if (rc < 0) {
exit(3);
}
rc = lc15bts_power_set(LC15BTS_POWER_PA2, 1);
if (rc < 0) {
exit(3);
}
/* handle broadcast messages for ipaccess-find */
if (lc15bts_mgr_nl_init() != 0)
exit(3);
/* Initialize the temperature control */
lc15bts_mgr_temp_init(&manager);
if (lc15bts_mgr_calib_init(&manager) != 0)
exit(3);
if (daemonize) {
rc = osmo_daemonize();
if (rc < 0) {
perror("Error during daemonize");
exit(1);
}
}
while (1) {
log_reset_context();
osmo_select_main(0);
}
}

View File

@ -0,0 +1,111 @@
#ifndef _LC15BTS_MGR_H
#define _LC15BTS_MGR_H
#include <osmocom/vty/vty.h>
#include <osmocom/vty/command.h>
#include <osmocom/core/select.h>
#include <osmocom/core/timer.h>
#include <stdint.h>
enum {
DTEMP,
DFW,
DFIND,
DCALIB,
};
// TODO NTQD: Define new actions like reducing output power, limit ARM core speed, shutdown second TRX/PA, ...
enum {
#if 0
TEMP_ACT_PWR_CONTRL = 0x1,
#endif
TEMP_ACT_PA1_OFF = 0x2,
TEMP_ACT_PA2_OFF = 0x4,
TEMP_ACT_BTS_SRV_OFF = 0x10,
};
/* actions only for normal state */
enum {
#if 0
TEMP_ACT_NORM_PW_CONTRL = 0x1,
#endif
TEMP_ACT_NORM_PA1_ON = 0x2,
TEMP_ACT_NORM_PA2_ON = 0x4,
TEMP_ACT_NORM_BTS_SRV_ON= 0x10,
};
enum lc15bts_temp_state {
STATE_NORMAL, /* Everything is fine */
STATE_WARNING_HYST, /* Go back to normal next? */
STATE_WARNING, /* We are above the warning threshold */
STATE_CRITICAL, /* We have an issue. Wait for below warning */
};
/**
* Temperature Limits. We separate from a threshold
* that will generate a warning and one that is so
* severe that an action will be taken.
*/
struct lc15bts_temp_limit {
int thresh_warn;
int thresh_crit;
};
enum mgr_vty_node {
MGR_NODE = _LAST_OSMOVTY_NODE + 1,
ACT_NORM_NODE,
ACT_WARN_NODE,
ACT_CRIT_NODE,
LIMIT_SUPPLY_NODE,
LIMIT_SOC_NODE,
LIMIT_FPGA_NODE,
LIMIT_MEMORY_NODE,
LIMIT_TX1_NODE,
LIMIT_TX2_NODE,
LIMIT_PA1_NODE,
LIMIT_PA2_NODE,
};
struct lc15bts_mgr_instance {
const char *config_file;
struct {
int action_norm;
int action_warn;
int action_crit;
enum lc15bts_temp_state state;
struct lc15bts_temp_limit supply_limit;
struct lc15bts_temp_limit soc_limit;
struct lc15bts_temp_limit fpga_limit;
struct lc15bts_temp_limit memory_limit;
struct lc15bts_temp_limit tx1_limit;
struct lc15bts_temp_limit tx2_limit;
struct lc15bts_temp_limit pa1_limit;
struct lc15bts_temp_limit pa2_limit;
} temp;
struct {
int state;
int calib_from_loop;
struct osmo_timer_list calib_timeout;
} calib;
};
int lc15bts_mgr_vty_init(void);
int lc15bts_mgr_parse_config(struct lc15bts_mgr_instance *mgr);
int lc15bts_mgr_nl_init(void);
int lc15bts_mgr_temp_init(struct lc15bts_mgr_instance *mgr);
const char *lc15bts_mgr_temp_get_state(enum lc15bts_temp_state state);
int lc15bts_mgr_calib_init(struct lc15bts_mgr_instance *mgr);
int lc15bts_mgr_calib_run(struct lc15bts_mgr_instance *mgr);
extern void *tall_mgr_ctx;
#endif

View File

@ -0,0 +1,246 @@
/* OCXO calibration control for Litecell 1.5 BTS management daemon */
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* Based on sysmoBTS:
* sysmobts_mgr_calib.c
* (C) 2014,2015 by Holger Hans Peter Freyther
* (C) 2014 by Harald Welte for the IPA code from the oml router
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "misc/lc15bts_mgr.h"
#include "misc/lc15bts_misc.h"
#include "misc/lc15bts_clock.h"
#include "osmo-bts/msg_utils.h"
#include <osmocom/core/logging.h>
#include <osmocom/core/select.h>
#include <osmocom/ctrl/control_cmd.h>
#include <osmocom/gsm/ipa.h>
#include <osmocom/gsm/protocol/ipaccess.h>
#include <osmocom/abis/abis.h>
#include <osmocom/abis/e1_input.h>
#include <osmocom/abis/ipa.h>
static void calib_adjust(struct lc15bts_mgr_instance *mgr);
static void calib_state_reset(struct lc15bts_mgr_instance *mgr, int reason);
static void calib_loop_run(void *_data);
enum calib_state {
CALIB_INITIAL,
CALIB_IN_PROGRESS,
};
enum calib_result {
CALIB_FAIL_START,
CALIB_FAIL_GPSFIX,
CALIB_FAIL_CLKERR,
CALIB_FAIL_OCXODAC,
CALIB_SUCCESS,
};
static void calib_start(struct lc15bts_mgr_instance *mgr)
{
int rc;
rc = lc15bts_clock_err_open();
if (rc != 0) {
LOGP(DCALIB, LOGL_ERROR, "Failed to open clock error module %d\n", rc);
calib_state_reset(mgr, CALIB_FAIL_CLKERR);
return;
}
rc = lc15bts_clock_dac_open();
if (rc != 0) {
LOGP(DCALIB, LOGL_ERROR, "Failed to open OCXO dac module %d\n", rc);
calib_state_reset(mgr, CALIB_FAIL_OCXODAC);
return;
}
calib_adjust(mgr);
}
static void calib_adjust(struct lc15bts_mgr_instance *mgr)
{
int rc;
int fault;
int error_ppt;
int accuracy_ppq;
int interval_sec;
int dac_value;
int new_dac_value;
double dac_correction;
rc = lc15bts_clock_err_get(&fault, &error_ppt,
&accuracy_ppq, &interval_sec);
if (rc < 0) {
LOGP(DCALIB, LOGL_ERROR,
"Failed to get clock error measurement %d\n", rc);
calib_state_reset(mgr, CALIB_FAIL_CLKERR);
return;
}
if (fault) {
LOGP(DCALIB, LOGL_NOTICE, "GPS has no fix\n");
calib_state_reset(mgr, CALIB_FAIL_GPSFIX);
return;
}
rc = lc15bts_clock_dac_get(&dac_value);
if (rc < 0) {
LOGP(DCALIB, LOGL_ERROR,
"Failed to get OCXO dac value %d\n", rc);
calib_state_reset(mgr, CALIB_FAIL_OCXODAC);
return;
}
LOGP(DCALIB, LOGL_NOTICE,
"Calibration ERR(%f PPB) ACC(%f PPB) INT(%d) DAC(%d)\n",
error_ppt / 1000., accuracy_ppq / 1000000., interval_sec, dac_value);
/* 1 unit of correction equal about 0.5 - 1 PPB correction */
dac_correction = (int)(-error_ppt * 0.00056);
new_dac_value = dac_value + dac_correction + 0.5;
/* We have a fix, make sure the measured error is
meaningful (10 times the accuracy) */
if ((new_dac_value != dac_value) && ((100l * abs(error_ppt)) > accuracy_ppq)) {
if (new_dac_value > 4095)
dac_value = 4095;
else if (new_dac_value < 0)
dac_value = 0;
else
dac_value = new_dac_value;
LOGP(DCALIB, LOGL_NOTICE,
"Going to apply %d as new clock setting.\n",
dac_value);
rc = lc15bts_clock_dac_set(dac_value);
if (rc < 0) {
LOGP(DCALIB, LOGL_ERROR,
"Failed to set OCXO dac value %d\n", rc);
calib_state_reset(mgr, CALIB_FAIL_OCXODAC);
return;
}
rc = lc15bts_clock_err_reset();
if (rc < 0) {
LOGP(DCALIB, LOGL_ERROR,
"Failed to set reset clock error module %d\n", rc);
calib_state_reset(mgr, CALIB_FAIL_CLKERR);
return;
}
}
/* Save the correction value in the DAC eeprom if the
frequency has been stable for 24 hours */
else if (interval_sec >= (24 * 60 * 60)) {
rc = lc15bts_clock_dac_save();
if (rc < 0) {
LOGP(DCALIB, LOGL_ERROR,
"Failed to save OCXO dac value %d\n", rc);
calib_state_reset(mgr, CALIB_FAIL_OCXODAC);
}
rc = lc15bts_clock_err_reset();
if (rc < 0) {
LOGP(DCALIB, LOGL_ERROR,
"Failed to set reste clock error module %d\n", rc);
calib_state_reset(mgr, CALIB_FAIL_CLKERR);
}
}
calib_state_reset(mgr, CALIB_SUCCESS);
return;
}
static void calib_close(struct lc15bts_mgr_instance *mgr)
{
lc15bts_clock_err_close();
lc15bts_clock_dac_close();
}
static void calib_state_reset(struct lc15bts_mgr_instance *mgr, int outcome)
{
if (mgr->calib.calib_from_loop) {
/*
* In case of success calibrate in two hours again
* and in case of a failure in some minutes.
*
* TODO NTQ: Select timeout based on last error and accuracy
*/
int timeout = 60;
//int timeout = 2 * 60 * 60;
//if (outcome != CALIB_SUCESS) }
// timeout = 5 * 60;
//}
mgr->calib.calib_timeout.data = mgr;
mgr->calib.calib_timeout.cb = calib_loop_run;
osmo_timer_schedule(&mgr->calib.calib_timeout, timeout, 0);
}
mgr->calib.state = CALIB_INITIAL;
calib_close(mgr);
}
static int calib_run(struct lc15bts_mgr_instance *mgr, int from_loop)
{
if (mgr->calib.state != CALIB_INITIAL) {
LOGP(DCALIB, LOGL_ERROR, "Calib is already in progress.\n");
return -1;
}
mgr->calib.calib_from_loop = from_loop;
/* From now on everything will be handled from the failure */
mgr->calib.state = CALIB_IN_PROGRESS;
calib_start(mgr);
return 0;
}
static void calib_loop_run(void *_data)
{
int rc;
struct lc15bts_mgr_instance *mgr = _data;
LOGP(DCALIB, LOGL_NOTICE, "Going to calibrate the system.\n");
rc = calib_run(mgr, 1);
if (rc != 0) {
calib_state_reset(mgr, CALIB_FAIL_START);
}
}
int lc15bts_mgr_calib_run(struct lc15bts_mgr_instance *mgr)
{
return calib_run(mgr, 0);
}
int lc15bts_mgr_calib_init(struct lc15bts_mgr_instance *mgr)
{
mgr->calib.state = CALIB_INITIAL;
mgr->calib.calib_timeout.data = mgr;
mgr->calib.calib_timeout.cb = calib_loop_run;
osmo_timer_schedule(&mgr->calib.calib_timeout, 0, 0);
return 0;
}

View File

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

View File

@ -0,0 +1,353 @@
/* Temperature control for NuRAN Litecell 1.5 BTS management daemon */
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* Based on sysmoBTS:
* sysmobts_mgr_temp.c
* (C) 2014 by Holger Hans Peter Freyther
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "misc/lc15bts_mgr.h"
#include "misc/lc15bts_misc.h"
#include "misc/lc15bts_temp.h"
#include "misc/lc15bts_power.h"
#include <osmo-bts/logging.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/utils.h>
static struct lc15bts_mgr_instance *s_mgr;
static struct osmo_timer_list temp_ctrl_timer;
static const struct value_string state_names[] = {
{ STATE_NORMAL, "NORMAL" },
{ STATE_WARNING_HYST, "WARNING (HYST)" },
{ STATE_WARNING, "WARNING" },
{ STATE_CRITICAL, "CRITICAL" },
{ 0, NULL }
};
const char *lc15bts_mgr_temp_get_state(enum lc15bts_temp_state state)
{
return get_value_string(state_names, state);
}
static int next_state(enum lc15bts_temp_state current_state, int critical, int warning)
{
int next_state = -1;
switch (current_state) {
case STATE_NORMAL:
if (critical)
next_state = STATE_CRITICAL;
else if (warning)
next_state = STATE_WARNING;
break;
case STATE_WARNING_HYST:
if (critical)
next_state = STATE_CRITICAL;
else if (warning)
next_state = STATE_WARNING;
else
next_state = STATE_NORMAL;
break;
case STATE_WARNING:
if (critical)
next_state = STATE_CRITICAL;
else if (!warning)
next_state = STATE_WARNING_HYST;
break;
case STATE_CRITICAL:
if (!critical && !warning)
next_state = STATE_WARNING;
break;
};
return next_state;
}
static void handle_normal_actions(int actions)
{
/* switch on the PA */
if (actions & TEMP_ACT_NORM_PA1_ON) {
if (lc15bts_power_set(LC15BTS_POWER_PA1, 1) != 0) {
LOGP(DTEMP, LOGL_ERROR,
"Failed to switch on the PA #1\n");
} else {
LOGP(DTEMP, LOGL_NOTICE,
"Switched on the PA #1 as normal action.\n");
}
}
if (actions & TEMP_ACT_NORM_PA2_ON) {
if (lc15bts_power_set(LC15BTS_POWER_PA2, 1) != 0) {
LOGP(DTEMP, LOGL_ERROR,
"Failed to switch on the PA #2\n");
} else {
LOGP(DTEMP, LOGL_NOTICE,
"Switched on the PA #2 as normal action.\n");
}
}
if (actions & TEMP_ACT_NORM_BTS_SRV_ON) {
LOGP(DTEMP, LOGL_NOTICE,
"Going to switch on the BTS service\n");
/*
* TODO: use/create something like nspawn that serializes
* and used SIGCHLD/waitpid to pick up the dead processes
* without invoking shell.
*/
system("/bin/systemctl start lc15bts.service");
}
}
static void handle_actions(int actions)
{
/* switch off the PA */
if (actions & TEMP_ACT_PA2_OFF) {
if (lc15bts_power_set(LC15BTS_POWER_PA2, 0) != 0) {
LOGP(DTEMP, LOGL_ERROR,
"Failed to switch off the PA #2. Stop BTS?\n");
} else {
LOGP(DTEMP, LOGL_NOTICE,
"Switched off the PA #2 due temperature.\n");
}
}
if (actions & TEMP_ACT_PA1_OFF) {
if (lc15bts_power_set(LC15BTS_POWER_PA1, 0) != 0) {
LOGP(DTEMP, LOGL_ERROR,
"Failed to switch off the PA #1. Stop BTS?\n");
} else {
LOGP(DTEMP, LOGL_NOTICE,
"Switched off the PA #1 due temperature.\n");
}
}
if (actions & TEMP_ACT_BTS_SRV_OFF) {
LOGP(DTEMP, LOGL_NOTICE,
"Going to switch off the BTS service\n");
/*
* TODO: use/create something like nspawn that serializes
* and used SIGCHLD/waitpid to pick up the dead processes
* without invoking shell.
*/
system("/bin/systemctl stop lc15bts.service");
}
}
/**
* Go back to normal! Depending on the configuration execute the normal
* actions that could (start to) undo everything we did in the other
* states. What is still missing is the power increase/decrease depending
* on the state. E.g. starting from WARNING_HYST we might want to slowly
* ramp up the output power again.
*/
static void execute_normal_act(struct lc15bts_mgr_instance *manager)
{
LOGP(DTEMP, LOGL_NOTICE, "System is back to normal temperature.\n");
handle_normal_actions(manager->temp.action_norm);
}
static void execute_warning_act(struct lc15bts_mgr_instance *manager)
{
LOGP(DTEMP, LOGL_NOTICE, "System has reached temperature warning.\n");
handle_actions(manager->temp.action_warn);
}
static void execute_critical_act(struct lc15bts_mgr_instance *manager)
{
LOGP(DTEMP, LOGL_NOTICE, "System has reached critical warning.\n");
handle_actions(manager->temp.action_crit);
}
static void lc15bts_mgr_temp_handle(struct lc15bts_mgr_instance *manager,
int critical, int warning)
{
int new_state = next_state(manager->temp.state, critical, warning);
/* Nothing changed */
if (new_state < 0)
return;
LOGP(DTEMP, LOGL_NOTICE, "Moving from state %s to %s.\n",
get_value_string(state_names, manager->temp.state),
get_value_string(state_names, new_state));
manager->temp.state = new_state;
switch (manager->temp.state) {
case STATE_NORMAL:
execute_normal_act(manager);
break;
case STATE_WARNING_HYST:
/* do nothing? Maybe start to increase transmit power? */
break;
case STATE_WARNING:
execute_warning_act(manager);
break;
case STATE_CRITICAL:
execute_critical_act(manager);
break;
};
}
static void temp_ctrl_check()
{
int rc;
int warn_thresh_passed = 0;
int crit_thresh_passed = 0;
LOGP(DTEMP, LOGL_DEBUG, "Going to check the temperature.\n");
/* Read the current supply temperature */
rc = lc15bts_temp_get(LC15BTS_TEMP_SUPPLY, LC15BTS_TEMP_INPUT);
if (rc < 0) {
LOGP(DTEMP, LOGL_ERROR,
"Failed to read the supply temperature. rc=%d\n", rc);
warn_thresh_passed = crit_thresh_passed = 1;
} else {
int temp = rc / 1000;
if (temp > s_mgr->temp.supply_limit.thresh_warn)
warn_thresh_passed = 1;
if (temp > s_mgr->temp.supply_limit.thresh_crit)
crit_thresh_passed = 1;
LOGP(DTEMP, LOGL_DEBUG, "Supply temperature is: %d\n", temp);
}
/* Read the current SoC temperature */
rc = lc15bts_temp_get(LC15BTS_TEMP_SOC, LC15BTS_TEMP_INPUT);
if (rc < 0) {
LOGP(DTEMP, LOGL_ERROR,
"Failed to read the SoC temperature. rc=%d\n", rc);
warn_thresh_passed = crit_thresh_passed = 1;
} else {
int temp = rc / 1000;
if (temp > s_mgr->temp.soc_limit.thresh_warn)
warn_thresh_passed = 1;
if (temp > s_mgr->temp.soc_limit.thresh_crit)
crit_thresh_passed = 1;
LOGP(DTEMP, LOGL_DEBUG, "SoC temperature is: %d\n", temp);
}
/* Read the current fpga temperature */
rc = lc15bts_temp_get(LC15BTS_TEMP_FPGA, LC15BTS_TEMP_INPUT);
if (rc < 0) {
LOGP(DTEMP, LOGL_ERROR,
"Failed to read the fpga temperature. rc=%d\n", rc);
warn_thresh_passed = crit_thresh_passed = 1;
} else {
int temp = rc / 1000;
if (temp > s_mgr->temp.fpga_limit.thresh_warn)
warn_thresh_passed = 1;
if (temp > s_mgr->temp.fpga_limit.thresh_crit)
crit_thresh_passed = 1;
LOGP(DTEMP, LOGL_DEBUG, "FPGA temperature is: %d\n", temp);
}
/* Read the current memory temperature */
rc = lc15bts_temp_get(LC15BTS_TEMP_MEMORY, LC15BTS_TEMP_INPUT);
if (rc < 0) {
LOGP(DTEMP, LOGL_ERROR,
"Failed to read the memory temperature. rc=%d\n", rc);
warn_thresh_passed = crit_thresh_passed = 1;
} else {
int temp = rc / 1000;
if (temp > s_mgr->temp.memory_limit.thresh_warn)
warn_thresh_passed = 1;
if (temp > s_mgr->temp.memory_limit.thresh_crit)
crit_thresh_passed = 1;
LOGP(DTEMP, LOGL_DEBUG, "Memory temperature is: %d\n", temp);
}
/* Read the current TX #1 temperature */
rc = lc15bts_temp_get(LC15BTS_TEMP_TX1, LC15BTS_TEMP_INPUT);
if (rc < 0) {
LOGP(DTEMP, LOGL_ERROR,
"Failed to read the TX #1 temperature. rc=%d\n", rc);
warn_thresh_passed = crit_thresh_passed = 1;
} else {
int temp = rc / 1000;
if (temp > s_mgr->temp.tx1_limit.thresh_warn)
warn_thresh_passed = 1;
if (temp > s_mgr->temp.tx1_limit.thresh_crit)
crit_thresh_passed = 1;
LOGP(DTEMP, LOGL_DEBUG, "TX #1 temperature is: %d\n", temp);
}
/* Read the current TX #2 temperature */
rc = lc15bts_temp_get(LC15BTS_TEMP_TX2, LC15BTS_TEMP_INPUT);
if (rc < 0) {
LOGP(DTEMP, LOGL_ERROR,
"Failed to read the TX #2 temperature. rc=%d\n", rc);
warn_thresh_passed = crit_thresh_passed = 1;
} else {
int temp = rc / 1000;
if (temp > s_mgr->temp.tx2_limit.thresh_warn)
warn_thresh_passed = 1;
if (temp > s_mgr->temp.tx2_limit.thresh_crit)
crit_thresh_passed = 1;
LOGP(DTEMP, LOGL_DEBUG, "TX #2 temperature is: %d\n", temp);
}
/* Read the current PA #1 temperature */
rc = lc15bts_temp_get(LC15BTS_TEMP_PA1, LC15BTS_TEMP_INPUT);
if (rc < 0) {
LOGP(DTEMP, LOGL_ERROR,
"Failed to read the PA #1 temperature. rc=%d\n", rc);
warn_thresh_passed = crit_thresh_passed = 1;
} else {
int temp = rc / 1000;
if (temp > s_mgr->temp.pa1_limit.thresh_warn)
warn_thresh_passed = 1;
if (temp > s_mgr->temp.pa1_limit.thresh_crit)
crit_thresh_passed = 1;
LOGP(DTEMP, LOGL_DEBUG, "PA #1 temperature is: %d\n", temp);
}
/* Read the current PA #2 temperature */
rc = lc15bts_temp_get(LC15BTS_TEMP_PA2, LC15BTS_TEMP_INPUT);
if (rc < 0) {
LOGP(DTEMP, LOGL_ERROR,
"Failed to read the PA #2 temperature. rc=%d\n", rc);
warn_thresh_passed = crit_thresh_passed = 1;
} else {
int temp = rc / 1000;
if (temp > s_mgr->temp.pa2_limit.thresh_warn)
warn_thresh_passed = 1;
if (temp > s_mgr->temp.pa2_limit.thresh_crit)
crit_thresh_passed = 1;
LOGP(DTEMP, LOGL_DEBUG, "PA #2 temperature is: %d\n", temp);
}
lc15bts_mgr_temp_handle(s_mgr, crit_thresh_passed, warn_thresh_passed);
}
static void temp_ctrl_check_cb(void *unused)
{
temp_ctrl_check();
/* Check every two minutes? XXX make it configurable! */
osmo_timer_schedule(&temp_ctrl_timer, 2 * 60, 0);
}
int lc15bts_mgr_temp_init(struct lc15bts_mgr_instance *mgr)
{
s_mgr = mgr;
temp_ctrl_timer.cb = temp_ctrl_check_cb;
temp_ctrl_check_cb(NULL);
return 0;
}

View File

@ -0,0 +1,602 @@
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* Based on sysmoBTS:
* sysmobts_mgr_vty.c
* (C) 2014 by lc15com - s.f.m.c. GmbH
*
* All Rights Reserved
*
* Author: Alvaro Neira Ayuso <anayuso@lc15com.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <stdint.h>
#include <ctype.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <osmocom/vty/vty.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/misc.h>
#include <osmo-bts/logging.h>
#include "lc15bts_misc.h"
#include "lc15bts_mgr.h"
#include "lc15bts_temp.h"
#include "lc15bts_power.h"
#include "btsconfig.h"
static struct lc15bts_mgr_instance *s_mgr;
static const char copyright[] =
"(C) 2012 by Harald Welte <laforge@gnumonks.org>\r\n"
"(C) 2014 by Holger Hans Peter Freyther\r\n"
"(C) 2015 by Yves Godin <support@nuranwireless.com>\r\n"
"License AGPLv3+: GNU AGPL version 2 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
"This is free software: you are free to change and redistribute it.\r\n"
"There is NO WARRANTY, to the extent permitted by law.\r\n";
static enum node_type go_to_parent(struct vty *vty)
{
switch (vty->node) {
case MGR_NODE:
vty->node = CONFIG_NODE;
break;
case ACT_NORM_NODE:
case ACT_WARN_NODE:
case ACT_CRIT_NODE:
case LIMIT_SUPPLY_NODE:
case LIMIT_SOC_NODE:
case LIMIT_FPGA_NODE:
case LIMIT_MEMORY_NODE:
case LIMIT_TX1_NODE:
case LIMIT_TX2_NODE:
case LIMIT_PA1_NODE:
case LIMIT_PA2_NODE:
vty->node = MGR_NODE;
break;
default:
vty->node = CONFIG_NODE;
}
return vty->node;
}
static int is_config_node(struct vty *vty, int node)
{
switch (node) {
case MGR_NODE:
case ACT_NORM_NODE:
case ACT_WARN_NODE:
case ACT_CRIT_NODE:
case LIMIT_SUPPLY_NODE:
case LIMIT_SOC_NODE:
case LIMIT_FPGA_NODE:
case LIMIT_MEMORY_NODE:
case LIMIT_TX1_NODE:
case LIMIT_TX2_NODE:
case LIMIT_PA1_NODE:
case LIMIT_PA2_NODE:
return 1;
default:
return 0;
}
}
static struct vty_app_info vty_info = {
.name = "lc15bts-mgr",
.version = PACKAGE_VERSION,
.go_parent_cb = go_to_parent,
.is_config_node = is_config_node,
.copyright = copyright,
};
#define MGR_STR "Configure lc15bts-mgr\n"
static struct cmd_node mgr_node = {
MGR_NODE,
"%s(lc15bts-mgr)# ",
1,
};
static struct cmd_node act_norm_node = {
ACT_NORM_NODE,
"%s(action-normal)# ",
1,
};
static struct cmd_node act_warn_node = {
ACT_WARN_NODE,
"%s(action-warn)# ",
1,
};
static struct cmd_node act_crit_node = {
ACT_CRIT_NODE,
"%s(action-critical)# ",
1,
};
static struct cmd_node limit_supply_node = {
LIMIT_SUPPLY_NODE,
"%s(limit-supply)# ",
1,
};
static struct cmd_node limit_soc_node = {
LIMIT_SOC_NODE,
"%s(limit-soc)# ",
1,
};
static struct cmd_node limit_fpga_node = {
LIMIT_FPGA_NODE,
"%s(limit-fpga)# ",
1,
};
static struct cmd_node limit_memory_node = {
LIMIT_MEMORY_NODE,
"%s(limit-memory)# ",
1,
};
static struct cmd_node limit_tx1_node = {
LIMIT_TX1_NODE,
"%s(limit-tx1)# ",
1,
};
static struct cmd_node limit_tx2_node = {
LIMIT_TX2_NODE,
"%s(limit-tx2)# ",
1,
};
static struct cmd_node limit_pa1_node = {
LIMIT_PA1_NODE,
"%s(limit-pa1)# ",
1,
};
static struct cmd_node limit_pa2_node = {
LIMIT_PA2_NODE,
"%s(limit-pa2)# ",
1,
};
DEFUN(cfg_mgr, cfg_mgr_cmd,
"lc15bts-mgr",
MGR_STR)
{
vty->node = MGR_NODE;
return CMD_SUCCESS;
}
static void write_temp_limit(struct vty *vty, const char *name,
struct lc15bts_temp_limit *limit)
{
vty_out(vty, " %s%s", name, VTY_NEWLINE);
vty_out(vty, " threshold warning %d%s",
limit->thresh_warn, VTY_NEWLINE);
vty_out(vty, " threshold critical %d%s",
limit->thresh_crit, VTY_NEWLINE);
}
static void write_norm_action(struct vty *vty, const char *name, int actions)
{
vty_out(vty, " %s%s", name, VTY_NEWLINE);
vty_out(vty, " %spa1-on%s",
(actions & TEMP_ACT_NORM_PA1_ON) ? "" : "no ", VTY_NEWLINE);
vty_out(vty, " %spa2-on%s",
(actions & TEMP_ACT_NORM_PA2_ON) ? "" : "no ", VTY_NEWLINE);
vty_out(vty, " %sbts-service-on%s",
(actions & TEMP_ACT_NORM_BTS_SRV_ON) ? "" : "no ", VTY_NEWLINE);
}
static void write_action(struct vty *vty, const char *name, int actions)
{
vty_out(vty, " %s%s", name, VTY_NEWLINE);
vty_out(vty, " %spa1-off%s",
(actions & TEMP_ACT_PA1_OFF) ? "" : "no ", VTY_NEWLINE);
vty_out(vty, " %spa2-off%s",
(actions & TEMP_ACT_PA2_OFF) ? "" : "no ", VTY_NEWLINE);
vty_out(vty, " %sbts-service-off%s",
(actions & TEMP_ACT_BTS_SRV_OFF) ? "" : "no ", VTY_NEWLINE);
}
static int config_write_mgr(struct vty *vty)
{
vty_out(vty, "lc15bts-mgr%s", VTY_NEWLINE);
write_temp_limit(vty, "limits supply", &s_mgr->temp.supply_limit);
write_temp_limit(vty, "limits soc", &s_mgr->temp.soc_limit);
write_temp_limit(vty, "limits fpga", &s_mgr->temp.fpga_limit);
write_temp_limit(vty, "limits memory", &s_mgr->temp.memory_limit);
write_temp_limit(vty, "limits tx1", &s_mgr->temp.tx1_limit);
write_temp_limit(vty, "limits tx2", &s_mgr->temp.tx2_limit);
write_temp_limit(vty, "limits pa1", &s_mgr->temp.pa1_limit);
write_temp_limit(vty, "limits pa2", &s_mgr->temp.pa2_limit);
write_norm_action(vty, "actions normal", s_mgr->temp.action_norm);
write_action(vty, "actions warn", s_mgr->temp.action_warn);
write_action(vty, "actions critical", s_mgr->temp.action_crit);
return CMD_SUCCESS;
}
static int config_write_dummy(struct vty *vty)
{
return CMD_SUCCESS;
}
#define CFG_LIMIT(name, expl, switch_to, variable) \
DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \
"limits " #name, \
"Configure Limits\n" expl) \
{ \
vty->node = switch_to; \
vty->index = &s_mgr->temp.variable; \
return CMD_SUCCESS; \
}
CFG_LIMIT(supply, "SUPPLY\n", LIMIT_SUPPLY_NODE, supply_limit)
CFG_LIMIT(soc, "SOC\n", LIMIT_SOC_NODE, soc_limit)
CFG_LIMIT(fpga, "FPGA\n", LIMIT_FPGA_NODE, fpga_limit)
CFG_LIMIT(memory, "MEMORY\n", LIMIT_MEMORY_NODE, memory_limit)
CFG_LIMIT(tx1, "TX1\n", LIMIT_TX1_NODE, tx1_limit)
CFG_LIMIT(tx2, "TX2\n", LIMIT_TX2_NODE, tx2_limit)
CFG_LIMIT(pa1, "PA1\n", LIMIT_PA1_NODE, pa1_limit)
CFG_LIMIT(pa2, "PA2\n", LIMIT_PA2_NODE, pa2_limit)
#undef CFG_LIMIT
DEFUN(cfg_limit_warning, cfg_thresh_warning_cmd,
"threshold warning <0-200>",
"Threshold to reach\n" "Warning level\n" "Range\n")
{
struct lc15bts_temp_limit *limit = vty->index;
limit->thresh_warn = atoi(argv[0]);
return CMD_SUCCESS;
}
DEFUN(cfg_limit_crit, cfg_thresh_crit_cmd,
"threshold critical <0-200>",
"Threshold to reach\n" "Severe level\n" "Range\n")
{
struct lc15bts_temp_limit *limit = vty->index;
limit->thresh_crit = atoi(argv[0]);
return CMD_SUCCESS;
}
#define CFG_ACTION(name, expl, switch_to, variable) \
DEFUN(cfg_action_##name, cfg_action_##name##_cmd, \
"actions " #name, \
"Configure Actions\n" expl) \
{ \
vty->node = switch_to; \
vty->index = &s_mgr->temp.variable; \
return CMD_SUCCESS; \
}
CFG_ACTION(normal, "Normal Actions\n", ACT_NORM_NODE, action_norm)
CFG_ACTION(warn, "Warning Actions\n", ACT_WARN_NODE, action_warn)
CFG_ACTION(critical, "Critical Actions\n", ACT_CRIT_NODE, action_crit)
#undef CFG_ACTION
DEFUN(cfg_action_pa1_on, cfg_action_pa1_on_cmd,
"pa1-on",
"Switch the Power Amplifier #1 on\n")
{
int *action = vty->index;
*action |= TEMP_ACT_NORM_PA1_ON;
return CMD_SUCCESS;
}
DEFUN(cfg_no_action_pa1_on, cfg_no_action_pa1_on_cmd,
"no pa1-on",
NO_STR "Switch the Power Amplifieri #1 on\n")
{
int *action = vty->index;
*action &= ~TEMP_ACT_NORM_PA1_ON;
return CMD_SUCCESS;
}
DEFUN(cfg_action_pa2_on, cfg_action_pa2_on_cmd,
"pa2-on",
"Switch the Power Amplifier #2 on\n")
{
int *action = vty->index;
*action |= TEMP_ACT_NORM_PA2_ON;
return CMD_SUCCESS;
}
DEFUN(cfg_no_action_pa2_on, cfg_no_action_pa2_on_cmd,
"no pa2-on",
NO_STR "Switch the Power Amplifieri #2 on\n")
{
int *action = vty->index;
*action &= ~TEMP_ACT_NORM_PA2_ON;
return CMD_SUCCESS;
}
DEFUN(cfg_action_bts_srv_on, cfg_action_bts_srv_on_cmd,
"bts-service-on",
"Start the systemd lc15bts.service\n")
{
int *action = vty->index;
*action |= TEMP_ACT_NORM_BTS_SRV_ON;
return CMD_SUCCESS;
}
DEFUN(cfg_no_action_bts_srv_on, cfg_no_action_bts_srv_on_cmd,
"no bts-service-on",
NO_STR "Start the systemd lc15bts.service\n")
{
int *action = vty->index;
*action &= ~TEMP_ACT_NORM_BTS_SRV_ON;
return CMD_SUCCESS;
}
DEFUN(cfg_action_pa1_off, cfg_action_pa1_off_cmd,
"pa1-off",
"Switch the Power Amplifier #1 off\n")
{
int *action = vty->index;
*action |= TEMP_ACT_PA1_OFF;
return CMD_SUCCESS;
}
DEFUN(cfg_no_action_pa1_off, cfg_no_action_pa1_off_cmd,
"no pa1-off",
NO_STR "Do not switch off the Power Amplifier #1\n")
{
int *action = vty->index;
*action &= ~TEMP_ACT_PA1_OFF;
return CMD_SUCCESS;
}
DEFUN(cfg_action_pa2_off, cfg_action_pa2_off_cmd,
"pa2-off",
"Switch the Power Amplifier #2 off\n")
{
int *action = vty->index;
*action |= TEMP_ACT_PA2_OFF;
return CMD_SUCCESS;
}
DEFUN(cfg_no_action_pa2_off, cfg_no_action_pa2_off_cmd,
"no pa2-off",
NO_STR "Do not switch off the Power Amplifier #2\n")
{
int *action = vty->index;
*action &= ~TEMP_ACT_PA2_OFF;
return CMD_SUCCESS;
}
DEFUN(cfg_action_bts_srv_off, cfg_action_bts_srv_off_cmd,
"bts-service-off",
"Stop the systemd lc15bts.service\n")
{
int *action = vty->index;
*action |= TEMP_ACT_BTS_SRV_OFF;
return CMD_SUCCESS;
}
DEFUN(cfg_no_action_bts_srv_off, cfg_no_action_bts_srv_off_cmd,
"no bts-service-off",
NO_STR "Stop the systemd lc15bts.service\n")
{
int *action = vty->index;
*action &= ~TEMP_ACT_BTS_SRV_OFF;
return CMD_SUCCESS;
}
DEFUN(show_mgr, show_mgr_cmd, "show manager",
SHOW_STR "Display information about the manager")
{
vty_out(vty, "Temperature control state: %s%s",
lc15bts_mgr_temp_get_state(s_mgr->temp.state), VTY_NEWLINE);
vty_out(vty, "Current Temperatures%s", VTY_NEWLINE);
vty_out(vty, " Main Supply : %f Celcius%s",
lc15bts_temp_get(LC15BTS_TEMP_SUPPLY,
LC15BTS_TEMP_INPUT) / 1000.0f,
VTY_NEWLINE);
vty_out(vty, " SoC : %f Celcius%s",
lc15bts_temp_get(LC15BTS_TEMP_SOC,
LC15BTS_TEMP_INPUT) / 1000.0f,
VTY_NEWLINE);
vty_out(vty, " FPGA : %f Celcius%s",
lc15bts_temp_get(LC15BTS_TEMP_FPGA,
LC15BTS_TEMP_INPUT) / 1000.0f,
VTY_NEWLINE);
vty_out(vty, " Memory (DDR): %f Celcius%s",
lc15bts_temp_get(LC15BTS_TEMP_MEMORY,
LC15BTS_TEMP_INPUT) / 1000.0f,
VTY_NEWLINE);
vty_out(vty, " TX 1 : %f Celcius%s",
lc15bts_temp_get(LC15BTS_TEMP_TX1,
LC15BTS_TEMP_INPUT) / 1000.0f,
VTY_NEWLINE);
vty_out(vty, " TX 2 : %f Celcius%s",
lc15bts_temp_get(LC15BTS_TEMP_TX2,
LC15BTS_TEMP_INPUT) / 1000.0f,
VTY_NEWLINE);
vty_out(vty, " Power Amp #1: %f Celcius%s",
lc15bts_temp_get(LC15BTS_TEMP_PA1,
LC15BTS_TEMP_INPUT) / 1000.0f,
VTY_NEWLINE);
vty_out(vty, " Power Amp #2: %f Celcius%s",
lc15bts_temp_get(LC15BTS_TEMP_PA2,
LC15BTS_TEMP_INPUT) / 1000.0f,
VTY_NEWLINE);
vty_out(vty, "Power Status%s", VTY_NEWLINE);
vty_out(vty, " Main Supply : (ON) [%6.2f Vdc, %4.2f A, %6.2f W]%s",
lc15bts_power_sensor_get(LC15BTS_POWER_SUPPLY,
LC15BTS_POWER_VOLTAGE)/1000.0f,
lc15bts_power_sensor_get(LC15BTS_POWER_SUPPLY,
LC15BTS_POWER_CURRENT)/1000.0f,
lc15bts_power_sensor_get(LC15BTS_POWER_SUPPLY,
LC15BTS_POWER_POWER)/1000000.0f,
VTY_NEWLINE);
vty_out(vty, " Power Amp #1: %s [%6.2f Vdc, %4.2f A, %6.2f W]%s",
lc15bts_power_get(LC15BTS_POWER_PA1) ? "ON " : "OFF",
lc15bts_power_sensor_get(LC15BTS_POWER_PA1,
LC15BTS_POWER_VOLTAGE)/1000.0f,
lc15bts_power_sensor_get(LC15BTS_POWER_PA1,
LC15BTS_POWER_CURRENT)/1000.0f,
lc15bts_power_sensor_get(LC15BTS_POWER_PA1,
LC15BTS_POWER_POWER)/1000000.0f,
VTY_NEWLINE);
vty_out(vty, " Power Amp #2: %s [%6.2f Vdc, %4.2f A, %6.2f W]%s",
lc15bts_power_get(LC15BTS_POWER_PA2) ? "ON " : "OFF",
lc15bts_power_sensor_get(LC15BTS_POWER_PA2,
LC15BTS_POWER_VOLTAGE)/1000.0f,
lc15bts_power_sensor_get(LC15BTS_POWER_PA2,
LC15BTS_POWER_CURRENT)/1000.0f,
lc15bts_power_sensor_get(LC15BTS_POWER_PA2,
LC15BTS_POWER_POWER)/1000000.0f,
VTY_NEWLINE);
return CMD_SUCCESS;
}
DEFUN(calibrate_clock, calibrate_clock_cmd,
"calibrate clock",
"Calibration commands\n"
"Calibrate clock against GPS PPS\n")
{
if (lc15bts_mgr_calib_run(s_mgr) < 0) {
vty_out(vty, "%%Failed to start calibration.%s", VTY_NEWLINE);
return CMD_WARNING;
}
return CMD_SUCCESS;
}
static void register_limit(int limit)
{
install_element(limit, &cfg_thresh_warning_cmd);
install_element(limit, &cfg_thresh_crit_cmd);
}
static void register_normal_action(int act)
{
install_element(act, &cfg_action_pa1_on_cmd);
install_element(act, &cfg_no_action_pa1_on_cmd);
install_element(act, &cfg_action_pa2_on_cmd);
install_element(act, &cfg_no_action_pa2_on_cmd);
install_element(act, &cfg_action_bts_srv_on_cmd);
install_element(act, &cfg_no_action_bts_srv_on_cmd);
}
static void register_action(int act)
{
install_element(act, &cfg_action_pa1_off_cmd);
install_element(act, &cfg_no_action_pa1_off_cmd);
install_element(act, &cfg_action_pa2_off_cmd);
install_element(act, &cfg_no_action_pa2_off_cmd);
install_element(act, &cfg_action_bts_srv_off_cmd);
install_element(act, &cfg_no_action_bts_srv_off_cmd);
}
int lc15bts_mgr_vty_init(void)
{
vty_init(&vty_info);
install_element_ve(&show_mgr_cmd);
install_element(ENABLE_NODE, &calibrate_clock_cmd);
install_node(&mgr_node, config_write_mgr);
install_element(CONFIG_NODE, &cfg_mgr_cmd);
vty_install_default(MGR_NODE);
/* install the limit nodes */
install_node(&limit_supply_node, config_write_dummy);
install_element(MGR_NODE, &cfg_limit_supply_cmd);
register_limit(LIMIT_SUPPLY_NODE);
vty_install_default(LIMIT_SUPPLY_NODE);
install_node(&limit_soc_node, config_write_dummy);
install_element(MGR_NODE, &cfg_limit_soc_cmd);
register_limit(LIMIT_SOC_NODE);
vty_install_default(LIMIT_SOC_NODE);
install_node(&limit_fpga_node, config_write_dummy);
install_element(MGR_NODE, &cfg_limit_fpga_cmd);
register_limit(LIMIT_FPGA_NODE);
vty_install_default(LIMIT_FPGA_NODE);
install_node(&limit_memory_node, config_write_dummy);
install_element(MGR_NODE, &cfg_limit_memory_cmd);
register_limit(LIMIT_MEMORY_NODE);
vty_install_default(LIMIT_MEMORY_NODE);
install_node(&limit_tx1_node, config_write_dummy);
install_element(MGR_NODE, &cfg_limit_tx1_cmd);
register_limit(LIMIT_TX1_NODE);
vty_install_default(LIMIT_TX1_NODE);
install_node(&limit_tx2_node, config_write_dummy);
install_element(MGR_NODE, &cfg_limit_tx2_cmd);
register_limit(LIMIT_TX2_NODE);
vty_install_default(LIMIT_TX2_NODE);
install_node(&limit_pa1_node, config_write_dummy);
install_element(MGR_NODE, &cfg_limit_pa1_cmd);
register_limit(LIMIT_PA1_NODE);
vty_install_default(LIMIT_PA1_NODE);
install_node(&limit_pa2_node, config_write_dummy);
install_element(MGR_NODE, &cfg_limit_pa2_cmd);
register_limit(LIMIT_PA2_NODE);
vty_install_default(LIMIT_PA2_NODE);
/* install the normal node */
install_node(&act_norm_node, config_write_dummy);
install_element(MGR_NODE, &cfg_action_normal_cmd);
register_normal_action(ACT_NORM_NODE);
/* install the warning and critical node */
install_node(&act_warn_node, config_write_dummy);
install_element(MGR_NODE, &cfg_action_warn_cmd);
register_action(ACT_WARN_NODE);
vty_install_default(ACT_WARN_NODE);
install_node(&act_crit_node, config_write_dummy);
install_element(MGR_NODE, &cfg_action_critical_cmd);
register_action(ACT_CRIT_NODE);
vty_install_default(ACT_CRIT_NODE);
return 0;
}
int lc15bts_mgr_parse_config(struct lc15bts_mgr_instance *manager)
{
int rc;
s_mgr = manager;
rc = vty_read_config_file(s_mgr->config_file, NULL);
if (rc < 0) {
fprintf(stderr, "Failed to parse the config file: '%s'\n",
s_mgr->config_file);
return rc;
}
return 0;
}

View File

@ -0,0 +1,249 @@
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* Based on sysmoBTS:
* sysmobts_misc.c
* (C) 2012 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>
#include <fcntl.h>
#include <limits.h>
#include <time.h>
#include <sys/signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/application.h>
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/logging.h>
#include "btsconfig.h"
#include "lc15bts_misc.h"
#include "lc15bts_par.h"
#include "lc15bts_mgr.h"
#include "lc15bts_temp.h"
/*********************************************************************
* Temperature handling
*********************************************************************/
static const struct {
const char *name;
int has_max;
enum lc15bts_temp_sensor sensor;
enum lc15bts_par ee_par;
} temp_data[] = {
{
.name = "supply",
.has_max = 1,
.sensor = LC15BTS_TEMP_SUPPLY,
.ee_par = LC15BTS_PAR_TEMP_SUPPLY_MAX,
}, {
.name = "soc",
.has_max = 0,
.sensor = LC15BTS_TEMP_SOC,
.ee_par = LC15BTS_PAR_TEMP_SOC_MAX,
}, {
.name = "fpga",
.has_max = 0,
.sensor = LC15BTS_TEMP_FPGA,
.ee_par = LC15BTS_PAR_TEMP_FPGA_MAX,
}, {
.name = "memory",
.has_max = 1,
.sensor = LC15BTS_TEMP_MEMORY,
.ee_par = LC15BTS_PAR_TEMP_MEMORY_MAX,
}, {
.name = "tx1",
.has_max = 0,
.sensor = LC15BTS_TEMP_TX1,
.ee_par = LC15BTS_PAR_TEMP_TX1_MAX,
}, {
.name = "tx2",
.has_max = 0,
.sensor = LC15BTS_TEMP_TX2,
.ee_par = LC15BTS_PAR_TEMP_TX2_MAX,
}, {
.name = "pa1",
.has_max = 1,
.sensor = LC15BTS_TEMP_PA1,
.ee_par = LC15BTS_PAR_TEMP_PA1_MAX,
}, {
.name = "pa2",
.has_max = 1,
.sensor = LC15BTS_TEMP_PA2,
.ee_par = LC15BTS_PAR_TEMP_PA2_MAX,
}
};
void lc15bts_check_temp(int no_rom_write)
{
int temp_old[ARRAY_SIZE(temp_data)];
int temp_hi[ARRAY_SIZE(temp_data)];
int temp_cur[ARRAY_SIZE(temp_data)];
int i, rc;
for (i = 0; i < ARRAY_SIZE(temp_data); i++) {
int ret;
rc = lc15bts_par_get_int(temp_data[i].ee_par, &ret);
temp_old[i] = ret * 1000;
if (temp_data[i].has_max) {
temp_hi[i] = lc15bts_temp_get(temp_data[i].sensor,
LC15BTS_TEMP_HIGHEST);
temp_cur[i] = lc15bts_temp_get(temp_data[i].sensor,
LC15BTS_TEMP_INPUT);
if ((temp_cur[i] < 0 && temp_cur[i] > -1000) ||
(temp_hi[i] < 0 && temp_hi[i] > -1000)) {
LOGP(DTEMP, LOGL_ERROR, "Error reading temperature\n");
continue;
}
}
else {
temp_cur[i] = lc15bts_temp_get(temp_data[i].sensor,
LC15BTS_TEMP_INPUT);
if (temp_cur[i] < 0 && temp_cur[i] > -1000) {
LOGP(DTEMP, LOGL_ERROR, "Error reading temperature\n");
continue;
}
temp_hi[i] = temp_cur[i];
}
LOGP(DTEMP, LOGL_DEBUG, "Current %s temperature: %d.%d C\n",
temp_data[i].name, temp_cur[i]/1000, temp_cur[i]%1000);
if (temp_hi[i] > temp_old[i]) {
LOGP(DTEMP, LOGL_NOTICE, "New maximum %s "
"temperature: %d.%d C\n", temp_data[i].name,
temp_hi[i]/1000, temp_hi[i]%1000);
if (!no_rom_write) {
rc = lc15bts_par_set_int(temp_data[i].ee_par,
temp_hi[i]/1000);
if (rc < 0)
LOGP(DTEMP, LOGL_ERROR, "error writing new %s "
"max temp %d (%s)\n", temp_data[i].name,
rc, strerror(errno));
}
}
}
}
/*********************************************************************
* Hours handling
*********************************************************************/
static time_t last_update;
int lc15bts_update_hours(int no_rom_write)
{
time_t now = time(NULL);
int rc, op_hrs;
/* first time after start of manager program */
if (last_update == 0) {
last_update = now;
rc = lc15bts_par_get_int(LC15BTS_PAR_HOURS, &op_hrs);
if (rc < 0) {
LOGP(DTEMP, LOGL_ERROR, "Unable to read "
"operational hours: %d (%s)\n", rc,
strerror(errno));
return rc;
}
LOGP(DTEMP, LOGL_INFO, "Total hours of Operation: %u\n",
op_hrs);
return 0;
}
if (now >= last_update + 3600) {
rc = lc15bts_par_get_int(LC15BTS_PAR_HOURS, &op_hrs);
if (rc < 0) {
LOGP(DTEMP, LOGL_ERROR, "Unable to read "
"operational hours: %d (%s)\n", rc,
strerror(errno));
return rc;
}
/* number of hours to increase */
op_hrs += (now-last_update)/3600;
LOGP(DTEMP, LOGL_INFO, "Total hours of Operation: %u\n",
op_hrs);
if (!no_rom_write) {
rc = lc15bts_par_set_int(LC15BTS_PAR_HOURS, op_hrs);
if (rc < 0)
return rc;
}
last_update = now;
}
return 0;
}
/*********************************************************************
* Firmware reloading
*********************************************************************/
static const char *fw_sysfs[_NUM_FW] = {
[LC15BTS_FW_DSP0] = "/sys/kernel/debug/remoteproc/remoteproc0/recovery",
[LC15BTS_FW_DSP1] = "/sys/kernel/debug/remoteproc/remoteproc0/recovery",
};
int lc15bts_firmware_reload(enum lc15bts_firmware_type type)
{
int fd;
int rc;
switch (type) {
case LC15BTS_FW_DSP0:
case LC15BTS_FW_DSP1:
fd = open(fw_sysfs[type], O_WRONLY);
if (fd < 0) {
LOGP(DFW, LOGL_ERROR, "unable ot open firmware device %s: %s\n",
fw_sysfs[type], strerror(errno));
close(fd);
return fd;
}
rc = write(fd, "restart", 8);
if (rc < 8) {
LOGP(DFW, LOGL_ERROR, "short write during "
"fw write to %s\n", fw_sysfs[type]);
close(fd);
return -EIO;
}
close(fd);
default:
return -EINVAL;
}
return 0;
}

View File

@ -0,0 +1,16 @@
#ifndef _LC15BTS_MISC_H
#define _LC15BTS_MISC_H
#include <stdint.h>
void lc15bts_check_temp(int no_rom_write);
int lc15bts_update_hours(int no_rom_write);
enum lc15bts_firmware_type {
LC15BTS_FW_DSP0,
LC15BTS_FW_DSP1,
_NUM_FW
};
#endif

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,181 @@
/* lc15bts - access to hardware related parameters */
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* Based on sysmoBTS:
* sysmobts_par.c
* (C) 2012 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <osmocom/core/utils.h>
#include "lc15bts_par.h"
#define FACTORY_ROM_PATH "/mnt/rom/factory"
#define USER_ROM_PATH "/mnt/rom/user"
const struct value_string lc15bts_par_names[_NUM_LC15BTS_PAR+1] = {
{ LC15BTS_PAR_TEMP_SUPPLY_MAX, "temp-supply-max" },
{ LC15BTS_PAR_TEMP_SOC_MAX, "temp-soc-max" },
{ LC15BTS_PAR_TEMP_FPGA_MAX, "temp-fpga-max" },
{ LC15BTS_PAR_TEMP_MEMORY_MAX, "temp-memory-max" },
{ LC15BTS_PAR_TEMP_TX1_MAX, "temp-tx1-max" },
{ LC15BTS_PAR_TEMP_TX2_MAX, "temp-tx2-max" },
{ LC15BTS_PAR_TEMP_PA1_MAX, "temp-pa1-max" },
{ LC15BTS_PAR_TEMP_PA2_MAX, "temp-pa2-max" },
{ LC15BTS_PAR_SERNR, "serial-nr" },
{ LC15BTS_PAR_HOURS, "hours-running" },
{ LC15BTS_PAR_BOOTS, "boot-count" },
{ LC15BTS_PAR_KEY, "key" },
{ 0, NULL }
};
int lc15bts_par_is_int(enum lc15bts_par par)
{
switch (par) {
case LC15BTS_PAR_TEMP_SUPPLY_MAX:
case LC15BTS_PAR_TEMP_SOC_MAX:
case LC15BTS_PAR_TEMP_FPGA_MAX:
case LC15BTS_PAR_TEMP_MEMORY_MAX:
case LC15BTS_PAR_TEMP_TX1_MAX:
case LC15BTS_PAR_TEMP_TX2_MAX:
case LC15BTS_PAR_TEMP_PA1_MAX:
case LC15BTS_PAR_TEMP_PA2_MAX:
case LC15BTS_PAR_SERNR:
case LC15BTS_PAR_HOURS:
case LC15BTS_PAR_BOOTS:
return 1;
default:
return 0;
}
}
int lc15bts_par_get_int(enum lc15bts_par par, int *ret)
{
char fpath[PATH_MAX];
FILE *fp;
int rc;
if (par >= _NUM_LC15BTS_PAR)
return -ENODEV;
snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(lc15bts_par_names, par));
fpath[sizeof(fpath)-1] = '\0';
fp = fopen(fpath, "r");
if (fp == NULL) {
return -errno;
}
rc = fscanf(fp, "%d", ret);
if (rc != 1) {
fclose(fp);
return -EIO;
}
fclose(fp);
return 0;
}
int lc15bts_par_set_int(enum lc15bts_par par, int val)
{
char fpath[PATH_MAX];
FILE *fp;
int rc;
if (par >= _NUM_LC15BTS_PAR)
return -ENODEV;
snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(lc15bts_par_names, par));
fpath[sizeof(fpath)-1] = '\0';
fp = fopen(fpath, "w");
if (fp == NULL) {
return -errno;
}
rc = fprintf(fp, "%d", val);
if (rc < 0) {
fclose(fp);
return -EIO;
}
fclose(fp);
return 0;
}
int lc15bts_par_get_buf(enum lc15bts_par par, uint8_t *buf,
unsigned int size)
{
char fpath[PATH_MAX];
FILE *fp;
int rc;
if (par >= _NUM_LC15BTS_PAR)
return -ENODEV;
snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(lc15bts_par_names, par));
fpath[sizeof(fpath)-1] = '\0';
fp = fopen(fpath, "rb");
if (fp == NULL) {
return -errno;
}
rc = fread(buf, 1, size, fp);
fclose(fp);
return rc;
}
int lc15bts_par_set_buf(enum lc15bts_par par, const uint8_t *buf,
unsigned int size)
{
char fpath[PATH_MAX];
FILE *fp;
int rc;
if (par >= _NUM_LC15BTS_PAR)
return -ENODEV;
snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(lc15bts_par_names, par));
fpath[sizeof(fpath)-1] = '\0';
fp = fopen(fpath, "wb");
if (fp == NULL) {
return -errno;
}
rc = fwrite(buf, 1, size, fp);
fclose(fp);
return rc;
}

View File

@ -0,0 +1,33 @@
#ifndef _LC15BTS_PAR_H
#define _LC15BTS_PAR_H
#include <osmocom/core/utils.h>
enum lc15bts_par {
LC15BTS_PAR_TEMP_SUPPLY_MAX,
LC15BTS_PAR_TEMP_SOC_MAX,
LC15BTS_PAR_TEMP_FPGA_MAX,
LC15BTS_PAR_TEMP_MEMORY_MAX,
LC15BTS_PAR_TEMP_TX1_MAX,
LC15BTS_PAR_TEMP_TX2_MAX,
LC15BTS_PAR_TEMP_PA1_MAX,
LC15BTS_PAR_TEMP_PA2_MAX,
LC15BTS_PAR_SERNR,
LC15BTS_PAR_HOURS,
LC15BTS_PAR_BOOTS,
LC15BTS_PAR_KEY,
_NUM_LC15BTS_PAR
};
extern const struct value_string lc15bts_par_names[_NUM_LC15BTS_PAR+1];
int lc15bts_par_get_int(enum lc15bts_par par, int *ret);
int lc15bts_par_set_int(enum lc15bts_par par, int val);
int lc15bts_par_get_buf(enum lc15bts_par par, uint8_t *buf,
unsigned int size);
int lc15bts_par_set_buf(enum lc15bts_par par, const uint8_t *buf,
unsigned int size);
int lc15bts_par_is_int(enum lc15bts_par par);
#endif

View File

@ -0,0 +1,167 @@
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include "lc15bts_power.h"
#define LC15BTS_PA_VOLTAGE 24000000
#define PA_SUPPLY_MIN_SYSFS "/sys/devices/0.pa-supply/min_microvolts"
#define PA_SUPPLY_MAX_SYSFS "/sys/devices/0.pa-supply/max_microvolts"
static const char *power_enable_devs[_NUM_POWER_SOURCES] = {
[LC15BTS_POWER_PA1] = "/sys/devices/0.pa1/state",
[LC15BTS_POWER_PA2] = "/sys/devices/0.pa2/state",
};
static const char *power_sensor_devs[_NUM_POWER_SOURCES] = {
[LC15BTS_POWER_SUPPLY] = "/sys/bus/i2c/devices/2-0040/hwmon/hwmon6/",
[LC15BTS_POWER_PA1] = "/sys/bus/i2c/devices/2-0044/hwmon/hwmon7/",
[LC15BTS_POWER_PA2] = "/sys/bus/i2c/devices/2-0045/hwmon/hwmon8/",
};
static const char *power_sensor_type_str[_NUM_POWER_TYPES] = {
[LC15BTS_POWER_POWER] = "power1_input",
[LC15BTS_POWER_VOLTAGE] = "in1_input",
[LC15BTS_POWER_CURRENT] = "curr1_input",
};
int lc15bts_power_sensor_get(
enum lc15bts_power_source source,
enum lc15bts_power_type type)
{
char buf[PATH_MAX];
char pwrstr[10];
int fd, rc;
if (source >= _NUM_POWER_SOURCES)
return -EINVAL;
if (type >= _NUM_POWER_TYPES)
return -EINVAL;
snprintf(buf, sizeof(buf)-1, "%s%s", power_sensor_devs[source], power_sensor_type_str[type]);
buf[sizeof(buf)-1] = '\0';
fd = open(buf, O_RDONLY);
if (fd < 0)
return fd;
rc = read(fd, pwrstr, sizeof(pwrstr));
pwrstr[sizeof(pwrstr)-1] = '\0';
if (rc < 0) {
close(fd);
return rc;
}
if (rc == 0) {
close(fd);
return -EIO;
}
close(fd);
return atoi(pwrstr);
}
int lc15bts_power_set(
enum lc15bts_power_source source,
int en)
{
int fd;
int rc;
if ((source != LC15BTS_POWER_PA1)
&& (source != LC15BTS_POWER_PA2) ) {
return -EINVAL;
}
fd = open(PA_SUPPLY_MAX_SYSFS, O_WRONLY);
if (fd < 0) {
return fd;
}
rc = write(fd, "32000000", 9);
close( fd );
if (rc != 9) {
return -1;
}
fd = open(PA_SUPPLY_MIN_SYSFS, O_WRONLY);
if (fd < 0) {
return fd;
}
/* TODO NTQ: Make the voltage configurable */
rc = write(fd, "24000000", 9);
close( fd );
if (rc != 9) {
return -1;
}
fd = open(power_enable_devs[source], O_WRONLY);
if (fd < 0) {
return fd;
}
rc = write(fd, en?"1":"0", 2);
close( fd );
if (rc != 2) {
return -1;
}
if (en) usleep(50*1000);
return 0;
}
int lc15bts_power_get(
enum lc15bts_power_source source)
{
int fd;
int rc;
char enstr[10];
fd = open(power_enable_devs[source], O_RDONLY);
if (fd < 0) {
return fd;
}
rc = read(fd, enstr, sizeof(enstr));
enstr[sizeof(enstr)-1] = '\0';
close(fd);
if (rc < 0) {
return rc;
}
if (rc == 0) {
return -EIO;
}
return atoi(enstr);
}

View File

@ -0,0 +1,29 @@
#ifndef _LC15BTS_POWER_H
#define _LC15BTS_POWER_H
enum lc15bts_power_source {
LC15BTS_POWER_SUPPLY,
LC15BTS_POWER_PA1,
LC15BTS_POWER_PA2,
_NUM_POWER_SOURCES
};
enum lc15bts_power_type {
LC15BTS_POWER_POWER,
LC15BTS_POWER_VOLTAGE,
LC15BTS_POWER_CURRENT,
_NUM_POWER_TYPES
};
int lc15bts_power_sensor_get(
enum lc15bts_power_source source,
enum lc15bts_power_type type);
int lc15bts_power_set(
enum lc15bts_power_source source,
int en);
int lc15bts_power_get(
enum lc15bts_power_source source);
#endif

View File

@ -0,0 +1,117 @@
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <osmocom/core/utils.h>
#include "lc15bts_temp.h"
static const char *temp_devs[_NUM_TEMP_SENSORS] = {
[LC15BTS_TEMP_SUPPLY] = "/sys/bus/i2c/devices/2-004d/hwmon/hwmon5/temp1_",
[LC15BTS_TEMP_SOC] = "/sys/class/hwmon/hwmon1/temp1_",
[LC15BTS_TEMP_FPGA] = "/sys/devices/0.iio_hwmon/temp1_",
[LC15BTS_TEMP_MEMORY] = "/sys/bus/i2c/devices/2-004c/hwmon/hwmon4/temp1_",
[LC15BTS_TEMP_TX1] = "/sys/devices/0.ncp15xh103_tx1/temp1_",
[LC15BTS_TEMP_TX2] = "/sys/devices/0.ncp15xh103_tx2/temp1_",
[LC15BTS_TEMP_PA1] = "/sys/bus/i2c/devices/2-004d/hwmon/hwmon5/temp2_",
[LC15BTS_TEMP_PA2] = "/sys/bus/i2c/devices/2-004c/hwmon/hwmon4/temp2_",
};
static const int temp_has_fault[_NUM_TEMP_SENSORS] = {
[LC15BTS_TEMP_PA1] = 1,
[LC15BTS_TEMP_PA2] = 1,
};
static const char *temp_type_str[_NUM_TEMP_TYPES] = {
[LC15BTS_TEMP_INPUT] = "input",
[LC15BTS_TEMP_LOWEST] = "lowest",
[LC15BTS_TEMP_HIGHEST] = "highest",
[LC15BTS_TEMP_FAULT] = "fault",
};
int lc15bts_temp_get(enum lc15bts_temp_sensor sensor,
enum lc15bts_temp_type type)
{
char buf[PATH_MAX];
char tempstr[8];
char faultstr[8];
int fd, rc;
if (sensor < 0 || sensor >= _NUM_TEMP_SENSORS)
return -EINVAL;
if (type >= ARRAY_SIZE(temp_type_str))
return -EINVAL;
snprintf(buf, sizeof(buf)-1, "%s%s", temp_devs[sensor], temp_type_str[type]);
buf[sizeof(buf)-1] = '\0';
fd = open(buf, O_RDONLY);
if (fd < 0)
return fd;
rc = read(fd, tempstr, sizeof(tempstr));
tempstr[sizeof(tempstr)-1] = '\0';
if (rc < 0) {
close(fd);
return rc;
}
if (rc == 0) {
close(fd);
return -EIO;
}
close(fd);
// Check fault
if (type == LC15BTS_TEMP_FAULT || !temp_has_fault[sensor])
return atoi(tempstr);
snprintf(buf, sizeof(buf)-1, "%s%s", temp_devs[sensor], temp_type_str[LC15BTS_TEMP_FAULT]);
buf[sizeof(buf)-1] = '\0';
fd = open(buf, O_RDONLY);
if (fd < 0)
return fd;
rc = read(fd, faultstr, sizeof(faultstr));
tempstr[sizeof(faultstr)-1] = '\0';
if (rc < 0) {
close(fd);
return rc;
}
if (rc == 0) {
close(fd);
return -EIO;
}
close(fd);
if (atoi(faultstr))
return -EIO;
return atoi(tempstr);
}

View File

@ -0,0 +1,27 @@
#ifndef _LC15BTS_TEMP_H
#define _LC15BTS_TEMP_H
enum lc15bts_temp_sensor {
LC15BTS_TEMP_SUPPLY,
LC15BTS_TEMP_SOC,
LC15BTS_TEMP_FPGA,
LC15BTS_TEMP_MEMORY,
LC15BTS_TEMP_TX1,
LC15BTS_TEMP_TX2,
LC15BTS_TEMP_PA1,
LC15BTS_TEMP_PA2,
_NUM_TEMP_SENSORS
};
enum lc15bts_temp_type {
LC15BTS_TEMP_INPUT,
LC15BTS_TEMP_LOWEST,
LC15BTS_TEMP_HIGHEST,
LC15BTS_TEMP_FAULT,
_NUM_TEMP_TYPES
};
int lc15bts_temp_get(enum lc15bts_temp_sensor sensor,
enum lc15bts_temp_type type);
#endif

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -0,0 +1,13 @@
#pragma once
struct gsm_bts;
struct osmo_fd;
/**
* The default path lc15bts will listen for incoming
* registrations for OML routing and sending.
*/
#define OML_ROUTER_PATH "/var/run/lc15bts_oml_router"
int oml_router_init(struct gsm_bts *bts, const char *path, struct osmo_fd *accept, struct osmo_fd *read);

View File

@ -0,0 +1,534 @@
/* Traffic channel support for NuRAN Wireless Litecell 1.5 BTS L1 */
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
*
* Based on sysmoBTS:
* (C) 2011-2012 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/select.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/bits.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/trau/osmo_ortp.h>
#include <osmo-bts/logging.h>
#include <osmo-bts/bts.h>
#include <osmo-bts/gsm_data.h>
#include <osmo-bts/measurement.h>
#include <osmo-bts/amr.h>
#include <osmo-bts/l1sap.h>
#include <nrw/litecell15/litecell15.h>
#include <nrw/litecell15/gsml1prim.h>
#include <nrw/litecell15/gsml1const.h>
#include <nrw/litecell15/gsml1types.h>
#include "lc15bts.h"
#include "l1_if.h"
/* input octet-aligned, output not octet-aligned */
void osmo_nibble_shift_right(uint8_t *out, const uint8_t *in,
unsigned int num_nibbles)
{
unsigned int i;
unsigned int num_whole_bytes = num_nibbles / 2;
/* first byte: upper nibble empty, lower nibble from src */
out[0] = (in[0] >> 4);
/* bytes 1.. */
for (i = 1; i < num_whole_bytes; i++)
out[i] = ((in[i-1] & 0xF) << 4) | (in[i] >> 4);
/* shift the last nibble, in case there's an odd count */
i = num_whole_bytes;
if (num_nibbles & 1)
out[i] = ((in[i-1] & 0xF) << 4) | (in[i] >> 4);
else
out[i] = (in[i-1] & 0xF) << 4;
}
/* input unaligned, output octet-aligned */
void osmo_nibble_shift_left_unal(uint8_t *out, const uint8_t *in,
unsigned int num_nibbles)
{
unsigned int i;
unsigned int num_whole_bytes = num_nibbles / 2;
for (i = 0; i < num_whole_bytes; i++)
out[i] = ((in[i] & 0xF) << 4) | (in[i+1] >> 4);
/* shift the last nibble, in case there's an odd count */
i = num_whole_bytes;
if (num_nibbles & 1)
out[i] = (in[i] & 0xF) << 4;
}
#define GSM_FR_BITS 260
#define GSM_EFR_BITS 244
#define GSM_FR_BYTES 33 /* TS 101318 Chapter 5.1: 260 bits + 4bit sig */
#define GSM_HR_BYTES 14 /* TS 101318 Chapter 5.2: 112 bits, no sig */
#define GSM_EFR_BYTES 31 /* TS 101318 Chapter 5.3: 244 bits + 4bit sig */
static struct msgb *l1_to_rtppayload_fr(uint8_t *l1_payload, uint8_t payload_len)
{
struct msgb *msg;
uint8_t *cur;
msg = msgb_alloc_headroom(1024, 128, "L1C-to-RTP");
if (!msg)
return NULL;
/* new L1 can deliver bits like we need them */
cur = msgb_put(msg, GSM_FR_BYTES);
memcpy(cur, l1_payload, GSM_FR_BYTES);
return msg;
}
/*! \brief convert GSM-FR from RTP payload to L1 format
* \param[out] l1_payload payload part of L1 buffer
* \param[in] rtp_payload pointer to RTP payload data
* \param[in] payload_len length of \a rtp_payload
* \returns number of \a l1_payload bytes filled
*/
static int rtppayload_to_l1_fr(uint8_t *l1_payload, const uint8_t *rtp_payload,
unsigned int payload_len)
{
/* new L1 can deliver bits like we need them */
memcpy(l1_payload, rtp_payload, GSM_FR_BYTES);
return GSM_FR_BYTES;
}
static struct msgb *l1_to_rtppayload_efr(uint8_t *l1_payload, uint8_t payload_len)
{
struct msgb *msg;
uint8_t *cur;
msg = msgb_alloc_headroom(1024, 128, "L1C-to-RTP");
if (!msg)
return NULL;
/* new L1 can deliver bits like we need them */
cur = msgb_put(msg, GSM_EFR_BYTES);
memcpy(cur, l1_payload, GSM_EFR_BYTES);
return msg;
}
static int rtppayload_to_l1_efr(uint8_t *l1_payload, const uint8_t *rtp_payload,
unsigned int payload_len)
{
memcpy(l1_payload, rtp_payload, payload_len);
return payload_len;
}
static struct msgb *l1_to_rtppayload_hr(uint8_t *l1_payload, uint8_t payload_len)
{
struct msgb *msg;
uint8_t *cur;
msg = msgb_alloc_headroom(1024, 128, "L1C-to-RTP");
if (!msg)
return NULL;
if (payload_len != GSM_HR_BYTES) {
LOGP(DL1C, LOGL_ERROR, "L1 HR frame length %u != expected %u\n",
payload_len, GSM_HR_BYTES);
return NULL;
}
cur = msgb_put(msg, GSM_HR_BYTES);
memcpy(cur, l1_payload, GSM_HR_BYTES);
return msg;
}
/*! \brief convert GSM-FR from RTP payload to L1 format
* \param[out] l1_payload payload part of L1 buffer
* \param[in] rtp_payload pointer to RTP payload data
* \param[in] payload_len length of \a rtp_payload
* \returns number of \a l1_payload bytes filled
*/
static int rtppayload_to_l1_hr(uint8_t *l1_payload, const uint8_t *rtp_payload,
unsigned int payload_len)
{
if (payload_len != GSM_HR_BYTES) {
LOGP(DL1C, LOGL_ERROR, "RTP HR frame length %u != expected %u\n",
payload_len, GSM_HR_BYTES);
return 0;
}
memcpy(l1_payload, rtp_payload, GSM_HR_BYTES);
return GSM_HR_BYTES;
}
static struct msgb *l1_to_rtppayload_amr(uint8_t *l1_payload, uint8_t payload_len,
struct gsm_lchan *lchan)
{
struct msgb *msg;
uint8_t amr_if2_len = payload_len - 2;
uint8_t *cur;
msg = msgb_alloc_headroom(1024, 128, "L1C-to-RTP");
if (!msg)
return NULL;
cur = msgb_put(msg, amr_if2_len);
memcpy(cur, l1_payload+2, amr_if2_len);
/*
* Audiocode's MGW doesn't like receiving CMRs that are not
* the same as the previous one. This means we need to patch
* the content here.
*/
if ((cur[0] & 0xF0) == 0xF0)
cur[0]= lchan->tch.last_cmr << 4;
else
lchan->tch.last_cmr = cur[0] >> 4;
return msg;
}
enum amr_frame_type {
AMR_FT_SID_AMR = 8,
};
int get_amr_mode_idx(const struct amr_multirate_conf *amr_mrc, uint8_t cmi)
{
unsigned int i;
for (i = 0; i < amr_mrc->num_modes; i++) {
if (amr_mrc->bts_mode[i].mode == cmi)
return i;
}
return -EINVAL;
}
/*! \brief convert AMR from RTP payload to L1 format
* \param[out] l1_payload payload part of L1 buffer
* \param[in] rtp_payload pointer to RTP payload data
* \param[in] payload_len length of \a rtp_payload
* \returns number of \a l1_payload bytes filled
*/
static int rtppayload_to_l1_amr(uint8_t *l1_payload, const uint8_t *rtp_payload,
uint8_t payload_len,
struct gsm_lchan *lchan)
{
struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr;
uint8_t ft = (rtp_payload[1] >> 3) & 0xf;
uint8_t cmr = rtp_payload[0] >> 4;
uint8_t cmi, sti;
uint8_t *l1_cmi_idx = l1_payload;
uint8_t *l1_cmr_idx = l1_payload+1;
int rc;
memcpy(l1_payload+2, rtp_payload, payload_len);
/* CMI in downlink tells the L1 encoder which encoding function
* it will use, so we have to use the frame type */
switch (ft) {
case 0: case 1: case 2: case 3:
case 4: case 5: case 6: case 7:
cmi = ft;
LOGP(DRTP, LOGL_DEBUG, "SPEECH frame with CMI %u\n", cmi);
break;
case AMR_FT_SID_AMR:
/* extract the mode indiciation from last bits of
* 39 bit SID frame (Table 6 / 26.101) */
cmi = (rtp_payload[2+4] >> 1) & 0x7;
sti = rtp_payload[2+4] & 0x10;
LOGP(DRTP, LOGL_DEBUG, "SID %s frame with CMI %u\n",
sti ? "UPDATE" : "FIRST", cmi);
break;
default:
LOGP(DRTP, LOGL_ERROR, "unsupported AMR FT 0x%02x\n", ft);
return -EINVAL;
break;
}
rc = get_amr_mode_idx(amr_mrc, cmi);
if (rc < 0) {
LOGP(DRTP, LOGL_ERROR, "AMR CMI %u not part of AMR MR set\n",
cmi);
*l1_cmi_idx = 0;
} else
*l1_cmi_idx = rc;
/* Codec Mode Request is in upper 4 bits of RTP payload header,
* and we simply copy the CMR into the CMC */
if (cmr == 0xF) {
/* FIXME: we need some state about the last codec mode */
*l1_cmr_idx = 0;
} else {
rc = get_amr_mode_idx(amr_mrc, cmr);
if (rc < 0) {
/* FIXME: we need some state about the last codec mode */
LOGP(DRTP, LOGL_INFO, "RTP->L1: overriding CMR %u\n", cmr);
*l1_cmr_idx = 0;
} else
*l1_cmr_idx = rc;
}
#if 0
/* check for bad quality indication */
if (rtp_payload[1] & AMR_TOC_QBIT) {
/* obtain frame type from AMR FT */
l1_payload[2] = ft;
} else {
/* bad quality, we should indicate that... */
if (ft == AMR_FT_SID_AMR) {
/* FIXME: Should we do GsmL1_TchPlType_Amr_SidBad? */
l1_payload[2] = ft;
} else {
l1_payload[2] = ft;
}
}
#endif
if (ft == AMR_FT_SID_AMR) {
/* store the last SID frame in lchan context */
unsigned int copy_len;
copy_len = OSMO_MIN(payload_len+1,
ARRAY_SIZE(lchan->tch.last_sid.buf));
lchan->tch.last_sid.len = copy_len;
memcpy(lchan->tch.last_sid.buf, l1_payload, copy_len);
}
return payload_len+1;
}
#define RTP_MSGB_ALLOC_SIZE 512
/*! \brief function for incoming RTP via TCH.req
* \param rs RTP Socket
* \param[in] rtp_pl buffer containing RTP payload
* \param[in] rtp_pl_len length of \a rtp_pl
*
* This function prepares a msgb with a L1 PH-DATA.req primitive and
* queues it into lchan->dl_tch_queue.
*
* Note that the actual L1 primitive header is not fully initialized
* yet, as things like the frame number, etc. are unknown at the time we
* pre-fill the primtive.
*/
void l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len,
const uint8_t *rtp_pl, unsigned int rtp_pl_len)
{
uint8_t *payload_type;
uint8_t *l1_payload;
int rc;
DEBUGP(DRTP, "%s RTP IN: %s\n", gsm_lchan_name(lchan),
osmo_hexdump(rtp_pl, rtp_pl_len));
payload_type = &data[0];
l1_payload = &data[1];
switch (lchan->tch_mode) {
case GSM48_CMODE_SPEECH_V1:
if (lchan->type == GSM_LCHAN_TCH_F) {
*payload_type = GsmL1_TchPlType_Fr;
rc = rtppayload_to_l1_fr(l1_payload,
rtp_pl, rtp_pl_len);
} else{
*payload_type = GsmL1_TchPlType_Hr;
rc = rtppayload_to_l1_hr(l1_payload,
rtp_pl, rtp_pl_len);
}
break;
case GSM48_CMODE_SPEECH_EFR:
*payload_type = GsmL1_TchPlType_Efr;
rc = rtppayload_to_l1_efr(l1_payload, rtp_pl,
rtp_pl_len);
break;
case GSM48_CMODE_SPEECH_AMR:
*payload_type = GsmL1_TchPlType_Amr;
rc = rtppayload_to_l1_amr(l1_payload, rtp_pl,
rtp_pl_len, lchan);
break;
default:
/* we don't support CSD modes */
rc = -1;
break;
}
if (rc < 0) {
LOGP(DRTP, LOGL_ERROR, "%s unable to parse RTP payload\n",
gsm_lchan_name(lchan));
return;
}
*len = rc + 1;
DEBUGP(DRTP, "%s RTP->L1: %s\n", gsm_lchan_name(lchan),
osmo_hexdump(data, *len));
}
static int is_recv_only(uint8_t speech_mode)
{
return (speech_mode & 0xF0) == (1 << 4);
}
/*! \brief receive a traffic L1 primitive for a given lchan */
int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg)
{
GsmL1_Prim_t *l1p = msgb_l1prim(l1p_msg);
GsmL1_PhDataInd_t *data_ind = &l1p->u.phDataInd;
uint8_t payload_type = data_ind->msgUnitParam.u8Buffer[0];
uint8_t *payload = data_ind->msgUnitParam.u8Buffer + 1;
uint8_t payload_len;
struct msgb *rmsg = NULL;
struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)];
if (is_recv_only(lchan->abis_ip.speech_mode))
return -EAGAIN;
if (data_ind->msgUnitParam.u8Size < 1) {
LOGP(DL1C, LOGL_ERROR, "chan_nr %d Rx Payload size 0\n",
chan_nr);
return -EINVAL;
}
payload_len = data_ind->msgUnitParam.u8Size - 1;
switch (payload_type) {
case GsmL1_TchPlType_Fr:
case GsmL1_TchPlType_Efr:
if (lchan->type != GSM_LCHAN_TCH_F)
goto err_payload_match;
break;
case GsmL1_TchPlType_Hr:
if (lchan->type != GSM_LCHAN_TCH_H)
goto err_payload_match;
break;
case GsmL1_TchPlType_Amr:
if (lchan->type != GSM_LCHAN_TCH_H &&
lchan->type != GSM_LCHAN_TCH_F)
goto err_payload_match;
break;
default:
LOGP(DL1C, LOGL_NOTICE, "%s Rx Payload Type %s is unsupported\n",
gsm_lchan_name(lchan),
get_value_string(lc15bts_tch_pl_names, payload_type));
break;
}
switch (payload_type) {
case GsmL1_TchPlType_Fr:
rmsg = l1_to_rtppayload_fr(payload, payload_len);
break;
case GsmL1_TchPlType_Hr:
rmsg = l1_to_rtppayload_hr(payload, payload_len);
break;
case GsmL1_TchPlType_Efr:
rmsg = l1_to_rtppayload_efr(payload, payload_len);
break;
case GsmL1_TchPlType_Amr:
rmsg = l1_to_rtppayload_amr(payload, payload_len, lchan);
break;
}
if (rmsg) {
struct osmo_phsap_prim *l1sap;
LOGP(DL1C, LOGL_DEBUG, "%s Rx -> RTP: %s\n",
gsm_lchan_name(lchan), osmo_hexdump(rmsg->data, rmsg->len));
/* add l1sap header */
rmsg->l2h = rmsg->data;
msgb_push(rmsg, sizeof(*l1sap));
rmsg->l1h = rmsg->data;
l1sap = msgb_l1sap_prim(rmsg);
osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH, PRIM_OP_INDICATION, rmsg);
l1sap->u.tch.chan_nr = chan_nr;
return l1sap_up(trx, l1sap);
}
return 0;
err_payload_match:
LOGP(DL1C, LOGL_ERROR, "%s Rx Payload Type %s incompatible with lchan\n",
gsm_lchan_name(lchan),
get_value_string(lc15bts_tch_pl_names, payload_type));
return -EINVAL;
}
struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan)
{
struct msgb *msg;
GsmL1_Prim_t *l1p;
GsmL1_PhDataReq_t *data_req;
GsmL1_MsgUnitParam_t *msu_param;
uint8_t *payload_type;
uint8_t *l1_payload;
msg = l1p_msgb_alloc();
if (!msg)
return NULL;
l1p = msgb_l1prim(msg);
data_req = &l1p->u.phDataReq;
msu_param = &data_req->msgUnitParam;
payload_type = &msu_param->u8Buffer[0];
l1_payload = &msu_param->u8Buffer[1];
switch (lchan->tch_mode) {
case GSM48_CMODE_SPEECH_AMR:
*payload_type = GsmL1_TchPlType_Amr;
if (lchan->tch.last_sid.len) {
memcpy(l1_payload, lchan->tch.last_sid.buf,
lchan->tch.last_sid.len);
msu_param->u8Size = lchan->tch.last_sid.len+1;
} else {
/* FIXME: decide if we should send SPEECH_BAD or
* SID_BAD */
#if 0
*payload_type = GsmL1_TchPlType_Amr_SidBad;
memset(l1_payload, 0xFF, 5);
msu_param->u8Size = 5 + 3;
#else
/* send an all-zero SID */
msu_param->u8Size = 8;
#endif
}
break;
default:
msgb_free(msg);
msg = NULL;
break;
}
return msg;
}

View File

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

View File

@ -0,0 +1,17 @@
#ifndef _UTILS_H
#define _UTILS_H
#include <stdint.h>
#include "lc15bts.h"
struct gsm_bts_trx;
int band_lc152osmo(GsmL1_FreqBand_t band);
int lc15bts_select_lc15_band(struct gsm_bts_trx *trx, uint16_t arfcn);
int lc15bts_get_nominal_power(struct gsm_bts_trx *trx);
struct lc15l1_hdl *trx_lc15l1_hdl(struct gsm_bts_trx *trx);
#endif