diff options
author | Vadim Yanitskiy <vyanitskiy@sysmocom.de> | 2020-10-29 06:40:58 +0700 |
---|---|---|
committer | Vadim Yanitskiy <vyanitskiy@sysmocom.de> | 2020-11-05 03:39:50 +0700 |
commit | ef1671c6ed3362958b95a7074c8d2242b756c6cf (patch) | |
tree | 1c80d546d39abcee29b0924e251a58b451c20369 /src/osmo-bts-lc15 | |
parent | 44d25af9cd1288feda867ce60c636d9334521c4f (diff) |
osmo-bts-lc15: use consistent name for containing directory
The binary is called 'osmo-bts-lc15', while the containing folder
is named 'osmo-bts-litecell15'. This inconsistency complicates
automatic generation of the XML VTY reference - fix it.
Change-Id: I55c073fbd01aee42871101401d76d87e7c91832e
Related: SYS#4937, OS#3036
Diffstat (limited to 'src/osmo-bts-lc15')
43 files changed, 12145 insertions, 0 deletions
diff --git a/src/osmo-bts-lc15/Makefile.am b/src/osmo-bts-lc15/Makefile.am new file mode 100644 index 00000000..bb68efaf --- /dev/null +++ b/src/osmo-bts-lc15/Makefile.am @@ -0,0 +1,38 @@ +AUTOMAKE_OPTIONS = subdir-objects + +AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include $(LITECELL15_INCDIR) +AM_CFLAGS = -Wall $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCODEC_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOTRAU_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(LIBGPS_CFLAGS) $(LIBSYSTEMD_CFLAGS) +COMMON_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOCODEC_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOTRAU_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOCTRL_LIBS) + +AM_CFLAGS += -DENABLE_LC15BTS + +EXTRA_DIST = misc/lc15bts_mgr.h misc/lc15bts_misc.h misc/lc15bts_par.h misc/lc15bts_led.h \ + misc/lc15bts_temp.h misc/lc15bts_power.h misc/lc15bts_clock.h \ + misc/lc15bts_bid.h misc/lc15bts_nl.h misc/lc15bts_bts.h misc/lc15bts_swd.h \ + hw_misc.h l1_if.h l1_transp.h lc15bts.h utils.h + +bin_PROGRAMS = osmo-bts-lc15 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 + +osmo_bts_lc15_SOURCES = $(COMMON_SOURCES) l1_transp_hw.c +osmo_bts_lc15_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 \ + misc/lc15bts_led.c \ + misc/lc15bts_bts.c \ + misc/lc15bts_swd.c + +lc15bts_mgr_LDADD = $(top_builddir)/src/common/libbts.a $(LIBGPS_LIBS) $(LIBOSMOCORE_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMOABIS_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOCTRL_LIBS) $(LIBSYSTEMD_LIBS) $(COMMON_LDADD) + +lc15bts_util_SOURCES = misc/lc15bts_util.c misc/lc15bts_par.c +lc15bts_util_LDADD = $(LIBOSMOCORE_LIBS) diff --git a/src/osmo-bts-lc15/calib_file.c b/src/osmo-bts-lc15/calib_file.c new file mode 100644 index 00000000..2641552f --- /dev/null +++ b/src/osmo-bts-lc15/calib_file.c @@ -0,0 +1,456 @@ +/* NuRAN Wireless Litecell 1.5 BTS L1 calibration file routines*/ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org> + * + * Based on sysmoBTS: + * (C) 2012 by Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <fcntl.h> +#include <limits.h> +#include <errno.h> + +#include <osmocom/core/utils.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> + +#include <nrw/litecell15/litecell15.h> +#include <nrw/litecell15/gsml1const.h> + +#include "l1_if.h" +#include "lc15bts.h" +#include "utils.h" + +/* Maximum calibration data chunk size */ +#define MAX_CALIB_TBL_SIZE 65536 +/* Calibration header version */ +#define CALIB_HDR_V1 0x01 + +struct calib_file_desc { + const char *fname; + int rx; + int trx; + int rxpath; +}; + +static const struct calib_file_desc calib_files[] = { + { + .fname = "calib_rx0a.conf", + .rx = 1, + .trx = 0, + .rxpath = 0, + }, { + .fname = "calib_rx0b.conf", + .rx = 1, + .trx = 0, + .rxpath = 1, + }, { + .fname = "calib_rx1a.conf", + .rx = 1, + .trx = 1, + .rxpath = 0, + }, { + .fname = "calib_rx1b.conf", + .rx = 1, + .trx = 1, + .rxpath = 1, + }, { + .fname = "calib_tx0.conf", + .rx = 0, + .trx = 0, + }, { + .fname = "calib_tx1.conf", + .rx = 0, + .trx = 1, + }, +}; + +struct calTbl_t +{ + union + { + struct + { + uint8_t u8Version; /* Header version (1) */ + uint8_t u8Parity; /* Parity byte (xor) */ + uint8_t u8Type; /* Table type (0:TX Downlink, 1:RX-A Uplink, 2:RX-B Uplink) */ + uint8_t u8Band; /* GSM Band (0:GSM-850, 1:EGSM-900, 2:DCS-1800, 3:PCS-1900) */ + uint32_t u32Len; /* Table length in bytes including the header */ + struct + { + uint32_t u32DescOfst; /* Description section offset */ + uint32_t u32DateOfst; /* Date section offset */ + uint32_t u32StationOfst; /* Calibration test station section offset */ + uint32_t u32FpgaFwVerOfst; /* Calibration FPGA firmware version section offset */ + uint32_t u32DspFwVerOfst; /* Calibration DSP firmware section offset */ + uint32_t u32DataOfst; /* Calibration data section offset */ + } toc; + } v1; + } hdr; + + uint8_t u8RawData[MAX_CALIB_TBL_SIZE - 32]; +}; + + +static int calib_file_send(struct lc15l1_hdl *fl1h, + const struct calib_file_desc *desc); +static int calib_verify(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) +{ + struct phy_link *plink = fl1h->phy_inst->phy_link; + int i; + + for (i = last_idx+1; i < ARRAY_SIZE(calib_files); i++) { + if (calib_files[i].trx == plink->num) + return i; + } + return -1; +} + +static int calib_file_open(struct lc15l1_hdl *fl1h, + const struct calib_file_desc *desc) +{ + struct calib_send_state *st = &fl1h->st; + char *calib_path = fl1h->phy_inst->u.lc15.calib_path; + char fname[PATH_MAX]; + + if (st->fp) { + LOGP(DL1C, LOGL_NOTICE, "L1 calibration file was left opened !!\n"); + fclose(st->fp); + st->fp = NULL; + } + + fname[0] = '\0'; + snprintf(fname, sizeof(fname)-1, "%s/%s", calib_path, desc->fname); + fname[sizeof(fname)-1] = '\0'; + + st->fp = fopen(fname, "rb"); + if (!st->fp) { + LOGP(DL1C, LOGL_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; + } + + rc = calib_verify(fl1h, desc); + if ( rc < 0 ) { + LOGP(DL1C, LOGL_ERROR, "Verify L1 calibration table %s -> failed (%d)\n", desc->fname, rc); + st->last_file_idx = get_next_calib_file_idx(fl1h, st->last_file_idx); + + if (st->last_file_idx >= 0) + return calib_file_send(fl1h, + &calib_files[st->last_file_idx]); + return 0; + + } + + LOGP(DL1C, LOGL_INFO, "Verify L1 calibration table %s -> done\n", desc->fname); + + return calib_file_send_next_chunk(fl1h); +} + +/* completion callback after every SetCalibTbl is confirmed */ +static int calib_send_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + struct 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; + char *calib_path = fl1h->phy_inst->u.lc15.calib_path; + + if (!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]); +} + + +static int calib_verify(struct lc15l1_hdl *fl1h, const struct calib_file_desc *desc) +{ + int rc, sz; + struct calib_send_state *st = &fl1h->st; + struct phy_link *plink = fl1h->phy_inst->phy_link; + char *rbuf; + struct calTbl_t *calTbl; + char calChkSum ; + + /* calculate file size in bytes */ + fseek(st->fp, 0L, SEEK_END); + sz = ftell(st->fp); + + /* rewind read pointer */ + fseek(st->fp, 0L, SEEK_SET); + + /* read file */ + rbuf = (char *) malloc( sizeof(char) * sz ); + + rc = fread(rbuf, 1, sizeof(char) * sz, st->fp); + if ( rc != sz) { + + LOGP(DL1C, LOGL_ERROR, "%s reading error\n", desc->fname); + free(rbuf); + + /* close file */ + rc = calib_file_close(fl1h); + if (rc < 0 ) { + LOGP(DL1C, LOGL_ERROR, "%s can not close\n", desc->fname); + return rc; + } + + return -2; + } + + calTbl = (struct calTbl_t*) rbuf; + /* calculate file checksum */ + calChkSum = 0; + while ( sz-- ) { + calChkSum ^= rbuf[sz]; + } + + /* validate Tx calibration parity */ + if ( calChkSum ) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid checksum %x.\n", desc->fname, calChkSum); + return -4; + } + + /* validate Tx calibration header */ + if ( calTbl->hdr.v1.u8Version != CALIB_HDR_V1 ) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid header version %u.\n", desc->fname, calTbl->hdr.v1.u8Version); + return -5; + } + + /* validate calibration description */ + if ( calTbl->hdr.v1.toc.u32DescOfst == 0xFFFFFFFF ) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration description offset.\n", desc->fname); + return -6; + } + + /* validate calibration date */ + if ( calTbl->hdr.v1.toc.u32DateOfst == 0xFFFFFFFF ) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration date offset.\n", desc->fname); + return -7; + } + + LOGP(DL1C, LOGL_INFO, "L1 calibration table %s created on %s\n", + desc->fname, + calTbl->u8RawData + calTbl->hdr.v1.toc.u32DateOfst); + + /* validate calibration station */ + if ( calTbl->hdr.v1.toc.u32StationOfst == 0xFFFFFFFF ) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration station ID offset.\n", desc->fname); + return -8; + } + + /* validate FPGA FW version */ + if ( calTbl->hdr.v1.toc.u32FpgaFwVerOfst == 0xFFFFFFFF ) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid FPGA FW version offset.\n", desc->fname); + return -9; + } + + /* validate DSP FW version */ + if ( calTbl->hdr.v1.toc.u32DspFwVerOfst == 0xFFFFFFFF ) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid DSP FW version offset.\n", desc->fname); + return -10; + } + + /* validate Tx calibration data offset */ + if ( calTbl->hdr.v1.toc.u32DataOfst == 0xFFFFFFFF ) { + LOGP(DL1C, LOGL_ERROR, "%s has invalid calibration data offset.\n", desc->fname); + return -11; + } + + if ( !desc->rx ) { + + /* parse min/max Tx power */ + fl1h->phy_inst->u.lc15.minTxPower = calTbl->u8RawData[calTbl->hdr.v1.toc.u32DataOfst + (5 << 2)]; + fl1h->phy_inst->u.lc15.maxTxPower = calTbl->u8RawData[calTbl->hdr.v1.toc.u32DataOfst + (6 << 2)]; + + /* override nominal Tx power of given TRX if needed */ + if ( fl1h->phy_inst->trx->nominal_power > fl1h->phy_inst->u.lc15.maxTxPower) { + LOGP(DL1C, LOGL_INFO, "Set TRX %u nominal Tx power to %d dBm (%d)\n", + plink->num, + fl1h->phy_inst->u.lc15.maxTxPower, + fl1h->phy_inst->trx->nominal_power); + + fl1h->phy_inst->trx->nominal_power = fl1h->phy_inst->u.lc15.maxTxPower; + } + + if ( fl1h->phy_inst->trx->nominal_power < fl1h->phy_inst->u.lc15.minTxPower) { + LOGP(DL1C, LOGL_INFO, "Set TRX %u nominal Tx power to %d dBm (%d)\n", + plink->num, + fl1h->phy_inst->u.lc15.minTxPower, + fl1h->phy_inst->trx->nominal_power); + + fl1h->phy_inst->trx->nominal_power = fl1h->phy_inst->u.lc15.minTxPower; + } + + if ( fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm > to_mdB(fl1h->phy_inst->u.lc15.maxTxPower) ) { + LOGP(DL1C, LOGL_INFO, "Set TRX %u Tx power parameter to %d dBm (%d)\n", + plink->num, + to_mdB(fl1h->phy_inst->u.lc15.maxTxPower), + fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm); + + fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm = to_mdB(fl1h->phy_inst->u.lc15.maxTxPower); + } + + if ( fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm < to_mdB(fl1h->phy_inst->u.lc15.minTxPower) ) { + LOGP(DL1C, LOGL_INFO, "Set TRX %u Tx power parameter to %d dBm (%d)\n", + plink->num, + to_mdB(fl1h->phy_inst->u.lc15.minTxPower), + fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm); + + fl1h->phy_inst->trx->power_params.trx_p_max_out_mdBm = to_mdB(fl1h->phy_inst->u.lc15.minTxPower); + } + + LOGP(DL1C, LOGL_DEBUG, "%s: minTxPower=%d, maxTxPower=%d\n", + desc->fname, + fl1h->phy_inst->u.lc15.minTxPower, + fl1h->phy_inst->u.lc15.maxTxPower ); + } + + /* rewind read pointer for subsequence tasks */ + fseek(st->fp, 0L, SEEK_SET); + free(rbuf); + + return 0; +} + diff --git a/src/osmo-bts-lc15/hw_misc.c b/src/osmo-bts-lc15/hw_misc.c new file mode 100644 index 00000000..9f070bba --- /dev/null +++ b/src/osmo-bts-lc15/hw_misc.c @@ -0,0 +1,88 @@ +/* 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("/var/lc15/leds/led0/brightness", O_WRONLY); + if (fd < 0) + return -ENODEV; + + rc = write(fd, cmd[0] ? "1" : "0", 2); + if (rc != 2) { + close(fd); + return -1; + } + close(fd); + + fd = open("/var/lc15/leds/led1/brightness", O_WRONLY); + if (fd < 0) + return -ENODEV; + + rc = write(fd, cmd[1] ? "1" : "0", 2); + if (rc != 2) { + close(fd); + return -1; + } + close(fd); + return 0; +} diff --git a/src/osmo-bts-lc15/hw_misc.h b/src/osmo-bts-lc15/hw_misc.h new file mode 100644 index 00000000..59ed04b7 --- /dev/null +++ b/src/osmo-bts-lc15/hw_misc.h @@ -0,0 +1,13 @@ +#ifndef _HW_MISC_H +#define _HW_MISC_H + +enum lc15bts_led_color { + LED_OFF, + LED_RED, + LED_GREEN, + LED_ORANGE, +}; + +int lc15bts_led_set(enum lc15bts_led_color c); + +#endif diff --git a/src/osmo-bts-lc15/l1_if.c b/src/osmo-bts-lc15/l1_if.c new file mode 100644 index 00000000..b7c24c08 --- /dev/null +++ b/src/osmo-bts-lc15/l1_if.c @@ -0,0 +1,1726 @@ +/* Interface handler for NuRAN Wireless Litecell 1.5 L1 */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org> + * + * Based on sysmoBTS: + * (C) 2011-2014 by Harald Welte <laforge@gnumonks.org> + * (C) 2014 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> + +#include <sys/types.h> +#include <sys/stat.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/select.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/write_queue.h> +#include <osmocom/gsm/gsm_utils.h> +#include <osmocom/gsm/lapdm.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/phy_link.h> +#include <osmo-bts/paging.h> +#include <osmo-bts/measurement.h> +#include <osmo-bts/pcu_if.h> +#include <osmo-bts/handover.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/msg_utils.h> +#include <osmo-bts/dtx_dl_amr_fsm.h> +#include <osmo-bts/nm_common_fsm.h> + +#include <nrw/litecell15/litecell15.h> +#include <nrw/litecell15/gsml1prim.h> +#include <nrw/litecell15/gsml1const.h> +#include <nrw/litecell15/gsml1types.h> + +#include "lc15bts.h" +#include "l1_if.h" +#include "l1_transp.h" +#include "hw_misc.h" +#include "misc/lc15bts_par.h" +#include "misc/lc15bts_bid.h" +#include "utils.h" + +extern unsigned int dsp_trace; + +struct wait_l1_conf { + struct llist_head list; /* internal linked list */ + struct osmo_timer_list timer; /* timer for L1 timeout */ + unsigned int conf_prim_id; /* primitive we expect in response */ + HANDLE conf_hLayer3; /* layer 3 handle we expect in response */ + unsigned int is_sys_prim; /* is this a system (1) or L1 (0) primitive */ + l1if_compl_cb *cb; + void *cb_data; +}; + +static void release_wlc(struct wait_l1_conf *wlc) +{ + osmo_timer_del(&wlc->timer); + talloc_free(wlc); +} + +static void l1if_req_timeout(void *data) +{ + struct wait_l1_conf *wlc = data; + + if (wlc->is_sys_prim) + LOGP(DL1C, LOGL_FATAL, "Timeout waiting for SYS primitive %s\n", + get_value_string(lc15bts_sysprim_names, wlc->conf_prim_id)); + else + LOGP(DL1C, LOGL_FATAL, "Timeout waiting for L1 primitive %s\n", + get_value_string(lc15bts_l1prim_names, wlc->conf_prim_id)); + exit(23); +} + +static HANDLE l1p_get_hLayer3(GsmL1_Prim_t *prim) +{ + switch (prim->id) { + case GsmL1_PrimId_MphInitReq: + return prim->u.mphInitReq.hLayer3; + case GsmL1_PrimId_MphCloseReq: + return prim->u.mphCloseReq.hLayer3; + case GsmL1_PrimId_MphConnectReq: + return prim->u.mphConnectReq.hLayer3; + case GsmL1_PrimId_MphDisconnectReq: + return prim->u.mphDisconnectReq.hLayer3; + case GsmL1_PrimId_MphActivateReq: + return prim->u.mphActivateReq.hLayer3; + case GsmL1_PrimId_MphDeactivateReq: + return prim->u.mphDeactivateReq.hLayer3; + case GsmL1_PrimId_MphConfigReq: + return prim->u.mphConfigReq.hLayer3; + case GsmL1_PrimId_MphMeasureReq: + return prim->u.mphMeasureReq.hLayer3; + case GsmL1_PrimId_MphInitCnf: + return prim->u.mphInitCnf.hLayer3; + case GsmL1_PrimId_MphCloseCnf: + return prim->u.mphCloseCnf.hLayer3; + case GsmL1_PrimId_MphConnectCnf: + return prim->u.mphConnectCnf.hLayer3; + case GsmL1_PrimId_MphDisconnectCnf: + return prim->u.mphDisconnectCnf.hLayer3; + case GsmL1_PrimId_MphActivateCnf: + return prim->u.mphActivateCnf.hLayer3; + case GsmL1_PrimId_MphDeactivateCnf: + return prim->u.mphDeactivateCnf.hLayer3; + case GsmL1_PrimId_MphConfigCnf: + return prim->u.mphConfigCnf.hLayer3; + case GsmL1_PrimId_MphMeasureCnf: + return prim->u.mphMeasureCnf.hLayer3; + case GsmL1_PrimId_MphTimeInd: + case GsmL1_PrimId_MphSyncInd: + case GsmL1_PrimId_PhEmptyFrameReq: + case GsmL1_PrimId_PhDataReq: + case GsmL1_PrimId_PhConnectInd: + case GsmL1_PrimId_PhReadyToSendInd: + case GsmL1_PrimId_PhDataInd: + case GsmL1_PrimId_PhRaInd: + break; + default: + LOGP(DL1C, LOGL_ERROR, "unknown L1 primitive %u\n", prim->id); + break; + } + return 0; +} + +static int _l1if_req_compl(struct lc15l1_hdl *fl1h, struct msgb *msg, + int is_system_prim, l1if_compl_cb *cb, void *data) +{ + struct wait_l1_conf *wlc; + struct osmo_wqueue *wqueue; + unsigned int timeout_secs; + + /* allocate new wsc and store reference to mutex and conf_id */ + wlc = talloc_zero(fl1h, struct wait_l1_conf); + wlc->cb = cb; + wlc->cb_data = data; + + /* Make sure we actually have received a REQUEST type primitive */ + if (is_system_prim == 0) { + GsmL1_Prim_t *l1p = msgb_l1prim(msg); + + LOGP(DL1P, LOGL_INFO, "Tx L1 prim %s\n", + get_value_string(lc15bts_l1prim_names, l1p->id)); + + if (lc15bts_get_l1prim_type(l1p->id) != L1P_T_REQ) { + LOGP(DL1C, LOGL_ERROR, "L1 Prim %s is not a Request!\n", + get_value_string(lc15bts_l1prim_names, l1p->id)); + talloc_free(wlc); + return -EINVAL; + } + wlc->is_sys_prim = 0; + wlc->conf_prim_id = lc15bts_get_l1prim_conf(l1p->id); + wlc->conf_hLayer3 = l1p_get_hLayer3(l1p); + wqueue = &fl1h->write_q[MQ_L1_WRITE]; + timeout_secs = 30; + } else { + Litecell15_Prim_t *sysp = msgb_sysprim(msg); + + LOGP(DL1C, LOGL_INFO, "Tx SYS prim %s\n", + get_value_string(lc15bts_sysprim_names, sysp->id)); + + if (lc15bts_get_sysprim_type(sysp->id) != L1P_T_REQ) { + LOGP(DL1C, LOGL_ERROR, "SYS Prim %s is not a Request!\n", + get_value_string(lc15bts_sysprim_names, sysp->id)); + talloc_free(wlc); + return -EINVAL; + } + wlc->is_sys_prim = 1; + wlc->conf_prim_id = lc15bts_get_sysprim_conf(sysp->id); + wqueue = &fl1h->write_q[MQ_SYS_WRITE]; + timeout_secs = 30; + } + + /* enqueue the message in the queue and add wsc to list */ + if (osmo_wqueue_enqueue(wqueue, msg) != 0) { + /* So we will get a timeout but the log message might help */ + LOGP(DL1C, LOGL_ERROR, "Write queue for %s full. dropping msg.\n", + is_system_prim ? "system primitive" : "gsm"); + msgb_free(msg); + } + llist_add(&wlc->list, &fl1h->wlc_list); + + /* schedule a timer for timeout_secs seconds. If DSP fails to respond, we terminate */ + wlc->timer.data = wlc; + wlc->timer.cb = l1if_req_timeout; + osmo_timer_schedule(&wlc->timer, timeout_secs, 0); + + return 0; +} + +/* send a request primitive to the L1 and schedule completion call-back */ +int l1if_req_compl(struct lc15l1_hdl *fl1h, struct msgb *msg, + l1if_compl_cb *cb, void *data) +{ + return _l1if_req_compl(fl1h, msg, 1, cb, data); +} + +int l1if_gsm_req_compl(struct lc15l1_hdl *fl1h, struct msgb *msg, + l1if_compl_cb *cb, void *data) +{ + return _l1if_req_compl(fl1h, msg, 0, cb, data); +} + +/* allocate a msgb containing a GsmL1_Prim_t */ +struct msgb *l1p_msgb_alloc(void) +{ + struct msgb *msg = msgb_alloc(sizeof(GsmL1_Prim_t), "l1_prim"); + + if (msg) + msg->l1h = msgb_put(msg, sizeof(GsmL1_Prim_t)); + + return msg; +} + +/* allocate a msgb containing a Litecell15_Prim_t */ +struct msgb *sysp_msgb_alloc(void) +{ + struct msgb *msg = msgb_alloc(sizeof(Litecell15_Prim_t), "sys_prim"); + + if (msg) + msg->l1h = msgb_put(msg, sizeof(Litecell15_Prim_t)); + + return msg; +} + +static GsmL1_PhDataReq_t * +data_req_from_rts_ind(GsmL1_Prim_t *l1p, + const GsmL1_PhReadyToSendInd_t *rts_ind) +{ + GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq; + + l1p->id = GsmL1_PrimId_PhDataReq; + + /* copy fields from PH-RSS.ind */ + data_req->hLayer1 = rts_ind->hLayer1; + data_req->u8Tn = rts_ind->u8Tn; + data_req->u32Fn = rts_ind->u32Fn; + data_req->sapi = rts_ind->sapi; + data_req->subCh = rts_ind->subCh; + data_req->u8BlockNbr = rts_ind->u8BlockNbr; + + return data_req; +} + +static GsmL1_PhEmptyFrameReq_t * +empty_req_from_rts_ind(GsmL1_Prim_t *l1p, + const GsmL1_PhReadyToSendInd_t *rts_ind) +{ + GsmL1_PhEmptyFrameReq_t *empty_req = &l1p->u.phEmptyFrameReq; + + l1p->id = GsmL1_PrimId_PhEmptyFrameReq; + + empty_req->hLayer1 = rts_ind->hLayer1; + empty_req->u8Tn = rts_ind->u8Tn; + empty_req->u32Fn = rts_ind->u32Fn; + empty_req->sapi = rts_ind->sapi; + empty_req->subCh = rts_ind->subCh; + empty_req->u8BlockNbr = rts_ind->u8BlockNbr; + + return empty_req; +} + +/* fill PH-DATA.req from l1sap primitive */ +static GsmL1_PhDataReq_t * +data_req_from_l1sap(GsmL1_Prim_t *l1p, struct lc15l1_hdl *fl1, + uint8_t tn, uint32_t fn, uint8_t sapi, uint8_t sub_ch, + uint8_t block_nr, uint8_t len) +{ + GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq; + + l1p->id = GsmL1_PrimId_PhDataReq; + + /* copy fields from PH-RSS.ind */ + data_req->hLayer1 = (HANDLE)fl1->hLayer1; + data_req->u8Tn = tn; + data_req->u32Fn = fn; + data_req->sapi = sapi; + data_req->subCh = sub_ch; + data_req->u8BlockNbr = block_nr; + + data_req->msgUnitParam.u8Size = len; + + return data_req; +} + +/* fill PH-EMPTY_FRAME.req from l1sap primitive */ +static GsmL1_PhEmptyFrameReq_t * +empty_req_from_l1sap(GsmL1_Prim_t *l1p, struct lc15l1_hdl *fl1, + uint8_t tn, uint32_t fn, uint8_t sapi, + uint8_t subch, uint8_t block_nr) +{ + GsmL1_PhEmptyFrameReq_t *empty_req = &l1p->u.phEmptyFrameReq; + + l1p->id = GsmL1_PrimId_PhEmptyFrameReq; + + empty_req->hLayer1 = (HANDLE)fl1->hLayer1; + empty_req->u8Tn = tn; + empty_req->u32Fn = fn; + empty_req->sapi = sapi; + empty_req->subCh = subch; + empty_req->u8BlockNbr = block_nr; + + return empty_req; +} + +static int ph_data_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap, bool use_cache) +{ + struct lc15l1_hdl *fl1 = trx_lc15l1_hdl(trx); + struct msgb *l1msg = l1p_msgb_alloc(); + struct gsm_lchan *lchan; + uint32_t u32Fn; + uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi = 0; + uint8_t chan_nr, link_id; + int len; + + if (!msg) { + LOGPFN(DL1C, LOGL_FATAL, l1sap->u.data.fn, "PH-DATA.req without msg. Please fix!\n"); + abort(); + } + + len = msgb_l2len(msg); + + chan_nr = l1sap->u.data.chan_nr; + link_id = l1sap->u.data.link_id; + u32Fn = l1sap->u.data.fn; + u8Tn = L1SAP_CHAN2TS(chan_nr); + subCh = 0x1f; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + if (L1SAP_IS_LINK_SACCH(link_id)) { + sapi = GsmL1_Sapi_Sacch; + if (!L1SAP_IS_CHAN_TCHF(chan_nr) && !L1SAP_IS_CHAN_PDCH(chan_nr)) + subCh = l1sap_chan2ss(chan_nr); + } else if (L1SAP_IS_CHAN_TCHF(chan_nr) || L1SAP_IS_CHAN_PDCH(chan_nr)) { + if (ts_is_pdch(&trx->ts[u8Tn])) { + if (L1SAP_IS_PTCCH(u32Fn)) { + sapi = GsmL1_Sapi_Ptcch; + u8BlockNbr = L1SAP_FN2PTCCHBLOCK(u32Fn); + } else { + sapi = GsmL1_Sapi_Pdtch; + u8BlockNbr = L1SAP_FN2MACBLOCK(u32Fn); + } + } else { + sapi = GsmL1_Sapi_FacchF; + u8BlockNbr = (u32Fn % 13) >> 2; + } + } else if (L1SAP_IS_CHAN_TCHH(chan_nr)) { + subCh = L1SAP_CHAN2SS_TCHH(chan_nr); + sapi = GsmL1_Sapi_FacchH; + u8BlockNbr = (u32Fn % 26) >> 3; + } else if (L1SAP_IS_CHAN_SDCCH4(chan_nr)) { + subCh = L1SAP_CHAN2SS_SDCCH4(chan_nr); + sapi = GsmL1_Sapi_Sdcch; + } else if (L1SAP_IS_CHAN_SDCCH8(chan_nr)) { + subCh = L1SAP_CHAN2SS_SDCCH8(chan_nr); + sapi = GsmL1_Sapi_Sdcch; + } else if (L1SAP_IS_CHAN_BCCH(chan_nr)) { + sapi = GsmL1_Sapi_Bcch; + } else if (L1SAP_IS_CHAN_CBCH(chan_nr)) { + sapi = GsmL1_Sapi_Cbch; + } else if (L1SAP_IS_CHAN_AGCH_PCH(chan_nr)) { + /* The sapi depends on DSP configuration, not + * on the actual SYSTEM INFORMATION 3. */ + u8BlockNbr = l1sap_fn2ccch_block(u32Fn); + if (u8BlockNbr >= num_agch(trx, "PH-DATA-REQ")) + sapi = GsmL1_Sapi_Pch; + else + sapi = GsmL1_Sapi_Agch; + } else { + LOGPFN(DL1C, LOGL_NOTICE, u32Fn, "unknown prim %d op %d " + "chan_nr %d link_id %d\n", l1sap->oph.primitive, + l1sap->oph.operation, chan_nr, link_id); + msgb_free(l1msg); + return -EINVAL; + } + + /* convert l1sap message to GsmL1 primitive, keep payload */ + if (len) { + /* data request */ + GsmL1_Prim_t *l1p = msgb_l1prim(l1msg); + data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr, len); + if (use_cache) + memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer, + lchan->tch.dtx.facch, msgb_l2len(msg)); + else if (dtx_dl_amr_enabled(lchan) && + ((lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F) || + (lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F) || + (lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F))) { + if (sapi == GsmL1_Sapi_FacchF) { + sapi = GsmL1_Sapi_TchF; + } + if (sapi == GsmL1_Sapi_FacchH) { + sapi = GsmL1_Sapi_TchH; + subCh = L1SAP_CHAN2SS_TCHH(chan_nr); + u8BlockNbr = (u32Fn % 13) >> 2; + } + if (sapi == GsmL1_Sapi_TchH || sapi == GsmL1_Sapi_TchF) { + /* FACCH interruption of DTX silence */ + /* cache FACCH data */ + memcpy(lchan->tch.dtx.facch, msg->l2h, + msgb_l2len(msg)); + /* prepare ONSET or INH message */ + if(lchan->tch.dtx.dl_amr_fsm->state == ST_ONSET_F) + l1p->u.phDataReq.msgUnitParam.u8Buffer[0] = + GsmL1_TchPlType_Amr_Onset; + else if(lchan->tch.dtx.dl_amr_fsm->state == ST_U_INH_F) + l1p->u.phDataReq.msgUnitParam.u8Buffer[0] = + GsmL1_TchPlType_Amr_SidUpdateInH; + else if(lchan->tch.dtx.dl_amr_fsm->state == ST_F1_INH_F) + l1p->u.phDataReq.msgUnitParam.u8Buffer[0] = + GsmL1_TchPlType_Amr_SidFirstInH; + /* ignored CMR/CMI pair */ + l1p->u.phDataReq.msgUnitParam.u8Buffer[1] = 0; + l1p->u.phDataReq.msgUnitParam.u8Buffer[2] = 0; + /* update length */ + data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, + subCh, u8BlockNbr, 3); + /* update FN so it can be checked by TCH silence + resume handler */ + lchan->tch.dtx.fn = LCHAN_FN_DUMMY; + } + } else if (dtx_dl_amr_enabled(lchan) && + lchan->tch.dtx.dl_amr_fsm->state == ST_FACCH) { + /* update FN so it can be checked by TCH silence + resume handler */ + lchan->tch.dtx.fn = LCHAN_FN_DUMMY; + } + else { + OSMO_ASSERT(msgb_l2len(msg) <= sizeof(l1p->u.phDataReq.msgUnitParam.u8Buffer)); + memcpy(l1p->u.phDataReq.msgUnitParam.u8Buffer, msg->l2h, + msgb_l2len(msg)); + } + LOGPFN(DL1P, LOGL_DEBUG, u32Fn, "PH-DATA.req(%s)\n", + osmo_hexdump(l1p->u.phDataReq.msgUnitParam.u8Buffer, + l1p->u.phDataReq.msgUnitParam.u8Size)); + } else { + /* empty frame */ + GsmL1_Prim_t *l1p = msgb_l1prim(l1msg); + + empty_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr); + } + + /* send message to DSP's queue */ + if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], l1msg) != 0) { + LOGPFN(DL1P, LOGL_ERROR, u32Fn, "MQ_L1_WRITE queue full. Dropping msg.\n"); + msgb_free(l1msg); + } else + dtx_int_signal(lchan); + + if (dtx_recursion(lchan)) + ph_data_req(trx, msg, l1sap, true); + return 0; +} + +static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap, bool use_cache, bool marker) +{ + struct lc15l1_hdl *fl1 = trx_lc15l1_hdl(trx); + struct gsm_lchan *lchan; + uint32_t u32Fn; + uint8_t u8Tn, subCh, u8BlockNbr = 0, sapi; + uint8_t chan_nr; + GsmL1_Prim_t *l1p; + struct msgb *nmsg = NULL; + int rc = -1; + + chan_nr = l1sap->u.tch.chan_nr; + u32Fn = l1sap->u.tch.fn; + u8Tn = L1SAP_CHAN2TS(chan_nr); + u8BlockNbr = (u32Fn % 13) >> 2; + if (L1SAP_IS_CHAN_TCHH(chan_nr)) { + subCh = L1SAP_CHAN2SS_TCHH(chan_nr); + sapi = GsmL1_Sapi_TchH; + } else { + subCh = 0x1f; + sapi = GsmL1_Sapi_TchF; + } + + lchan = get_lchan_by_chan_nr(trx, chan_nr); + + /* create new message and fill data */ + if (msg) { + msgb_pull(msg, sizeof(*l1sap)); + /* create new message */ + nmsg = l1p_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + l1p = msgb_l1prim(nmsg); + rc = l1if_tch_encode(lchan, + l1p->u.phDataReq.msgUnitParam.u8Buffer, + &l1p->u.phDataReq.msgUnitParam.u8Size, + msg->data, msg->len, u32Fn, use_cache, + l1sap->u.tch.marker); + if (rc < 0) { + /* no data encoded for L1: smth will be generated below */ + msgb_free(nmsg); + nmsg = NULL; + } + } + + /* no message/data, we might generate an empty traffic msg or re-send + cached SID in case of DTX */ + if (!nmsg) + nmsg = gen_empty_tch_msg(lchan, u32Fn); + + /* no traffic message, we generate an empty msg */ + if (!nmsg) { + nmsg = l1p_msgb_alloc(); + if (!nmsg) + return -ENOMEM; + } + + l1p = msgb_l1prim(nmsg); + + /* if we provide data, or if data is already in nmsg */ + if (l1p->u.phDataReq.msgUnitParam.u8Size) { + /* data request */ + data_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, + u8BlockNbr, + l1p->u.phDataReq.msgUnitParam.u8Size); + } else { + /* empty frame */ + if (trx->bts->dtxd && trx != trx->bts->c0) + lchan->tch.dtx.dl_active = true; + empty_req_from_l1sap(l1p, fl1, u8Tn, u32Fn, sapi, subCh, u8BlockNbr); + } + /* send message to DSP's queue */ + osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], nmsg); + if (dtx_is_first_p1(lchan)) + dtx_dispatch(lchan, E_FIRST); + else + dtx_int_signal(lchan); + + if (dtx_recursion(lchan)) /* DTX: send voice after ONSET was sent */ + return ph_tch_req(trx, l1sap->oph.msg, l1sap, true, false); + + return 0; +} + +static int mph_info_req(struct gsm_bts_trx *trx, struct msgb *msg, + struct osmo_phsap_prim *l1sap) +{ + struct lc15l1_hdl *fl1 = trx_lc15l1_hdl(trx); + uint8_t chan_nr; + struct gsm_lchan *lchan; + int rc = 0; + + switch (l1sap->u.info.type) { + case PRIM_INFO_ACT_CIPH: + chan_nr = l1sap->u.info.u.ciph_req.chan_nr; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + if (l1sap->u.info.u.ciph_req.uplink) { + l1if_set_ciphering(fl1, lchan, 0); + lchan->ciph_state = LCHAN_CIPH_RX_REQ; + } + if (l1sap->u.info.u.ciph_req.downlink) { + l1if_set_ciphering(fl1, lchan, 1); + lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ; + } + if (l1sap->u.info.u.ciph_req.downlink + && l1sap->u.info.u.ciph_req.uplink) + lchan->ciph_state = LCHAN_CIPH_RXTX_REQ; + break; + case PRIM_INFO_ACTIVATE: + case PRIM_INFO_DEACTIVATE: + case PRIM_INFO_MODIFY: + chan_nr = l1sap->u.info.u.act_req.chan_nr; + lchan = get_lchan_by_chan_nr(trx, chan_nr); + if (l1sap->u.info.type == PRIM_INFO_ACTIVATE) + l1if_rsl_chan_act(lchan); + else if (l1sap->u.info.type == PRIM_INFO_MODIFY) { + if (lchan->ho.active == HANDOVER_WAIT_FRAME) + l1if_rsl_chan_mod(lchan); + else + l1if_rsl_mode_modify(lchan); + } else if (l1sap->u.info.u.act_req.sacch_only) + l1if_rsl_deact_sacch(lchan); + else + l1if_rsl_chan_rel(lchan); + break; + default: + LOGP(DL1C, LOGL_NOTICE, "unknown MPH-INFO.req %d\n", + l1sap->u.info.type); + rc = -EINVAL; + } + + return rc; +} + +/* primitive from common part */ +int bts_model_l1sap_down(struct gsm_bts_trx *trx, struct osmo_phsap_prim *l1sap) +{ + struct msgb *msg = l1sap->oph.msg; + int rc = 0; + + /* called functions MUST NOT take ownership of msgb, as it is + * free()d below */ + switch (OSMO_PRIM_HDR(&l1sap->oph)) { + case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_REQUEST): + rc = ph_data_req(trx, msg, l1sap, false); + break; + case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST): + rc = ph_tch_req(trx, msg, l1sap, false, l1sap->u.tch.marker); + break; + case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST): + rc = mph_info_req(trx, msg, l1sap); + break; + default: + LOGP(DL1C, LOGL_NOTICE, "unknown prim %d op %d\n", + l1sap->oph.primitive, l1sap->oph.operation); + rc = -EINVAL; + } + + msgb_free(msg); + + return rc; +} + +static int handle_mph_time_ind(struct lc15l1_hdl *fl1, + GsmL1_MphTimeInd_t *time_ind, + struct msgb *msg) +{ + struct gsm_bts_trx *trx = lc15l1_hdl_trx(fl1); + struct gsm_bts *bts = trx->bts; + struct osmo_phsap_prim l1sap; + uint32_t fn; + + /* increment the primitive count for the alive timer */ + fl1->alive_prim_cnt++; + + /* ignore every time indication, except for c0 */ + if (trx != bts->c0) { + msgb_free(msg); + return 0; + } + + fn = time_ind->u32Fn; + + memset(&l1sap, 0, sizeof(l1sap)); + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, + PRIM_OP_INDICATION, NULL); + l1sap.u.info.type = PRIM_INFO_TIME; + l1sap.u.info.u.time_ind.fn = fn; + + msgb_free(msg); + + return l1sap_up(trx, &l1sap); +} + +static enum gsm_phys_chan_config pick_pchan(struct gsm_bts_trx_ts *ts) +{ + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_PDCH: + if (ts->flags & TS_F_PDCH_ACTIVE) + return GSM_PCHAN_PDCH; + return GSM_PCHAN_TCH_F; + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + return ts->dyn.pchan_is; + default: + return ts->pchan; + } +} + +static uint8_t chan_nr_by_sapi(struct gsm_bts_trx_ts *ts, + GsmL1_Sapi_t sapi, GsmL1_SubCh_t subCh, + uint8_t u8Tn, uint32_t u32Fn) +{ + uint8_t cbits = 0; + enum gsm_phys_chan_config pchan = pick_pchan(ts); + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_PDCH); + OSMO_ASSERT(pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH); + + switch (sapi) { + case GsmL1_Sapi_Bcch: + cbits = 0x10; + break; + case GsmL1_Sapi_Cbch: + cbits = 0xc8 >> 3; /* Osmocom extension for CBCH via L1SAP */ + break; + case GsmL1_Sapi_Sacch: + switch(pchan) { + case GSM_PCHAN_TCH_F: + cbits = 0x01; + break; + case GSM_PCHAN_TCH_H: + cbits = 0x02 + subCh; + break; + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + cbits = 0x04 + subCh; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + cbits = 0x08 + subCh; + break; + default: + LOGP(DL1C, LOGL_ERROR, "SACCH for pchan %d?\n", + pchan); + return 0; + } + break; + case GsmL1_Sapi_Sdcch: + switch(pchan) { + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + cbits = 0x04 + subCh; + break; + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + cbits = 0x08 + subCh; + break; + default: + LOGP(DL1C, LOGL_ERROR, "SDCCH for pchan %d?\n", + pchan); + return 0; + } + break; + case GsmL1_Sapi_Agch: + case GsmL1_Sapi_Pch: + cbits = 0x12; + break; + case GsmL1_Sapi_Pdtch: + case GsmL1_Sapi_Pacch: + switch(pchan) { + case GSM_PCHAN_PDCH: + cbits = 0x01; + break; + default: + LOGP(DL1C, LOGL_ERROR, "PDTCH for pchan %d?\n", + pchan); + return 0; + } + break; + case GsmL1_Sapi_TchF: + cbits = 0x01; + break; + case GsmL1_Sapi_TchH: + cbits = 0x02 + subCh; + break; + case GsmL1_Sapi_FacchF: + cbits = 0x01; + break; + case GsmL1_Sapi_FacchH: + cbits = 0x02 + subCh; + break; + case GsmL1_Sapi_Ptcch: + if (!L1SAP_IS_PTCCH(u32Fn)) { + LOGP(DL1C, LOGL_FATAL, "Not expecting PTCCH at frame " + "number other than 12, got it at %u (%u). " + "Please fix!\n", u32Fn % 52, u32Fn); + abort(); + } + switch(pchan) { + case GSM_PCHAN_PDCH: + cbits = 0x01; + break; + default: + LOGP(DL1C, LOGL_ERROR, "PTCCH for pchan %d?\n", + pchan); + return 0; + } + break; + default: + return 0; + } + + /* not reached due to default case above */ + return (cbits << 3) | u8Tn; +} + +static const enum l1sap_common_sapi common_sapi_by_sapi_t[] = { + [GsmL1_Sapi_Idle] = L1SAP_COMMON_SAPI_IDLE, + [GsmL1_Sapi_Fcch] = L1SAP_COMMON_SAPI_FCCH, + [GsmL1_Sapi_Sch] = L1SAP_COMMON_SAPI_SCH, + [GsmL1_Sapi_Sacch] = L1SAP_COMMON_SAPI_SACCH, + [GsmL1_Sapi_Sdcch] = L1SAP_COMMON_SAPI_SDCCH, + [GsmL1_Sapi_Bcch] = L1SAP_COMMON_SAPI_BCCH, + [GsmL1_Sapi_Pch] = L1SAP_COMMON_SAPI_PCH, + [GsmL1_Sapi_Agch] = L1SAP_COMMON_SAPI_AGCH, + [GsmL1_Sapi_Cbch] = L1SAP_COMMON_SAPI_CBCH, + [GsmL1_Sapi_Rach] = L1SAP_COMMON_SAPI_RACH, + [GsmL1_Sapi_TchF] = L1SAP_COMMON_SAPI_TCH_F, + [GsmL1_Sapi_FacchF] = L1SAP_COMMON_SAPI_FACCH_F, + [GsmL1_Sapi_TchH] = L1SAP_COMMON_SAPI_TCH_H, + [GsmL1_Sapi_FacchH] = L1SAP_COMMON_SAPI_FACCH_H, + [GsmL1_Sapi_Nch] = L1SAP_COMMON_SAPI_NCH, + [GsmL1_Sapi_Pdtch] = L1SAP_COMMON_SAPI_PDTCH, + [GsmL1_Sapi_Pacch] = L1SAP_COMMON_SAPI_PACCH, + [GsmL1_Sapi_Pbcch] = L1SAP_COMMON_SAPI_PBCCH, + [GsmL1_Sapi_Pagch] = L1SAP_COMMON_SAPI_PAGCH, + [GsmL1_Sapi_Ppch] = L1SAP_COMMON_SAPI_PPCH, + [GsmL1_Sapi_Pnch] = L1SAP_COMMON_SAPI_PNCH, + [GsmL1_Sapi_Ptcch] = L1SAP_COMMON_SAPI_PTCCH, + [GsmL1_Sapi_Prach] = L1SAP_COMMON_SAPI_PRACH, +}; + +static enum l1sap_common_sapi get_common_sapi(GsmL1_Sapi_t sapi) +{ + if (sapi >= GsmL1_Sapi_NUM) + return L1SAP_COMMON_SAPI_UNKNOWN; + return common_sapi_by_sapi_t[sapi]; +} + +static void set_log_ctx_sapi(GsmL1_Sapi_t sapi) +{ + l1sap_log_ctx_sapi = get_common_sapi(sapi); + log_set_context(LOG_CTX_L1_SAPI, &l1sap_log_ctx_sapi); +} + +static int handle_ph_readytosend_ind(struct lc15l1_hdl *fl1, + GsmL1_PhReadyToSendInd_t *rts_ind, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = lc15l1_hdl_trx(fl1); + struct gsm_bts *bts = trx->bts; + struct msgb *resp_msg; + GsmL1_PhDataReq_t *data_req; + GsmL1_MsgUnitParam_t *msu_param; + struct gsm_time g_time; + uint32_t t3p; + int rc; + struct osmo_phsap_prim *l1sap; + uint8_t chan_nr, link_id; + uint32_t fn; + + set_log_ctx_sapi(rts_ind->sapi); + + /* check if primitive should be handled by common part */ + chan_nr = chan_nr_by_sapi(&trx->ts[rts_ind->u8Tn], rts_ind->sapi, + rts_ind->subCh, rts_ind->u8Tn, rts_ind->u32Fn); + if (chan_nr) { + fn = rts_ind->u32Fn; + if (rts_ind->sapi == GsmL1_Sapi_Sacch) + link_id = LID_SACCH; + else + link_id = LID_DEDIC; + /* recycle the msgb and use it for the L1 primitive, + * which means that we (or our caller) must not free it */ + rc = msgb_trim(l1p_msg, sizeof(*l1sap)); + if (rc < 0) + MSGB_ABORT(l1p_msg, "No room for primitive\n"); + l1sap = msgb_l1sap_prim(l1p_msg); + if (rts_ind->sapi == GsmL1_Sapi_TchF + || rts_ind->sapi == GsmL1_Sapi_TchH) { + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_TCH_RTS, + PRIM_OP_INDICATION, l1p_msg); + l1sap->u.tch.chan_nr = chan_nr; + l1sap->u.tch.fn = fn; + } else { + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RTS, + PRIM_OP_INDICATION, l1p_msg); + l1sap->u.data.link_id = link_id; + l1sap->u.data.chan_nr = chan_nr; + l1sap->u.data.fn = fn; + } + + return l1sap_up(trx, l1sap); + } + + gsm_fn2gsmtime(&g_time, rts_ind->u32Fn); + + DEBUGPGT(DL1P, &g_time, "Rx PH-RTS.ind SAPI=%s\n", + get_value_string(lc15bts_l1sapi_names, rts_ind->sapi)); + + /* in all other cases, we need to allocate a new PH-DATA.ind + * primitive msgb and start to fill it */ + resp_msg = l1p_msgb_alloc(); + data_req = data_req_from_rts_ind(msgb_l1prim(resp_msg), rts_ind); + msu_param = &data_req->msgUnitParam; + + /* set default size */ + msu_param->u8Size = GSM_MACBLOCK_LEN; + + switch (rts_ind->sapi) { + case GsmL1_Sapi_Sch: + /* compute T3prime */ + t3p = (g_time.t3 - 1) / 10; + /* fill SCH burst with data */ + msu_param->u8Size = 4; + msu_param->u8Buffer[0] = (bts->bsic << 2) | (g_time.t1 >> 9); + msu_param->u8Buffer[1] = (g_time.t1 >> 1); + msu_param->u8Buffer[2] = (g_time.t1 << 7) | (g_time.t2 << 2) | (t3p >> 1); + msu_param->u8Buffer[3] = (t3p & 1); + break; + case GsmL1_Sapi_Prach: + goto empty_frame; + break; + default: + memcpy(msu_param->u8Buffer, fill_frame, GSM_MACBLOCK_LEN); + break; + } +tx: + + /* transmit */ + if (osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], resp_msg) != 0) { + LOGPGT(DL1C, LOGL_ERROR, &g_time, "MQ_L1_WRITE queue full. Dropping msg.\n"); + msgb_free(resp_msg); + } + + /* free the msgb, as we have not handed it to l1sap and thus + * need to release its memory */ + msgb_free(l1p_msg); + return 0; + +empty_frame: + /* in case we decide to send an empty frame... */ + empty_req_from_rts_ind(msgb_l1prim(resp_msg), rts_ind); + + goto tx; +} + +#define LOG_FMT_MEAS "Meas: RSSI %-3.2f dBm, Qual %-3.2f dB, BER %-3.2f, Timing %d" +#define LOG_PARAM_MEAS(meas_param) (meas_param)->fRssi, (meas_param)->fLinkQuality, (meas_param)->fBer, (meas_param)->i16BurstTiming + +static int process_meas_res(struct gsm_bts_trx *trx, uint8_t chan_nr, + GsmL1_MeasParam_t *m, uint32_t fn) +{ + struct osmo_phsap_prim l1sap; + memset(&l1sap, 0, sizeof(l1sap)); + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, + PRIM_OP_INDICATION, NULL); + l1sap.u.info.type = PRIM_INFO_MEAS; + l1sap.u.info.u.meas_ind.chan_nr = chan_nr; + l1sap.u.info.u.meas_ind.ta_offs_256bits = m->i16BurstTiming*64; + l1sap.u.info.u.meas_ind.ber10k = (unsigned int) (m->fBer * 10000); + l1sap.u.info.u.meas_ind.inv_rssi = (uint8_t) (m->fRssi * -1); + l1sap.u.info.u.meas_ind.fn = fn; + + /* l1sap wants to take msgb ownership. However, as there is no + * msg, it will msgb_free(l1sap.oph.msg == NULL) */ + return l1sap_up(trx, &l1sap); +} + +static int handle_ph_data_ind(struct lc15l1_hdl *fl1, GsmL1_PhDataInd_t *data_ind, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = lc15l1_hdl_trx(fl1); + uint8_t chan_nr, link_id; + struct osmo_phsap_prim *l1sap; + uint32_t fn; + struct gsm_time g_time; + uint8_t *data, len; + int rc = 0; + int8_t rssi; + + set_log_ctx_sapi(data_ind->sapi); + + chan_nr = chan_nr_by_sapi(&trx->ts[data_ind->u8Tn], data_ind->sapi, + data_ind->subCh, data_ind->u8Tn, data_ind->u32Fn); + fn = data_ind->u32Fn; + link_id = (data_ind->sapi == GsmL1_Sapi_Sacch) ? LID_SACCH : LID_DEDIC; + gsm_fn2gsmtime(&g_time, fn); + + if (!chan_nr) { + LOGPGT(DL1C, LOGL_ERROR, &g_time, "PH-DATA-INDICATION for unknown sapi %s (%d)\n", + get_value_string(lc15bts_l1sapi_names, data_ind->sapi), data_ind->sapi); + msgb_free(l1p_msg); + return ENOTSUP; + } + + process_meas_res(trx, chan_nr, &data_ind->measParam, fn); + + + DEBUGPGT(DL1P, &g_time, "Rx PH-DATA.ind %s (hL2 %08x): %s, " LOG_FMT_MEAS "\n", + get_value_string(lc15bts_l1sapi_names, data_ind->sapi), (uint32_t)data_ind->hLayer2, + osmo_hexdump(data_ind->msgUnitParam.u8Buffer, data_ind->msgUnitParam.u8Size), + LOG_PARAM_MEAS(&data_ind->measParam)); + + /* check for TCH */ + if (data_ind->sapi == GsmL1_Sapi_TchF + || data_ind->sapi == GsmL1_Sapi_TchH) { + /* TCH speech frame handling */ + rc = l1if_tch_rx(trx, chan_nr, l1p_msg); + msgb_free(l1p_msg); + return rc; + } + + /* get rssi */ + rssi = (int8_t) (data_ind->measParam.fRssi); + /* get data pointer and length */ + data = data_ind->msgUnitParam.u8Buffer; + len = data_ind->msgUnitParam.u8Size; + /* pull lower header part before data */ + msgb_pull(l1p_msg, data - l1p_msg->data); + /* trim remaining data to it's size, to get rid of upper header part */ + rc = msgb_trim(l1p_msg, len); + if (rc < 0) + MSGB_ABORT(l1p_msg, "No room for primitive data\n"); + l1p_msg->l2h = l1p_msg->data; + /* push new l1 header */ + l1p_msg->l1h = msgb_push(l1p_msg, sizeof(*l1sap)); + /* fill header */ + l1sap = msgb_l1sap_prim(l1p_msg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_DATA, + PRIM_OP_INDICATION, l1p_msg); + l1sap->u.data.link_id = link_id; + l1sap->u.data.chan_nr = chan_nr; + l1sap->u.data.fn = fn; + l1sap->u.data.rssi = rssi; + l1sap->u.data.ber10k = data_ind->measParam.fBer * 10000; + l1sap->u.data.ta_offs_256bits = data_ind->measParam.i16BurstTiming*64; + l1sap->u.data.lqual_cb = data_ind->measParam.fLinkQuality * 10; + + return l1sap_up(trx, l1sap); +} + +static int handle_ph_ra_ind(struct lc15l1_hdl *fl1, GsmL1_PhRaInd_t *ra_ind, + struct msgb *l1p_msg) +{ + struct gsm_bts_trx *trx = lc15l1_hdl_trx(fl1); + struct gsm_lchan *lchan; + struct osmo_phsap_prim *l1sap; + int rc; + struct ph_rach_ind_param rach_ind_param; + + set_log_ctx_sapi(ra_ind->sapi); + LOGPFN(DL1C, LOGL_DEBUG, ra_ind->u32Fn, "Rx PH-RA.ind, " LOG_FMT_MEAS "\n", + LOG_PARAM_MEAS(&ra_ind->measParam)); + + if ((ra_ind->msgUnitParam.u8Size != 1) && + (ra_ind->msgUnitParam.u8Size != 2)) { + LOGPFN(DL1P, LOGL_ERROR, ra_ind->u32Fn, "PH-RACH-INDICATION has %d bits\n", ra_ind->sapi); + msgb_free(l1p_msg); + return 0; + } + + /* We need to evaluate ra_ind before below msgb_trim(), since that invalidates *ra_ind. */ + rach_ind_param = (struct ph_rach_ind_param) { + /* .chan_nr set below */ + /* .ra set below */ + .acc_delay = 0, + .fn = ra_ind->u32Fn, + /* .is_11bit set below */ + /* .burst_type set below */ + .rssi = (int8_t) ra_ind->measParam.fRssi, + .ber10k = (unsigned int) (ra_ind->measParam.fBer * 10000.0), + .acc_delay_256bits = ra_ind->measParam.i16BurstTiming * 64, + .lqual_cb = (int16_t) ra_ind->measParam.fLinkQuality * 10, /* centiBels */ + }; + + lchan = l1if_hLayer_to_lchan(trx, (uint32_t)ra_ind->hLayer2); + if (!lchan || lchan->ts->pchan == GSM_PCHAN_CCCH || + lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4 || + lchan->ts->pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH) + rach_ind_param.chan_nr = RSL_CHAN_RACH; + else + rach_ind_param.chan_nr = gsm_lchan2chan_nr(lchan); + + if (ra_ind->msgUnitParam.u8Size == 2) { + uint16_t temp; + uint16_t ra = ra_ind->msgUnitParam.u8Buffer[0]; + ra = ra << 3; + temp = (ra_ind->msgUnitParam.u8Buffer[1] & 0x7); + ra = ra | temp; + rach_ind_param.is_11bit = 1; + rach_ind_param.ra = ra; + } else { + rach_ind_param.is_11bit = 0; + rach_ind_param.ra = ra_ind->msgUnitParam.u8Buffer[0]; + } + + /* the old legacy full-bits acc_delay cannot express negative values */ + if (ra_ind->measParam.i16BurstTiming > 0) + rach_ind_param.acc_delay = ra_ind->measParam.i16BurstTiming >> 2; + + /* mapping of the burst type, the values are specific to + * osmo-bts-litecell15 */ + switch (ra_ind->burstType) { + case GsmL1_BurstType_Access_0: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_ACCESS_0; + break; + case GsmL1_BurstType_Access_1: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_ACCESS_1; + break; + case GsmL1_BurstType_Access_2: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_ACCESS_2; + break; + default: + rach_ind_param.burst_type = + GSM_L1_BURST_TYPE_NONE; + break; + } + + /* msgb_trim() invalidates ra_ind, make that abundantly clear: */ + ra_ind = NULL; + rc = msgb_trim(l1p_msg, sizeof(*l1sap)); + if (rc < 0) + MSGB_ABORT(l1p_msg, "No room for primitive data\n"); + l1sap = msgb_l1sap_prim(l1p_msg); + osmo_prim_init(&l1sap->oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, + l1p_msg); + l1sap->u.rach_ind = rach_ind_param; + + return l1sap_up(trx, l1sap); +} + +/* handle any random indication from the L1 */ +static int l1if_handle_ind(struct lc15l1_hdl *fl1, struct msgb *msg) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(msg); + int rc = 0; + + /* all the below called functions must take ownership of the msgb */ + switch (l1p->id) { + case GsmL1_PrimId_MphTimeInd: + rc = handle_mph_time_ind(fl1, &l1p->u.mphTimeInd, msg); + break; + case GsmL1_PrimId_MphSyncInd: + msgb_free(msg); + break; + case GsmL1_PrimId_PhConnectInd: + msgb_free(msg); + break; + case GsmL1_PrimId_PhReadyToSendInd: + rc = handle_ph_readytosend_ind(fl1, &l1p->u.phReadyToSendInd, + msg); + break; + case GsmL1_PrimId_PhDataInd: + rc = handle_ph_data_ind(fl1, &l1p->u.phDataInd, msg); + break; + case GsmL1_PrimId_PhRaInd: + rc = handle_ph_ra_ind(fl1, &l1p->u.phRaInd, msg); + break; + default: + msgb_free(msg); + } + + return rc; +} + +static inline int is_prim_compat(GsmL1_Prim_t *l1p, struct wait_l1_conf *wlc) +{ + if (wlc->is_sys_prim != 0) + return 0; + if (l1p->id != wlc->conf_prim_id) + return 0; + if (l1p_get_hLayer3(l1p) != wlc->conf_hLayer3) + return 0; + return 1; +} + +int l1if_handle_l1prim(int wq, struct lc15l1_hdl *fl1h, struct msgb *msg) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(msg); + struct wait_l1_conf *wlc; + int rc; + + switch (l1p->id) { + case GsmL1_PrimId_MphTimeInd: + /* silent, don't clog the log file */ + break; + default: + LOGP(DL1P, LOGL_DEBUG, "Rx L1 prim %s on queue %d\n", + get_value_string(lc15bts_l1prim_names, l1p->id), wq); + } + + /* check if this is a resposne to a sync-waiting request */ + llist_for_each_entry(wlc, &fl1h->wlc_list, list) { + if (is_prim_compat(l1p, wlc)) { + llist_del(&wlc->list); + if (wlc->cb) { + /* call-back function must take + * ownership of msgb */ + rc = wlc->cb(lc15l1_hdl_trx(fl1h), msg, + wlc->cb_data); + } else { + rc = 0; + msgb_free(msg); + } + release_wlc(wlc); + return rc; + } + } + + /* if we reach here, it is not a Conf for a pending Req */ + return l1if_handle_ind(fl1h, msg); +} + +int l1if_handle_sysprim(struct lc15l1_hdl *fl1h, struct msgb *msg) +{ + Litecell15_Prim_t *sysp = msgb_sysprim(msg); + struct wait_l1_conf *wlc; + int rc; + + LOGP(DL1P, LOGL_DEBUG, "Rx SYS prim %s\n", + get_value_string(lc15bts_sysprim_names, sysp->id)); + + /* check if this is a resposne to a sync-waiting request */ + llist_for_each_entry(wlc, &fl1h->wlc_list, list) { + /* the limitation here is that we cannot have multiple callers + * sending the same primitive */ + if (wlc->is_sys_prim && sysp->id == wlc->conf_prim_id) { + llist_del(&wlc->list); + if (wlc->cb) { + /* call-back function must take + * ownership of msgb */ + rc = wlc->cb(lc15l1_hdl_trx(fl1h), msg, + wlc->cb_data); + } else { + rc = 0; + msgb_free(msg); + } + release_wlc(wlc); + return rc; + } + } + /* if we reach here, it is not a Conf for a pending Req */ + return l1if_handle_ind(fl1h, msg); +} + +static int activate_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + Litecell15_Prim_t *sysp = msgb_sysprim(resp); + GsmL1_Status_t status; + int on = 0; + unsigned int i; + + if (sysp->id == Litecell15_PrimId_ActivateRfCnf) + on = 1; + + if (on) + status = sysp->u.activateRfCnf.status; + else + status = sysp->u.deactivateRfCnf.status; + + LOGP(DL1C, LOGL_INFO, "Rx RF-%sACT.conf (status=%s)\n", on ? "" : "DE", + get_value_string(lc15bts_l1status_names, status)); + + struct bts_lc15_priv *bts_lc15 = trx->bts->model_priv; + + if (on) { + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_FATAL, "RF-ACT.conf with status %s\n", + get_value_string(lc15bts_l1status_names, status)); + bts_shutdown(trx->bts, "RF-ACT failure"); + } else { + if (bts_lc15->led_ctrl_mode == LC15_LED_CONTROL_BTS) + bts_update_status(BTS_STATUS_RF_ACTIVE, 1); + } + + /* signal availability */ + osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_SW_ACT, NULL); + osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_SW_ACT, NULL); + } else { + if (bts_lc15->led_ctrl_mode == LC15_LED_CONTROL_BTS) + bts_update_status(BTS_STATUS_RF_ACTIVE, 0); + osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_DISABLE, NULL); + osmo_fsm_inst_dispatch(trx->bb_transc.mo.fi, NM_EV_DISABLE, NULL); + } + + msgb_free(resp); + + return 0; +} + +/* activate or de-activate the entire RF-Frontend */ +int l1if_activate_rf(struct lc15l1_hdl *hdl, int on) +{ + struct msgb *msg = sysp_msgb_alloc(); + Litecell15_Prim_t *sysp = msgb_sysprim(msg); + struct phy_instance *pinst = hdl->phy_inst; + + if (on) { + sysp->id = Litecell15_PrimId_ActivateRfReq; + sysp->u.activateRfReq.msgq.u8UseTchMsgq = 0; + sysp->u.activateRfReq.msgq.u8UsePdtchMsgq = pcu_direct; + + sysp->u.activateRfReq.u8UnusedTsMode = pinst->u.lc15.pedestal_mode; + + sysp->u.activateRfReq.u8McCorrMode = 0; + + /* diversity mode: 0: SISO-A, 1: SISO-B, 2: MRC */ + sysp->u.activateRfReq.u8DiversityMode = pinst->u.lc15.diversity_mode; + + /* maximum cell size in quarter-bits, 90 == 12.456 km */ + sysp->u.activateRfReq.u8MaxCellSize = pinst->u.lc15.max_cell_size; + +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) + /* auto tx power adjustment mode 0:none, 1: automatic*/ + sysp->u.activateRfReq.u8EnAutoPowerAdjust = pinst->u.lc15.tx_pwr_adj_mode; +#endif + + } else { + sysp->id = Litecell15_PrimId_DeactivateRfReq; + } + + return l1if_req_compl(hdl, msg, activate_rf_compl_cb, NULL); +} + +static void mute_handle_ts(struct gsm_bts_trx_ts *ts, int is_muted) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ts->lchan); i++) { + struct gsm_lchan *lchan = &ts->lchan[i]; + + if (!is_muted) + continue; + + if (lchan->state != LCHAN_S_ACTIVE) + continue; + + /* skip channels that might be active for another reason */ + if (lchan->type == GSM_LCHAN_CCCH) + continue; + if (lchan->type == GSM_LCHAN_PDTCH) + continue; + + if (lchan->s <= 0) + continue; + + lchan->s = 0; + rsl_tx_conn_fail(lchan, RSL_ERR_RADIO_LINK_FAIL); + } +} + +static int mute_rf_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + Litecell15_Prim_t *sysp = msgb_sysprim(resp); + GsmL1_Status_t status; + + status = sysp->u.muteRfCnf.status; + + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_ERROR, "Rx RF-MUTE.conf with status %s\n", + get_value_string(lc15bts_l1status_names, status)); + oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 0); + } else { + int i; + + LOGP(DL1C, LOGL_INFO, "Rx RF-MUTE.conf with status=%s\n", + get_value_string(lc15bts_l1status_names, status)); + bts_update_status(BTS_STATUS_RF_MUTE, fl1h->last_rf_mute[0]); + oml_mo_rf_lock_chg(&trx->mo, fl1h->last_rf_mute, 1); + + osmo_static_assert( + ARRAY_SIZE(trx->ts) >= ARRAY_SIZE(fl1h->last_rf_mute), + ts_array_size); + + for (i = 0; i < ARRAY_SIZE(fl1h->last_rf_mute); ++i) + mute_handle_ts(&trx->ts[i], fl1h->last_rf_mute[i]); + } + + msgb_free(resp); + + return 0; +} + +/* mute/unmute RF time slots */ +int l1if_mute_rf(struct lc15l1_hdl *hdl, uint8_t mute[8], l1if_compl_cb *cb) +{ + struct msgb *msg = sysp_msgb_alloc(); + Litecell15_Prim_t *sysp = msgb_sysprim(msg); + + LOGP(DL1C, LOGL_INFO, "Tx RF-MUTE.req (%d, %d, %d, %d, %d, %d, %d, %d)\n", + mute[0], mute[1], mute[2], mute[3], + mute[4], mute[5], mute[6], mute[7] + ); + + sysp->id = Litecell15_PrimId_MuteRfReq; + memcpy(sysp->u.muteRfReq.u8Mute, mute, sizeof(sysp->u.muteRfReq.u8Mute)); + /* save for later use */ + memcpy(hdl->last_rf_mute, mute, sizeof(hdl->last_rf_mute)); + + return l1if_req_compl(hdl, msg, cb ? cb : mute_rf_compl_cb, NULL); +} + +/* call-back on arrival of DSP+FPGA version + band capability */ +static int info_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + Litecell15_Prim_t *sysp = msgb_sysprim(resp); + Litecell15_SystemInfoCnf_t *sic = &sysp->u.systemInfoCnf; + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + int rc; + + fl1h->hw_info.dsp_version[0] = sic->dspVersion.major; + fl1h->hw_info.dsp_version[1] = sic->dspVersion.minor; + fl1h->hw_info.dsp_version[2] = sic->dspVersion.build; + + fl1h->hw_info.fpga_version[0] = sic->fpgaVersion.major; + fl1h->hw_info.fpga_version[1] = sic->fpgaVersion.minor; + fl1h->hw_info.fpga_version[2] = sic->fpgaVersion.build; + + LOGP(DL1C, LOGL_INFO, "DSP v%u.%u.%u, FPGA v%u.%u.%u\n", + sic->dspVersion.major, sic->dspVersion.minor, + sic->dspVersion.build, sic->fpgaVersion.major, + sic->fpgaVersion.minor, sic->fpgaVersion.build); + + if (!(fl1h->hw_info.band_support & trx->bts->band)) + LOGP(DL1C, LOGL_FATAL, "BTS band %s not supported by hw\n", + gsm_band_name(trx->bts->band)); + + /* Request the activation */ + l1if_activate_rf(fl1h, 1); + + /* load calibration tables */ + rc = calib_load(fl1h); + if (rc < 0) + LOGP(DL1C, LOGL_ERROR, "Operating without calibration; " + "unable to load tables!\n"); + + msgb_free(resp); + return 0; +} + +/* request DSP+FPGA code versions */ +static int l1if_get_info(struct lc15l1_hdl *hdl) +{ + struct msgb *msg = sysp_msgb_alloc(); + Litecell15_Prim_t *sysp = msgb_sysprim(msg); + + sysp->id = Litecell15_PrimId_SystemInfoReq; + + return l1if_req_compl(hdl, msg, info_compl_cb, NULL); +} + +static int reset_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + Litecell15_Prim_t *sysp = msgb_sysprim(resp); + GsmL1_Status_t status = sysp->u.layer1ResetCnf.status; + + LOGP(DL1C, LOGL_NOTICE, "Rx L1-RESET.conf (status=%s)\n", + get_value_string(lc15bts_l1status_names, status)); + + msgb_free(resp); + + /* If we're coming out of reset .. */ + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_FATAL, "L1-RESET.conf with status %s\n", + get_value_string(lc15bts_l1status_names, status)); + bts_shutdown(trx->bts, "L1-RESET failure"); + } + + /* as we cannot get the current DSP trace flags, we simply + * set them to zero (or whatever dsp_trace_f has been initialized to */ + l1if_set_trace_flags(fl1h, fl1h->dsp_trace_f); + + /* obtain version information on DSP/FPGA and band capabilities */ + l1if_get_info(fl1h); + + return 0; +} + +int l1if_reset(struct lc15l1_hdl *hdl) +{ + struct msgb *msg = sysp_msgb_alloc(); + Litecell15_Prim_t *sysp = msgb_sysprim(msg); + sysp->id = Litecell15_PrimId_Layer1ResetReq; + + return l1if_req_compl(hdl, msg, reset_compl_cb, NULL); +} + +/* set the trace flags within the DSP */ +int l1if_set_trace_flags(struct lc15l1_hdl *hdl, uint32_t flags) +{ + struct msgb *msg = sysp_msgb_alloc(); + Litecell15_Prim_t *sysp = msgb_sysprim(msg); + + LOGP(DL1C, LOGL_INFO, "Tx SET-TRACE-FLAGS.req (0x%08x)\n", + flags); + + sysp->id = Litecell15_PrimId_SetTraceFlagsReq; + sysp->u.setTraceFlagsReq.u32Tf = flags; + + hdl->dsp_trace_f = flags; + + /* There is no confirmation we could wait for */ + if (osmo_wqueue_enqueue(&hdl->write_q[MQ_SYS_WRITE], msg) != 0) { + LOGP(DL1C, LOGL_ERROR, "MQ_SYS_WRITE queue full. Dropping msg\n"); + msgb_free(msg); + return -EAGAIN; + } + return 0; +} + +static int get_hwinfo(struct lc15l1_hdl *fl1h) +{ + int rc; + + rc = lc15bts_rev_get(); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "Failed to obtain LC15BTS revision: %d\n", rc); + return rc; + } + fl1h->hw_info.ver_major = rc; + + rc = lc15bts_model_get(); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "Failed to obtain LC15BTS model: %d\n", rc); + return rc; + } + fl1h->hw_info.ver_minor = rc; + + rc = lc15bts_option_get(LC15BTS_OPTION_BAND); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "Failed to obtain LC15BTS_OPTION_BAND: %d\n", rc); + return rc; + } + + switch (rc) { + case LC15BTS_BAND_850: + fl1h->hw_info.band_support = GSM_BAND_850; + break; + case LC15BTS_BAND_900: + fl1h->hw_info.band_support = GSM_BAND_900; + break; + case LC15BTS_BAND_1800: + fl1h->hw_info.band_support = GSM_BAND_1800; + break; + case LC15BTS_BAND_1900: + fl1h->hw_info.band_support = GSM_BAND_1900; + break; + default: + LOGP(DL1C, LOGL_ERROR, "Unexpected LC15BTS_BAND value: %d\n", rc); + return -1; + } + + LOGP(DL1C, LOGL_INFO, "BTS hw support band %s\n", gsm_band_name(fl1h->hw_info.band_support)); + + return 0; +} + +struct lc15l1_hdl *l1if_open(struct phy_instance *pinst) +{ + struct lc15l1_hdl *fl1h; + int rc; + + LOGP(DL1C, LOGL_INFO, "Litecell 1.5 BTS L1IF compiled against API headers " + "v%u.%u.%u\n", LITECELL15_API_VERSION >> 16, + (LITECELL15_API_VERSION >> 8) & 0xff, + LITECELL15_API_VERSION & 0xff); + + fl1h = talloc_zero(pinst, struct lc15l1_hdl); + if (!fl1h) + return NULL; + INIT_LLIST_HEAD(&fl1h->wlc_list); + + fl1h->phy_inst = pinst; + fl1h->dsp_trace_f = pinst->u.lc15.dsp_trace_f; + + get_hwinfo(fl1h); + + rc = l1if_transport_open(MQ_SYS_WRITE, fl1h); + if (rc < 0) { + talloc_free(fl1h); + return NULL; + } + + rc = l1if_transport_open(MQ_L1_WRITE, fl1h); + if (rc < 0) { + l1if_transport_close(MQ_SYS_WRITE, fl1h); + talloc_free(fl1h); + return NULL; + } + + return fl1h; +} + +int l1if_close(struct lc15l1_hdl *fl1h) +{ + l1if_transport_close(MQ_L1_WRITE, fl1h); + l1if_transport_close(MQ_SYS_WRITE, fl1h); + return 0; +} + +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) +static int dsp_alive_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, void *data) +{ + Litecell15_Prim_t *sysp = msgb_sysprim(resp); + Litecell15_IsAliveCnf_t *sac = &sysp->u.isAliveCnf; + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + + fl1h->hw_alive.dsp_alive_cnt++; + LOGP(DL1C, LOGL_DEBUG, "Rx SYS prim %s, status=%d (%d)\n", + get_value_string(lc15bts_sysprim_names, sysp->id), sac->status, trx->nr); + + msgb_free(resp); + return 0; +} + +static void dsp_alive_timer_cb(void *data) +{ + struct lc15l1_hdl *fl1h = data; + struct gsm_bts_trx *trx = fl1h->phy_inst->trx; + struct msgb *msg = sysp_msgb_alloc(); + int rc; + + Litecell15_Prim_t *sys_prim = msgb_sysprim(msg); + sys_prim->id = Litecell15_PrimId_IsAliveReq; + + if (fl1h->hw_alive.dsp_alive_cnt == 0) { + LOGP(DL1C, LOGL_ERROR, "Timeout waiting for SYS prim %s primitive (%d)\n", + get_value_string(lc15bts_sysprim_names, sys_prim->id + 1), trx->nr); + + if( fl1h->phy_inst->trx ){ + fl1h->phy_inst->trx->mo.obj_inst.trx_nr = fl1h->phy_inst->trx->nr; + } + } + + LOGP(DL1C, LOGL_DEBUG, "Tx SYS prim %s (%d)\n", + get_value_string(lc15bts_sysprim_names, sys_prim->id), trx->nr); + + rc = l1if_req_compl(fl1h, msg, dsp_alive_compl_cb, NULL); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "Failed to send %s primitive\n", get_value_string(lc15bts_sysprim_names, sys_prim->id)); + return; + } + + /* restart timer */ + fl1h->hw_alive.dsp_alive_cnt = 0; + osmo_timer_schedule(&fl1h->hw_alive.dsp_alive_timer, fl1h->hw_alive.dsp_alive_period, 0); + + return; +} +#endif + +int bts_model_phy_link_open(struct phy_link *plink) +{ + struct phy_instance *pinst = phy_instance_by_num(plink, 0); + + OSMO_ASSERT(pinst); + + if (!pinst->trx) { + LOGP(DL1C, LOGL_NOTICE, "Ignoring phy link %d instance %d " + "because no TRX is associated with it\n", plink->num, pinst->num); + return 0; + } + phy_link_state_set(plink, PHY_LINK_CONNECTING); + + pinst->u.lc15.hdl = l1if_open(pinst); + if (!pinst->u.lc15.hdl) { + LOGP(DL1C, LOGL_FATAL, "Cannot open L1 interface\n"); + return -EIO; + } + + /* Set default PHY parameters */ + if (!pinst->u.lc15.max_cell_size) + pinst->u.lc15.max_cell_size = LC15_BTS_MAX_CELL_SIZE_DEFAULT; + + if (!pinst->u.lc15.diversity_mode) + pinst->u.lc15.diversity_mode = LC15_BTS_DIVERSITY_MODE_DEFAULT; + + if (!pinst->u.lc15.pedestal_mode) + pinst->u.lc15.pedestal_mode = LC15_BTS_PEDESTAL_MODE_DEFAULT; + +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) + if (!pinst->u.lc15.dsp_alive_period) + pinst->u.lc15.dsp_alive_period = LC15_BTS_DSP_ALIVE_TMR_DEFAULT; + + if (!pinst->u.lc15.tx_pwr_adj_mode) + pinst->u.lc15.tx_pwr_adj_mode = LC15_BTS_TX_PWR_ADJ_DEFAULT; + + if (!pinst->u.lc15.tx_pwr_red_8psk) + pinst->u.lc15.tx_pwr_red_8psk = LC15_BTS_TX_RED_PWR_8PSK_DEFAULT; + + if (!pinst->u.lc15.tx_c0_idle_pwr_red) + pinst->u.lc15.tx_c0_idle_pwr_red = LC15_BTS_TX_C0_IDLE_RED_PWR_DEFAULT; +#endif + + struct lc15l1_hdl *fl1h = pinst->u.lc15.hdl; + fl1h->dsp_trace_f = dsp_trace; + + l1if_reset(pinst->u.lc15.hdl); + + phy_link_state_set(plink, PHY_LINK_CONNECTED); + +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) + /* Send first IS_ALIVE primitive */ + struct msgb *msg = sysp_msgb_alloc(); + int rc; + + Litecell15_Prim_t *sys_prim = msgb_sysprim(msg); + sys_prim->id = Litecell15_PrimId_IsAliveReq; + + rc = l1if_req_compl(fl1h, msg, dsp_alive_compl_cb, NULL); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "Failed to send %s primitive\n", get_value_string(lc15bts_sysprim_names, sys_prim->id)); + return -EIO; + } + + /* initialize DSP heart beat alive timer */ + osmo_timer_setup(&fl1h->hw_alive.dsp_alive_timer, dsp_alive_timer_cb, fl1h); + fl1h->hw_alive.dsp_alive_cnt = 0; + fl1h->hw_alive.dsp_alive_period = pinst->u.lc15.dsp_alive_period; + osmo_timer_schedule(&fl1h->hw_alive.dsp_alive_timer, fl1h->hw_alive.dsp_alive_period, 0); +#endif + + return 0; +} diff --git a/src/osmo-bts-lc15/l1_if.h b/src/osmo-bts-lc15/l1_if.h new file mode 100644 index 00000000..655e63fb --- /dev/null +++ b/src/osmo-bts-lc15/l1_if.h @@ -0,0 +1,149 @@ +#ifndef _L1_IF_H +#define _L1_IF_H + +#include <osmocom/core/select.h> +#include <osmocom/core/write_queue.h> +#include <osmocom/core/gsmtap_util.h> +#include <osmocom/core/timer.h> +#include <osmocom/gsm/gsm_utils.h> + +#include <osmo-bts/phy_link.h> + +#include <nrw/litecell15/gsml1prim.h> +#include <nrw/litecell15/gsml1types.h> + +#include <stdbool.h> + +enum { + MQ_SYS_READ, + MQ_L1_READ, + MQ_TCH_READ, + MQ_PDTCH_READ, + _NUM_MQ_READ +}; + +enum { + MQ_SYS_WRITE, + MQ_L1_WRITE, + MQ_TCH_WRITE, + MQ_PDTCH_WRITE, + _NUM_MQ_WRITE +}; + +/* gsm_bts->model_priv, specific to Litecell 1.5 BTS */ +struct bts_lc15_priv { + uint8_t led_ctrl_mode; /* 0: control by BTS, 1: not control by BTS */ + unsigned int rtp_drift_thres_ms; /* RTP timestamp drift detection threshold */ +}; + +struct calib_send_state { + FILE *fp; + const char *path; + int last_file_idx; +}; + +struct lc15l1_hdl { + struct gsm_time gsm_time; + HANDLE hLayer1; /* handle to the L1 instance in the DSP */ + uint32_t dsp_trace_f; /* currently operational DSP trace flags */ + struct llist_head wlc_list; + + struct phy_instance *phy_inst; + + struct osmo_timer_list alive_timer; + unsigned int alive_prim_cnt; + + struct osmo_fd read_ofd[_NUM_MQ_READ]; /* osmo file descriptors */ + struct osmo_wqueue write_q[_NUM_MQ_WRITE]; + + struct { + /* from DSP/FPGA after L1 Init */ + uint8_t dsp_version[3]; + uint8_t fpga_version[3]; + uint32_t band_support; + uint8_t ver_major; + uint8_t ver_minor; + } hw_info; + + struct calib_send_state st; + + uint8_t last_rf_mute[8]; + +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) + struct { + struct osmo_timer_list dsp_alive_timer; + unsigned int dsp_alive_cnt; + uint8_t dsp_alive_period; + } hw_alive; +#endif + +}; + +#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(struct phy_instance *pinst); +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 */ +int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len, + const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn, + bool use_cache, bool marker); +int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg); +int l1if_tch_fill(struct gsm_lchan *lchan, uint8_t *l1_buffer); +struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan, uint32_t fn); + +/* ciphering */ +int l1if_set_ciphering(struct 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); +int l1if_ms_pwr_ctrl(struct gsm_lchan *lchan, const int uplink_target, + const uint8_t ms_power, const float rxLevel); + +static inline struct lc15l1_hdl *trx_lc15l1_hdl(const struct gsm_bts_trx *trx) +{ + const struct phy_instance *pinst = trx_phy_instance(trx); + OSMO_ASSERT(pinst); + return pinst->u.lc15.hdl; +} + +static inline struct gsm_bts_trx *lc15l1_hdl_trx(const struct lc15l1_hdl *fl1h) +{ + OSMO_ASSERT(fl1h->phy_inst); + return fl1h->phy_inst->trx; +} + +#endif /* _L1_IF_H */ diff --git a/src/osmo-bts-lc15/l1_transp.h b/src/osmo-bts-lc15/l1_transp.h new file mode 100644 index 00000000..7d6772e8 --- /dev/null +++ b/src/osmo-bts-lc15/l1_transp.h @@ -0,0 +1,14 @@ +#ifndef _L1_TRANSP_H +#define _L1_TRANSP_H + +#include <osmocom/core/msgb.h> + +/* functions a transport calls on arrival of primitive from BTS */ +int l1if_handle_l1prim(int wq, struct lc15l1_hdl *fl1h, struct msgb *msg); +int l1if_handle_sysprim(struct lc15l1_hdl *fl1h, struct msgb *msg); + +/* functions exported by a transport */ +int l1if_transport_open(int q, struct lc15l1_hdl *fl1h); +int l1if_transport_close(int q, struct lc15l1_hdl *fl1h); + +#endif /* _L1_TRANSP_H */ diff --git a/src/osmo-bts-lc15/l1_transp_hw.c b/src/osmo-bts-lc15/l1_transp_hw.c new file mode 100644 index 00000000..ffe8613e --- /dev/null +++ b/src/osmo-bts-lc15/l1_transp_hw.c @@ -0,0 +1,318 @@ +/* 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 <sys/uio.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/select.h> +#include <osmocom/core/write_queue.h> +#include <osmocom/gsm/gsm_utils.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/gsm_data.h> + +#include <nrw/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 & OSMO_FD_READ) + queue->read_cb(fd); + + if (what & OSMO_FD_EXCEPT) + queue->except_cb(fd); + + if (what & OSMO_FD_WRITE) { + struct iovec iov[5]; + struct msgb *msg, *tmp; + int written, count = 0; + + fd->when &= ~OSMO_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 |= OSMO_FD_WRITE; + return 0; + } + + written = writev(fd->fd, iov, count); + if (written < 0) { + /* nothing written?! */ + if (!llist_empty(&queue->msg_queue)) + fd->when |= OSMO_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 |= OSMO_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)); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "failed to read from fd: %s\n", strerror(errno)); + /* N. B: we do not abort to let the cycle below cleanup allocated memory properly, + the return value is ignored by the caller anyway. + TODO: use libexplain's explain_readv() to provide detailed error description */ + count = 0; + } else + count = rc / prim_size; + + for (i = 0; i < count; ++i) { + msgb_put(msg[i], prim_size); + read_dispatch_one(ofd->data, msg[i], ofd->priv_nr); + } + + for (i = count; i < ARRAY_SIZE(iov); ++i) + msgb_free(msg[i]); + + return 1; +} + +/* callback when we can write to one of the l1 msg_queue devices */ +static int l1fd_write_cb(struct osmo_fd *ofd, struct msgb *msg) +{ + int rc; + + rc = write(ofd->fd, msg->l1h, msgb_l1len(msg)); + if (rc < 0) { + LOGP(DL1C, LOGL_ERROR, "error writing to L1 msg_queue: %s\n", + strerror(errno)); + return rc; + } else if (rc < msg->len) { + LOGP(DL1C, LOGL_ERROR, "short write to L1 msg_queue: " + "%u < %u\n", rc, msg->len); + return -EIO; + } + + return 0; +} + +int l1if_transport_open(int q, struct lc15l1_hdl *hdl) +{ + struct phy_link *plink = hdl->phy_inst->phy_link; + int rc; + char buf[PATH_MAX]; + + /* Step 1: Open all msg_queue file descriptors */ + struct osmo_fd *read_ofd = &hdl->read_ofd[q]; + struct osmo_wqueue *wq = &hdl->write_q[q]; + struct osmo_fd *write_ofd = &hdl->write_q[q].bfd; + + snprintf(buf, sizeof(buf)-1, "%s%d", rd_devnames[q], plink->num); + buf[sizeof(buf)-1] = '\0'; + + rc = open(buf, O_RDONLY); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "unable to open msg_queue %s: %s\n", + buf, strerror(errno)); + return rc; + } + osmo_fd_setup(read_ofd, rc, OSMO_FD_READ, l1if_fd_cb, hdl, q); + rc = osmo_fd_register(read_ofd); + if (rc < 0) { + close(read_ofd->fd); + read_ofd->fd = -1; + return rc; + } + + snprintf(buf, sizeof(buf)-1, "%s%d", wr_devnames[q], plink->num); + buf[sizeof(buf)-1] = '\0'; + + rc = open(buf, O_WRONLY); + if (rc < 0) { + LOGP(DL1C, LOGL_FATAL, "unable to open msg_queue %s: %s\n", + buf, strerror(errno)); + goto out_read; + } + osmo_wqueue_init(wq, 10); + wq->write_cb = l1fd_write_cb; + osmo_fd_setup(write_ofd, rc, OSMO_FD_WRITE, wqueue_vector_cb, hdl, q); + rc = osmo_fd_register(write_ofd); + if (rc < 0) { + close(write_ofd->fd); + write_ofd->fd = -1; + goto out_read; + } + + return 0; + +out_read: + close(hdl->read_ofd[q].fd); + osmo_fd_unregister(&hdl->read_ofd[q]); + + return rc; +} + +int l1if_transport_close(int q, struct lc15l1_hdl *hdl) +{ + struct osmo_fd *read_ofd = &hdl->read_ofd[q]; + struct osmo_fd *write_ofd = &hdl->write_q[q].bfd; + + osmo_fd_unregister(read_ofd); + close(read_ofd->fd); + read_ofd->fd = -1; + + osmo_fd_unregister(write_ofd); + close(write_ofd->fd); + write_ofd->fd = -1; + + return 0; +} diff --git a/src/osmo-bts-lc15/lc15bts.c b/src/osmo-bts-lc15/lc15bts.c new file mode 100644 index 00000000..0ba89a11 --- /dev/null +++ b/src/osmo-bts-lc15/lc15bts.c @@ -0,0 +1,353 @@ +/* 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; +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) + case Litecell15_PrimId_IsAliveReq: return L1P_T_REQ; + case Litecell15_PrimId_IsAliveCnf: return L1P_T_CONF; + case Litecell15_PrimId_SetMaxCellSizeReq: return L1P_T_REQ; + case Litecell15_PrimId_SetMaxCellSizeCnf: return L1P_T_CONF; + case Litecell15_PrimId_SetC0IdleSlotPowerReductionReq: return L1P_T_REQ; + case Litecell15_PrimId_SetC0IdleSlotPowerReductionCnf: return L1P_T_CONF; +#endif + 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" }, +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) + { Litecell15_PrimId_IsAliveReq, "IS-ALIVE.req" }, + { Litecell15_PrimId_IsAliveCnf, "IS-ALIVE-CNF.cnf" }, + { Litecell15_PrimId_SetMaxCellSizeReq, "SET-MAX-CELL-SIZE.req" }, + { Litecell15_PrimId_SetMaxCellSizeCnf, "SET-MAX-CELL-SIZE.cnf" }, + { Litecell15_PrimId_SetC0IdleSlotPowerReductionReq, "SET-C0-IDLE-PWR-RED.req" }, + { Litecell15_PrimId_SetC0IdleSlotPowerReductionCnf, "SET-C0-IDLE-PWR-RED.cnf" }, +#endif + { 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; +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) + case Litecell15_PrimId_IsAliveReq: return Litecell15_PrimId_IsAliveCnf; + case Litecell15_PrimId_SetMaxCellSizeReq: return Litecell15_PrimId_SetMaxCellSizeCnf; + case Litecell15_PrimId_SetC0IdleSlotPowerReductionReq: return Litecell15_PrimId_SetC0IdleSlotPowerReductionCnf; +#endif + default: return -1; // Weak + } +} + +const struct value_string lc15bts_l1sapi_names[GsmL1_Sapi_NUM+1] = { + { GsmL1_Sapi_Idle, "IDLE" }, + { GsmL1_Sapi_Fcch, "FCCH" }, + { GsmL1_Sapi_Sch, "SCH" }, + { GsmL1_Sapi_Sacch, "SACCH" }, + { GsmL1_Sapi_Sdcch, "SDCCH" }, + { GsmL1_Sapi_Bcch, "BCCH" }, + { GsmL1_Sapi_Pch, "PCH" }, + { GsmL1_Sapi_Agch, "AGCH" }, + { GsmL1_Sapi_Cbch, "CBCH" }, + { GsmL1_Sapi_Rach, "RACH" }, + { GsmL1_Sapi_TchF, "TCH/F" }, + { GsmL1_Sapi_FacchF, "FACCH/F" }, + { GsmL1_Sapi_TchH, "TCH/H" }, + { GsmL1_Sapi_FacchH, "FACCH/H" }, + { GsmL1_Sapi_Nch, "NCH" }, + { GsmL1_Sapi_Pdtch, "PDTCH" }, + { GsmL1_Sapi_Pacch, "PACCH" }, + { GsmL1_Sapi_Pbcch, "PBCCH" }, + { GsmL1_Sapi_Pagch, "PAGCH" }, + { GsmL1_Sapi_Ppch, "PPCH" }, + { GsmL1_Sapi_Pnch, "PNCH" }, + { GsmL1_Sapi_Ptcch, "PTCCH" }, + { GsmL1_Sapi_Prach, "PRACH" }, + { 0, NULL } +}; + +const struct value_string lc15bts_l1status_names[GSML1_STATUS_NUM+1] = { + { GsmL1_Status_Success, "Success" }, + { GsmL1_Status_Generic, "Generic error" }, + { GsmL1_Status_NoMemory, "Not enough memory" }, + { GsmL1_Status_Timeout, "Timeout" }, + { GsmL1_Status_InvalidParam, "Invalid parameter" }, + { GsmL1_Status_Busy, "Resource busy" }, + { GsmL1_Status_NoRessource, "No more resources" }, + { GsmL1_Status_Uninitialized, "Trying to use uninitialized resource" }, + { GsmL1_Status_NullInterface, "Trying to call a NULL interface" }, + { GsmL1_Status_NullFctnPtr, "Trying to call a NULL function ptr" }, + { GsmL1_Status_BadCrc, "Bad CRC" }, + { GsmL1_Status_BadUsf, "Bad USF" }, + { GsmL1_Status_InvalidCPS, "Invalid CPS field" }, + { GsmL1_Status_UnexpectedBurst, "Unexpected burst" }, + { GsmL1_Status_UnavailCodec, "AMR codec is unavailable" }, + { GsmL1_Status_CriticalError, "Critical error" }, + { GsmL1_Status_OverheatError, "Overheat error" }, + { GsmL1_Status_DeviceError, "Device error" }, + { GsmL1_Status_FacchError, "FACCH / TCH order error" }, + { GsmL1_Status_AlreadyDeactivated, "Lchan already deactivated" }, + { GsmL1_Status_TxBurstFifoOvrn, "FIFO overrun" }, + { GsmL1_Status_TxBurstFifoUndr, "FIFO underrun" }, + { GsmL1_Status_NotSynchronized, "Not synchronized" }, + { GsmL1_Status_Unsupported, "Unsupported feature" }, + { GsmL1_Status_ClockError, "System clock error" }, + { 0, NULL } +}; + +const struct value_string lc15bts_tracef_names[29] = { + { DBG_DEBUG, "DEBUG" }, + { DBG_L1WARNING, "L1_WARNING" }, + { DBG_ERROR, "ERROR" }, + { DBG_L1RXMSG, "L1_RX_MSG" }, + { DBG_L1RXMSGBYTE, "L1_RX_MSG_BYTE" }, + { DBG_L1TXMSG, "L1_TX_MSG" }, + { DBG_L1TXMSGBYTE, "L1_TX_MSG_BYTE" }, + { DBG_MPHCNF, "MPH_CNF" }, + { DBG_MPHIND, "MPH_IND" }, + { DBG_MPHREQ, "MPH_REQ" }, + { DBG_PHIND, "PH_IND" }, + { DBG_PHREQ, "PH_REQ" }, + { DBG_PHYRF, "PHY_RF" }, + { DBG_PHYRFMSGBYTE, "PHY_MSG_BYTE" }, + { DBG_MODE, "MODE" }, + { DBG_TDMAINFO, "TDMA_INFO" }, + { DBG_BADCRC, "BAD_CRC" }, + { DBG_PHINDBYTE, "PH_IND_BYTE" }, + { DBG_PHREQBYTE, "PH_REQ_BYTE" }, + { DBG_DEVICEMSG, "DEVICE_MSG" }, + { DBG_RACHINFO, "RACH_INFO" }, + { DBG_LOGCHINFO, "LOG_CH_INFO" }, + { DBG_MEMORY, "MEMORY" }, + { DBG_PROFILING, "PROFILING" }, + { DBG_TESTCOMMENT, "TEST_COMMENT" }, + { DBG_TEST, "TEST" }, + { DBG_STATUS, "STATUS" }, + { 0, NULL } +}; + +const struct value_string lc15bts_tracef_docs[29] = { + { DBG_DEBUG, "Debug Region" }, + { DBG_L1WARNING, "L1 Warning Region" }, + { DBG_ERROR, "Error Region" }, + { DBG_L1RXMSG, "L1_RX_MSG Region" }, + { DBG_L1RXMSGBYTE, "L1_RX_MSG_BYTE Region" }, + { DBG_L1TXMSG, "L1_TX_MSG Region" }, + { DBG_L1TXMSGBYTE, "L1_TX_MSG_BYTE Region" }, + { DBG_MPHCNF, "MphConfirmation Region" }, + { DBG_MPHIND, "MphIndication Region" }, + { DBG_MPHREQ, "MphRequest Region" }, + { DBG_PHIND, "PhIndication Region" }, + { DBG_PHREQ, "PhRequest Region" }, + { DBG_PHYRF, "PhyRF Region" }, + { DBG_PHYRFMSGBYTE, "PhyRF Message Region" }, + { DBG_MODE, "Mode Region" }, + { DBG_TDMAINFO, "TDMA Info Region" }, + { DBG_BADCRC, "Bad CRC Region" }, + { DBG_PHINDBYTE, "PH_IND_BYTE" }, + { DBG_PHREQBYTE, "PH_REQ_BYTE" }, + { DBG_DEVICEMSG, "Device Message Region" }, + { DBG_RACHINFO, "RACH Info" }, + { DBG_LOGCHINFO, "LOG_CH_INFO" }, + { DBG_MEMORY, "Memory Region" }, + { DBG_PROFILING, "Profiling Region" }, + { DBG_TESTCOMMENT, "Test Comments" }, + { DBG_TEST, "Test Region" }, + { DBG_STATUS, "Status Region" }, + { 0, NULL } +}; + +const struct value_string lc15bts_tch_pl_names[] = { + { GsmL1_TchPlType_NA, "N/A" }, + { GsmL1_TchPlType_Fr, "FR" }, + { GsmL1_TchPlType_Hr, "HR" }, + { GsmL1_TchPlType_Efr, "EFR" }, + { GsmL1_TchPlType_Amr, "AMR(IF2)" }, + { GsmL1_TchPlType_Amr_SidBad, "AMR(SID BAD)" }, + { GsmL1_TchPlType_Amr_Onset, "AMR(ONSET)" }, + { GsmL1_TchPlType_Amr_Ratscch, "AMR(RATSCCH)" }, + { GsmL1_TchPlType_Amr_SidUpdateInH, "AMR(SID_UPDATE INH)" }, + { GsmL1_TchPlType_Amr_SidFirstP1, "AMR(SID_FIRST P1)" }, + { GsmL1_TchPlType_Amr_SidFirstP2, "AMR(SID_FIRST P2)" }, + { GsmL1_TchPlType_Amr_SidFirstInH, "AMR(SID_FIRST INH)" }, + { GsmL1_TchPlType_Amr_RatscchMarker, "AMR(RATSCCH MARK)" }, + { GsmL1_TchPlType_Amr_RatscchData, "AMR(RATSCCH DATA)" }, + { 0, NULL } +}; + +const struct value_string lc15bts_dir_names[] = { + { GsmL1_Dir_TxDownlink, "TxDL" }, + { GsmL1_Dir_TxUplink, "TxUL" }, + { GsmL1_Dir_RxUplink, "RxUL" }, + { GsmL1_Dir_RxDownlink, "RxDL" }, + { GsmL1_Dir_TxDownlink|GsmL1_Dir_RxUplink, "BOTH" }, + { 0, NULL } +}; + +const struct value_string lc15bts_chcomb_names[] = { + { GsmL1_LogChComb_0, "dummy" }, + { GsmL1_LogChComb_I, "tch_f" }, + { GsmL1_LogChComb_II, "tch_h" }, + { GsmL1_LogChComb_IV, "ccch" }, + { GsmL1_LogChComb_V, "ccch_sdcch4" }, + { GsmL1_LogChComb_VII, "sdcch8" }, + { GsmL1_LogChComb_XIII, "pdtch" }, + { 0, NULL } +}; + +const uint8_t pdch_msu_size[_NUM_PDCH_CS] = { + [PDCH_CS_1] = 23, + [PDCH_CS_2] = 34, + [PDCH_CS_3] = 40, + [PDCH_CS_4] = 54, + [PDCH_MCS_1] = 27, + [PDCH_MCS_2] = 33, + [PDCH_MCS_3] = 42, + [PDCH_MCS_4] = 49, + [PDCH_MCS_5] = 60, + [PDCH_MCS_6] = 78, + [PDCH_MCS_7] = 118, + [PDCH_MCS_8] = 142, + [PDCH_MCS_9] = 154 +}; diff --git a/src/osmo-bts-lc15/lc15bts.h b/src/osmo-bts-lc15/lc15bts.h new file mode 100644 index 00000000..ecfeb3da --- /dev/null +++ b/src/osmo-bts-lc15/lc15bts.h @@ -0,0 +1,101 @@ +#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 lc15_diversity_mode{ + LC15_DIVERSITY_SISO_A = 0, + LC15_DIVERSITY_SISO_B, + LC15_DIVERSITY_MRC, +}; + +enum lc15_pedestal_mode{ + LC15_PEDESTAL_OFF = 0, + LC15_PEDESTAL_ON, +}; + +enum lc15_led_control_mode{ + LC15_LED_CONTROL_BTS = 0, + LC15_LED_CONTROL_EXT, +}; + +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) +enum lc15_auto_pwr_adjust_mode{ + LC15_TX_PWR_ADJ_NONE = 0, + LC15_TX_PWR_ADJ_AUTO, +}; +#endif + +enum l1prim_type lc15bts_get_l1prim_type(GsmL1_PrimId_t id); +extern 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); +extern const struct value_string lc15bts_sysprim_names[Litecell15_PrimId_NUM+1]; +Litecell15_PrimId_t lc15bts_get_sysprim_conf(Litecell15_PrimId_t id); + +extern const struct value_string lc15bts_l1sapi_names[GsmL1_Sapi_NUM+1]; +extern const struct value_string lc15bts_l1status_names[GSML1_STATUS_NUM+1]; + +extern const struct value_string lc15bts_tracef_names[29]; +extern const struct value_string lc15bts_tracef_docs[29]; + +extern const struct value_string lc15bts_tch_pl_names[15]; + +extern const struct value_string lc15bts_clksrc_names[10]; + +extern 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 +}; + +extern const uint8_t pdch_msu_size[_NUM_PDCH_CS]; + +/* LC15 default parameters */ +#define LC15_BTS_MAX_CELL_SIZE_DEFAULT 166 /* 166 qbits is default value */ +#define LC15_BTS_DIVERSITY_MODE_DEFAULT 0 /* SISO-A is default mode */ +#define LC15_BTS_PEDESTAL_MODE_DEFAULT 0 /* Unused TS is off by default */ +#define LC15_BTS_LED_CTRL_MODE_DEFAULT 0 /* LED is controlled by BTS by default */ +#define LC15_BTS_RTP_DRIFT_THRES_DEFAULT 0 /* Default RTP drift threshold is 0 ms (disabled) */ +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) +#define LC15_BTS_DSP_ALIVE_TMR_DEFAULT 5 /* Default DSP alive timer is 5 seconds */ +#define LC15_BTS_TX_PWR_ADJ_DEFAULT 0 /* Default Tx power auto adjustment is none */ +#define LC15_BTS_TX_RED_PWR_8PSK_DEFAULT 0 /* Default 8-PSK maximum power level is 0 dB */ +#define LC15_BTS_TX_C0_IDLE_RED_PWR_DEFAULT 0 /* Default C0 idle slot reduction power level is 0 dB */ +#endif + +#endif /* LC15BTS_H */ diff --git a/src/osmo-bts-lc15/lc15bts_vty.c b/src/osmo-bts-lc15/lc15bts_vty.c new file mode 100644 index 00000000..4a9d7908 --- /dev/null +++ b/src/osmo-bts-lc15/lc15bts_vty.c @@ -0,0 +1,638 @@ +/* VTY interface for NuRAN Wireless Litecell 1.5 */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org> + * + * Based on sysmoBTS: + * (C) 2011 by Harald Welte <laforge@gnumonks.org> + * (C) 2012,2013 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> +#include <ctype.h> + +#include <arpa/inet.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> +#include <osmocom/core/rate_ctr.h> + +#include <osmocom/gsm/tlv.h> + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/command.h> +#include <osmocom/vty/misc.h> + +#include <osmocom/ctrl/control_cmd.h> +#include <osmo-bts/signal.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/bts.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/phy_link.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/vty.h> +#include <osmo-bts/rsl.h> + +#include "lc15bts.h" +#include "l1_if.h" +#include "utils.h" + +extern int lchan_activate(struct gsm_lchan *lchan); +extern int rsl_tx_preproc_meas_res(struct gsm_lchan *lchan); + +#define TRX_STR "Transceiver related commands\n" "TRX number\n" + +#define SHOW_TRX_STR \ + SHOW_STR \ + TRX_STR +#define DSP_TRACE_F_STR "DSP Trace Flag\n" + +static const struct value_string lc15_diversity_mode_strs[] = { + { LC15_DIVERSITY_SISO_A, "siso-a" }, + { LC15_DIVERSITY_SISO_B, "siso-b" }, + { LC15_DIVERSITY_MRC, "mrc" }, + { 0, NULL } +}; + +static const struct value_string lc15_pedestal_mode_strs[] = { + { LC15_PEDESTAL_OFF, "off" }, + { LC15_PEDESTAL_ON, "on" }, + { 0, NULL } +}; + +static const struct value_string lc15_led_mode_strs[] = { + { LC15_LED_CONTROL_BTS, "bts" }, + { LC15_LED_CONTROL_EXT, "external" }, + { 0, NULL } +}; + +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) +static const struct value_string lc15_auto_adj_pwr_strs[] = { + { LC15_TX_PWR_ADJ_NONE, "none" }, + { LC15_TX_PWR_ADJ_AUTO, "auto" }, + { 0, NULL } +}; +#endif + +/* configuration */ + +DEFUN(cfg_phy_cal_path, cfg_phy_cal_path_cmd, + "trx-calibration-path PATH", + "Set the path name to TRX calibration data\n" "Path name\n") +{ + struct phy_instance *pinst = vty->index; + + if (pinst->u.lc15.calib_path) + talloc_free(pinst->u.lc15.calib_path); + + pinst->u.lc15.calib_path = talloc_strdup(pinst, argv[0]); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_dsp_trace_f, cfg_phy_dsp_trace_f_cmd, + "HIDDEN", TRX_STR) +{ + struct phy_instance *pinst = vty->index; + unsigned int flag; + + flag = get_string_value(lc15bts_tracef_names, argv[0]); + pinst->u.lc15.dsp_trace_f |= flag; + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_no_dsp_trace_f, cfg_phy_no_dsp_trace_f_cmd, + "HIDDEN", NO_STR TRX_STR) +{ + struct phy_instance *pinst = vty->index; + unsigned int flag; + + flag = get_string_value(lc15bts_tracef_names, argv[0]); + pinst->u.lc15.dsp_trace_f &= ~flag; + + return CMD_SUCCESS; +} + + +/* runtime */ + +DEFUN(show_dsp_trace_f, show_dsp_trace_f_cmd, + "show trx <0-0> dsp-trace-flags", + SHOW_TRX_STR "Display the current setting of the DSP trace flags") +{ + int trx_nr = atoi(argv[0]); + struct gsm_bts_trx *trx = gsm_bts_trx_num(g_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 phy_nr = atoi(argv[0]); + struct phy_instance *pinst; + struct lc15l1_hdl *fl1h; + unsigned int flag ; + + pinst = vty_get_phy_instance(vty, phy_nr, 0); + if (!pinst) + return CMD_WARNING; + + fl1h = pinst->u.lc15.hdl; + 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 phy_nr = atoi(argv[0]); + struct phy_instance *pinst; + struct lc15l1_hdl *fl1h; + unsigned int flag ; + + pinst = vty_get_phy_instance(vty, phy_nr, 0); + if (!pinst) + return CMD_WARNING; + + fl1h = pinst->u.lc15.hdl; + 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 phy <0-1> instance <0-0> system-information", + SHOW_TRX_STR "Display information about system\n") +{ + int phy_nr = atoi(argv[0]); + int inst_nr = atoi(argv[1]); + struct phy_link *plink = phy_link_by_num(phy_nr); + struct phy_instance *pinst; + struct lc15l1_hdl *fl1h; + int i; + + if (!plink) { + vty_out(vty, "Cannot find PHY link %u%s", + phy_nr, VTY_NEWLINE); + return CMD_WARNING; + } + pinst = phy_instance_by_num(plink, inst_nr); + if (!plink) { + vty_out(vty, "Cannot find PHY instance %u%s", + phy_nr, VTY_NEWLINE); + return CMD_WARNING; + } + fl1h = pinst->u.lc15.hdl; + + vty_out(vty, "DSP Version: %u.%u.%u, FPGA Version: %u.%u.%u%s", + fl1h->hw_info.dsp_version[0], + fl1h->hw_info.dsp_version[1], + fl1h->hw_info.dsp_version[2], + fl1h->hw_info.fpga_version[0], + fl1h->hw_info.fpga_version[1], + fl1h->hw_info.fpga_version[2], VTY_NEWLINE); + + vty_out(vty, "GSM Band Support: "); + for (i = 0; i < sizeof(fl1h->hw_info.band_support); i++) { + if (fl1h->hw_info.band_support & (1 << i)) + vty_out(vty, "%s ", gsm_band_name(1 << i)); + } + vty_out(vty, "%s", VTY_NEWLINE); + vty_out(vty, "Min Tx Power: %d dBm%s", fl1h->phy_inst->u.lc15.minTxPower, VTY_NEWLINE); + vty_out(vty, "Max Tx Power: %d dBm%s", fl1h->phy_inst->u.lc15.maxTxPower, VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(activate_lchan, activate_lchan_cmd, + "trx <0-0> <0-7> (activate|deactivate) <0-7>", + TRX_STR + "Timeslot number\n" + "Activate Logical Channel\n" + "Deactivate Logical Channel\n" + "Logical Channel Number\n" ) +{ + int trx_nr = atoi(argv[0]); + int ts_nr = atoi(argv[1]); + int lchan_nr = atoi(argv[3]); + struct gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr); + struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; + struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; + + if (!strcmp(argv[2], "activate")) + lchan_activate(lchan); + else + lchan_deactivate(lchan); + + return CMD_SUCCESS; +} + +DEFUN(set_tx_power, set_tx_power_cmd, + "trx nr <0-1> tx-power <-110-100>", + TRX_STR + "TRX number \n" + "Set transmit power (override BSC)\n" + "Transmit power in dBm\n") +{ + int trx_nr = atoi(argv[0]); + int power = atoi(argv[1]); + struct gsm_bts_trx *trx = gsm_bts_trx_num(g_bts, trx_nr); + + power_ramp_start(trx, to_mdB(power), 1, NULL); + + 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(g_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(g_bts, trx_nr); + struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr]; + struct gsm_lchan *lchan = &ts->lchan[lchan_nr]; + + lchan->loopback = 0; + + return CMD_SUCCESS; +} + +DEFUN(cfg_trx_nominal_power, cfg_trx_nominal_power_cmd, + "nominal-tx-power <0-40>", + "Set the nominal transmit output power in dBm\n" + "Nominal transmit output power level in dBm\n") +{ + int nominal_power = atoi(argv[0]); + struct gsm_bts_trx *trx = vty->index; + + if (( nominal_power > 40 ) || ( nominal_power < 0 )) { + vty_out(vty, "Nominal Tx power level must be between 0 and 40 dBm (%d) %s", + nominal_power, VTY_NEWLINE); + return CMD_WARNING; + } + + trx->nominal_power = nominal_power; + trx->power_params.trx_p_max_out_mdBm = to_mdB(nominal_power); + + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_max_cell_size, cfg_phy_max_cell_size_cmd, + "max-cell-size <0-166>", + "Set the maximum cell size in qbits\n") +{ + struct phy_instance *pinst = vty->index; + int cell_size = (uint8_t)atoi(argv[0]); + + if (( cell_size > 166 ) || ( cell_size < 0 )) { + vty_out(vty, "Max cell size must be between 0 and 166 qbits (%d) %s", + cell_size, VTY_NEWLINE); + return CMD_WARNING; + } + + pinst->u.lc15.max_cell_size = (uint8_t)cell_size; + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_diversity_mode, cfg_phy_diversity_mode_cmd, + "diversity-mode (siso-a|siso-b|mrc)", + "Set reception diversity mode \n" + "Reception diversity mode can be (siso-a, siso-b, mrc)\n") +{ + struct phy_instance *pinst = vty->index; + int val = get_string_value(lc15_diversity_mode_strs, argv[0]); + + if((val < LC15_DIVERSITY_SISO_A) || (val > LC15_DIVERSITY_MRC)) { + vty_out(vty, "Invalid reception diversity mode %d%s", val, VTY_NEWLINE); + return CMD_WARNING; + } + + pinst->u.lc15.diversity_mode = (uint8_t)val; + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_pedestal_mode, cfg_phy_pedestal_mode_cmd, + "pedestal-mode (on|off)", + "Set unused time-slot transmission in pedestal mode\n" + "Transmission pedestal mode can be (off, on)\n") +{ + struct phy_instance *pinst = vty->index; + int val = get_string_value(lc15_pedestal_mode_strs, argv[0]); + + if((val < LC15_PEDESTAL_OFF) || (val > LC15_PEDESTAL_ON)) { + vty_out(vty, "Invalid unused time-slot transmission mode %d%s", val, VTY_NEWLINE); + return CMD_WARNING; + } + + pinst->u.lc15.pedestal_mode = (uint8_t)val; + return CMD_SUCCESS; +} + +DEFUN(cfg_bts_led_mode, cfg_bts_led_mode_cmd, + "led-control-mode (bts|external)", + "Set LED controlled by BTS or external software\n" + "LED can be controlled by (bts, external)\n") +{ + struct gsm_bts *bts = vty->index; + int val = get_string_value(lc15_led_mode_strs, argv[0]); + + if((val < LC15_LED_CONTROL_BTS) || (val > LC15_LED_CONTROL_EXT)) { + vty_out(vty, "Invalid LED control mode %d%s", val, VTY_NEWLINE); + return CMD_WARNING; + } + + struct bts_lc15_priv *bts_lc15 = bts->model_priv; + bts_lc15->led_ctrl_mode = (uint8_t)val; + return CMD_SUCCESS; +} + +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) +DEFUN(cfg_phy_dsp_alive_timer, cfg_phy_dsp_alive_timer_cmd, + "dsp-alive-period <0-60>", + "Set DSP alive timer period in second\n") +{ + struct phy_instance *pinst = vty->index; + uint8_t period = (uint8_t)atoi(argv[0]); + + if (( period > 60 ) || ( period < 0 )) { + vty_out(vty, "DSP heart beat alive timer period must be between 0 and 60 seconds (%d) %s", + period, VTY_NEWLINE); + return CMD_WARNING; + } + + pinst->u.lc15.dsp_alive_period = period; + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_auto_tx_pwr_adj, cfg_phy_auto_tx_pwr_adj_cmd, + "pwr-adj-mode (none|auto)", + "Set output power adjustment mode\n") +{ + struct phy_instance *pinst = vty->index; + int val = get_string_value(lc15_auto_adj_pwr_strs, argv[0]); + + if((val < LC15_TX_PWR_ADJ_NONE) || (val > LC15_TX_PWR_ADJ_AUTO)) { + vty_out(vty, "Invalid output power adjustment mode %d%s", val, VTY_NEWLINE); + return CMD_WARNING; + } + + pinst->u.lc15.tx_pwr_adj_mode = (uint8_t)val; + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_tx_red_pwr_8psk, cfg_phy_tx_red_pwr_8psk_cmd, + "tx-red-pwr-8psk <0-40>", + "Set reduction output power for 8-PSK scheme in dB unit\n") +{ + struct phy_instance *pinst = vty->index; + int val = atoi(argv[0]); + + if ((val > 40) || (val < 0)) { + vty_out(vty, "Reduction Tx power level must be between 0 and 40 dB (%d) %s", + val, VTY_NEWLINE); + return CMD_WARNING; + } + + pinst->u.lc15.tx_pwr_red_8psk = (uint8_t)val; + return CMD_SUCCESS; +} + +DEFUN(cfg_phy_c0_idle_red_pwr, cfg_phy_c0_idle_red_pwr_cmd, + "c0-idle-red-pwr <0-40>", + "Set reduction output power for C0 idle slot in dB unit\n") +{ + struct phy_instance *pinst = vty->index; + int val = atoi(argv[0]); + + if ((val > 40) || (val < 0)) { + vty_out(vty, "Reduction Tx power level must be between 0 and 40 dB (%d) %s", + val, VTY_NEWLINE); + return CMD_WARNING; + } + + pinst->u.lc15.tx_c0_idle_pwr_red = (uint8_t)val; + return CMD_SUCCESS; +} +#endif + +DEFUN(cfg_bts_rtp_drift_threshold, cfg_bts_rtp_drift_threshold_cmd, + "rtp-drift-threshold <0-10000>", + "RTP parameters\n" + "RTP timestamp drift threshold in ms\n") +{ + struct gsm_bts *bts = vty->index; + + struct bts_lc15_priv *bts_lc15 = bts->model_priv; + bts_lc15->rtp_drift_thres_ms = (unsigned int) atoi(argv[0]); + + return CMD_SUCCESS; +} + +void bts_model_config_write_bts(struct vty *vty, const struct gsm_bts *bts) +{ + const struct bts_lc15_priv *bts_lc15 = bts->model_priv; + vty_out(vty, " led-control-mode %s%s", + get_value_string(lc15_led_mode_strs, bts_lc15->led_ctrl_mode), VTY_NEWLINE); + + vty_out(vty, " rtp-drift-threshold %d%s", + bts_lc15->rtp_drift_thres_ms, VTY_NEWLINE); + +} + +void bts_model_config_write_trx(struct vty *vty, const struct gsm_bts_trx *trx) +{ + vty_out(vty, " nominal-tx-power %d%s", trx->nominal_power,VTY_NEWLINE); +} + +void bts_model_config_write_phy(struct vty *vty, const struct phy_link *plink) +{ +} + +void bts_model_config_write_phy_inst(struct vty *vty, const struct phy_instance *pinst) +{ + int i; + + for (i = 0; i < 32; i++) { + if (pinst->u.lc15.dsp_trace_f & (1 << i)) { + const char *name; + name = get_value_string(lc15bts_tracef_names, (1 << i)); + vty_out(vty, " dsp-trace-flag %s%s", name, + VTY_NEWLINE); + } + } + if (pinst->u.lc15.calib_path) + vty_out(vty, " trx-calibration-path %s%s", + pinst->u.lc15.calib_path, VTY_NEWLINE); + + vty_out(vty, " max-cell-size %d%s", + pinst->u.lc15.max_cell_size, VTY_NEWLINE); + + vty_out(vty, " diversity-mode %s%s", + get_value_string(lc15_diversity_mode_strs, pinst->u.lc15.diversity_mode), VTY_NEWLINE); + + vty_out(vty, " pedestal-mode %s%s", + get_value_string(lc15_pedestal_mode_strs, pinst->u.lc15.pedestal_mode) , VTY_NEWLINE); + +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) + vty_out(vty, " dsp-alive-period %d%s", + pinst->u.lc15.dsp_alive_period, VTY_NEWLINE); + + vty_out(vty, " pwr-adj-mode %s%s", + get_value_string(lc15_auto_adj_pwr_strs, pinst->u.lc15.tx_pwr_adj_mode), VTY_NEWLINE); + + vty_out(vty, " tx-red-pwr-8psk %d%s", + pinst->u.lc15.tx_pwr_red_8psk, VTY_NEWLINE); + + vty_out(vty, " c0-idle-red-pwr %d%s", + pinst->u.lc15.tx_c0_idle_pwr_red, VTY_NEWLINE); +#endif +} + +int bts_model_vty_init(void *ctx) +{ + /* runtime-patch the command strings with debug levels */ + dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(ctx, lc15bts_tracef_names, + "phy <0-0> dsp-trace-flag (", + "|",")", VTY_DO_LOWER); + dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(ctx, lc15bts_tracef_docs, + TRX_STR DSP_TRACE_F_STR, + "\n", "", 0); + + no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(ctx, lc15bts_tracef_names, + "no phy <0-0> dsp-trace-flag (", + "|",")", VTY_DO_LOWER); + no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(ctx, lc15bts_tracef_docs, + NO_STR TRX_STR DSP_TRACE_F_STR, + "\n", "", 0); + + cfg_phy_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(ctx, + lc15bts_tracef_names, + "dsp-trace-flag (", + "|",")", VTY_DO_LOWER); + cfg_phy_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(ctx, + lc15bts_tracef_docs, + DSP_TRACE_F_STR, + "\n", "", 0); + + cfg_phy_no_dsp_trace_f_cmd.string = vty_cmd_string_from_valstr(ctx, + lc15bts_tracef_names, + "no dsp-trace-flag (", + "|",")", VTY_DO_LOWER); + cfg_phy_no_dsp_trace_f_cmd.doc = vty_cmd_string_from_valstr(ctx, + lc15bts_tracef_docs, + NO_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(BTS_NODE, &cfg_bts_led_mode_cmd); + install_element(BTS_NODE, &cfg_bts_rtp_drift_threshold_cmd); + + install_element(TRX_NODE, &cfg_trx_nominal_power_cmd); + + install_element(PHY_INST_NODE, &cfg_phy_dsp_trace_f_cmd); + install_element(PHY_INST_NODE, &cfg_phy_no_dsp_trace_f_cmd); + install_element(PHY_INST_NODE, &cfg_phy_cal_path_cmd); + + install_element(PHY_INST_NODE, &cfg_phy_diversity_mode_cmd); + install_element(PHY_INST_NODE, &cfg_phy_pedestal_mode_cmd); + install_element(PHY_INST_NODE, &cfg_phy_max_cell_size_cmd); +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) + install_element(PHY_INST_NODE, &cfg_phy_dsp_alive_timer_cmd); + install_element(PHY_INST_NODE, &cfg_phy_auto_tx_pwr_adj_cmd); + install_element(PHY_INST_NODE, &cfg_phy_tx_red_pwr_8psk_cmd); + install_element(PHY_INST_NODE, &cfg_phy_c0_idle_red_pwr_cmd); +#endif + return 0; +} + +int bts_model_ctrl_cmds_install(struct gsm_bts *bts) +{ + return 0; +} diff --git a/src/osmo-bts-lc15/main.c b/src/osmo-bts-lc15/main.c new file mode 100644 index 00000000..985d7dba --- /dev/null +++ b/src/osmo-bts-lc15/main.c @@ -0,0 +1,226 @@ +/* Main program for NuRAN Wireless Litecell 1.5 BTS */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * Copyright (C) 2016 by Harald Welte <laforge@gnumonks.org> + * + * Based on sysmoBTS: + * (C) 2011-2013 by Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <getopt.h> +#include <limits.h> +#include <sys/signal.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sched.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/application.h> +#include <osmocom/vty/telnet_interface.h> +#include <osmocom/vty/logging.h> +#include <osmocom/vty/ports.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/vty.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/pcu_if.h> +#include <osmo-bts/l1sap.h> + +static int write_status_file(char *status_file, char *status_str) +{ + FILE *outf; + char tmp[PATH_MAX+1]; + + snprintf(tmp, sizeof(tmp)-1, "/var/run/osmo-bts/%s", status_file); + tmp[PATH_MAX-1] = '\0'; + + outf = fopen(tmp, "w"); + if (!outf) + return -1; + + fprintf(outf, "%s\n", status_str); + + fclose(outf); + + return 0; +} + +/*NTQD: Change how rx_nr is handle in multi-trx*/ +#define LC15BTS_RF_LOCK_PATH "/var/lock/bts_rf_lock" + +#include "utils.h" +#include "l1_if.h" +#include "hw_misc.h" +#include "misc/lc15bts_bid.h" + +unsigned int dsp_trace = 0x00000000; + +int bts_model_init(struct gsm_bts *bts) +{ + struct stat st; + + struct bts_lc15_priv *bts_lc15 = talloc(bts, struct bts_lc15_priv); + + bts->model_priv = bts_lc15; + bts->variant = BTS_OSMO_LITECELL15; + bts->support.ciphers = CIPHER_A5(1) | CIPHER_A5(2) | CIPHER_A5(3); + + /* specific default values for LC15 platform */ + bts_lc15->led_ctrl_mode = LC15_BTS_LED_CTRL_MODE_DEFAULT; + /* RTP drift threshold default */ + bts_lc15->rtp_drift_thres_ms = LC15_BTS_RTP_DRIFT_THRES_DEFAULT; + + if (stat(LC15BTS_RF_LOCK_PATH, &st) == 0) { + LOGP(DL1C, LOGL_NOTICE, "Not starting BTS due to RF_LOCK file present\n"); + exit(23); + } + + osmo_bts_set_feature(bts->features, BTS_FEAT_GPRS); + osmo_bts_set_feature(bts->features, BTS_FEAT_EGPRS); + osmo_bts_set_feature(bts->features, BTS_FEAT_OML_ALERTS); + osmo_bts_set_feature(bts->features, BTS_FEAT_AGCH_PCH_PROP); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_V1); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_H_V1); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_EFR); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_F_AMR); + osmo_bts_set_feature(bts->features, BTS_FEAT_SPEECH_H_AMR); + + bts_internal_flag_set(bts, BTS_INTERNAL_FLAG_MS_PWR_CTRL_DSP); + + return 0; +} + +int bts_model_trx_init(struct gsm_bts_trx *trx) +{ + trx->nominal_power = 40; + trx->power_params.trx_p_max_out_mdBm = to_mdB(trx->bts->c0->nominal_power); + return 0; +} + +void bts_model_phy_link_set_defaults(struct phy_link *plink) +{ +} + +void bts_model_phy_instance_set_defaults(struct phy_instance *pinst) +{ +} + +int bts_model_oml_estab(struct gsm_bts *bts) +{ + /* update status file */ + write_status_file("state", ""); + + return 0; +} + +void bts_update_status(enum bts_global_status which, int on) +{ + static uint64_t states = 0; + uint64_t old_states = states; + + if (on) + states |= (1ULL << which); + else + states &= ~(1ULL << which); + + LOGP(DL1C, LOGL_INFO, + "Set global status #%d to %d (%04llx -> %04llx)", + which, on, + (long long)old_states, (long long)states); +} + +void bts_model_print_help() +{ + printf( "\nModel specific options:\n" + " -w --hw-version Print the targeted HW Version\n" + " -M --pcu-direct Force PCU to access message queue for " + "PDCH dchannel directly\n" + " -p --dsp-trace Set DSP trace flags\n" + ); +} + +static void print_hwversion() +{ + printf(get_hwversion_desc()); + printf("\n"); +} + +int bts_model_handle_options(int argc, char **argv) +{ + int num_errors = 0; + + while (1) { + int option_idx = 0, c; + static const struct option long_options[] = { + { "dsp-trace", 1, 0, 'p' }, + { "hw-version", 0, 0, 'w' }, + { "pcu-direct", 0, 0, 'M' }, + { 0, 0, 0, 0 } + }; + + c = getopt_long(argc, argv, "p:wM", + long_options, &option_idx); + if (c == -1) + break; + + switch (c) { + case 'p': + dsp_trace = strtoul(optarg, NULL, 16); + break; + case 'M': + pcu_direct = 1; + break; + case 'w': + print_hwversion(); + exit(0); + break; + default: + num_errors++; + break; + } + } + + return num_errors; +} + +void bts_model_abis_close(struct gsm_bts *bts) +{ + /* write to status file */ + write_status_file("state", "ABIS DOWN"); + + /* for now, we simply terminate the program and re-spawn */ + bts_shutdown(bts, "Abis close"); +} + +int main(int argc, char **argv) +{ + /* create status file with initial state */ + write_status_file("state", "ABIS DOWN"); + + return bts_main(argc, argv); +} diff --git a/src/osmo-bts-lc15/misc/lc15bts_bid.c b/src/osmo-bts-lc15/misc/lc15bts_bid.c new file mode 100644 index 00000000..9284b62e --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_bid.c @@ -0,0 +1,162 @@ +/* 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 <stdbool.h> + +#include "lc15bts_bid.h" + +#define BOARD_REV_SYSFS "/var/lc15/platform/revision" +#define BOARD_OPT_SYSFS "/var/lc15/platform/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; + +static inline bool read_board(const char *src, const char *spec, void *dst) +{ + FILE *fp = fopen(src, "r"); + if (!fp) { + fprintf(stderr, "Failed to open %s due to '%s' error\n", src, strerror(errno)); + return false; + } + + if (fscanf(fp, spec, dst) != 1) { + fclose(fp); + fprintf(stderr, "Failed to read %s due to '%s' error\n", src, strerror(errno)); + return false; + } + fclose(fp); + return true; +} + +int lc15bts_rev_get(void) +{ + char rev; + + if (board_rev != -1) { + return board_rev; + } + + if (!read_board(BOARD_REV_SYSFS, "%c", &rev)) + return -1; + + board_rev = rev; + return board_rev; +} + +int lc15bts_model_get(void) +{ + int opt; + + if (board_option != -1) + return board_option; + + if (!read_board(BOARD_OPT_SYSFS, "%X", &opt)) + return -1; + + 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; +} + +const char* get_hwversion_desc() +{ + int rev; + int model; + size_t len; + static char model_name[64] = {0, }; + len = snprintf(model_name, sizeof(model_name), "NuRAN Litecell 1.5 BTS"); + + rev = lc15bts_rev_get(); + if (rev >= 0) { + len += snprintf(model_name + len, sizeof(model_name) - len, + " Rev %c", (char)rev); + } + + model = lc15bts_model_get(); + if (model >= 0) { + snprintf(model_name + len, sizeof(model_name) - len, + "%s (%05X)", model_name, model); + } + return model_name; +} diff --git a/src/osmo-bts-lc15/misc/lc15bts_bid.h b/src/osmo-bts-lc15/misc/lc15bts_bid.h new file mode 100644 index 00000000..a71fdd7e --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_bid.h @@ -0,0 +1,52 @@ +#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); +const char* get_hwversion_desc(); + +#endif diff --git a/src/osmo-bts-lc15/misc/lc15bts_bts.c b/src/osmo-bts-lc15/misc/lc15bts_bts.c new file mode 100644 index 00000000..0343e930 --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_bts.c @@ -0,0 +1,131 @@ +/* Copyright (C) 2016 by NuRAN Wireless <support@nuranwireless.com> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <sys/ioctl.h> +#include <net/if.h> +#include <netdb.h> +#include <ifaddrs.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include "lc15bts_mgr.h" +#include "lc15bts_bts.h" + +static int check_eth_status(char *dev_name) +{ + int fd, rc; + struct ifreq ifr; + + fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); + if (fd < 0) + return fd; + + memset(&ifr, 0, sizeof(ifr)); + memcpy(&ifr.ifr_name, dev_name, sizeof(ifr.ifr_name)); + rc = ioctl(fd, SIOCGIFFLAGS, &ifr); + close(fd); + + if (rc < 0) + return rc; + + if ((ifr.ifr_flags & IFF_UP) && (ifr.ifr_flags & IFF_RUNNING)) + return 0; + + return 1; +} + +void check_bts_led_pattern(uint8_t *led) +{ + FILE *fp; + char str[64] = "\0"; + int rc; + + /* check for existing of BTS state file */ + if ((fp = fopen("/var/run/osmo-bts/state", "r")) == NULL) { + led[BLINK_PATTERN_INIT] = 1; + return; + } + + /* check Ethernet interface status */ + rc = check_eth_status("eth0"); + if (rc > 0) { + LOGP(DTEMP, LOGL_DEBUG,"External link is DOWN\n"); + led[BLINK_PATTERN_EXT_LINK_MALFUNC] = 1; + fclose(fp); + return; + } + + /* check for BTS is still alive */ + if (system("pidof osmo-bts-lc15 > /dev/null")) { + LOGP(DTEMP, LOGL_DEBUG,"BTS process has stopped\n"); + led[BLINK_PATTERN_INT_PROC_MALFUNC] = 1; + fclose(fp); + return; + } + + /* check for BTS state */ + while (fgets(str, 64, fp) != NULL) { + LOGP(DTEMP, LOGL_DEBUG,"BTS state is %s\n", (strstr(str, "ABIS DOWN") != NULL) ? "DOWN" : "UP"); + if (strstr(str, "ABIS DOWN") != NULL) + led[BLINK_PATTERN_INT_PROC_MALFUNC] = 1; + } + fclose(fp); + + return; +} + +int check_sensor_led_pattern( struct lc15bts_mgr_instance *mgr, uint8_t *led) +{ + if(mgr->alarms.temp_high == 1) + led[BLINK_PATTERN_TEMP_HIGH] = 1; + + if(mgr->alarms.temp_max == 1) + led[BLINK_PATTERN_TEMP_MAX] = 1; + + if(mgr->alarms.supply_low == 1) + led[BLINK_PATTERN_SUPPLY_VOLT_LOW] = 1; + + if(mgr->alarms.supply_min == 1) + led[BLINK_PATTERN_SUPPLY_VOLT_MIN] = 1; + + if(mgr->alarms.vswr_high == 1) + led[BLINK_PATTERN_VSWR_HIGH] = 1; + + if(mgr->alarms.vswr_max == 1) + led[BLINK_PATTERN_VSWR_MAX] = 1; + + if(mgr->alarms.supply_pwr_high == 1) + led[BLINK_PATTERN_SUPPLY_PWR_HIGH] = 1; + + if(mgr->alarms.supply_pwr_max == 1) + led[BLINK_PATTERN_SUPPLY_PWR_MAX] = 1; + + if(mgr->alarms.supply_pwr_max2 == 1) + led[BLINK_PATTERN_SUPPLY_PWR_MAX2] = 1; + + if(mgr->alarms.pa_pwr_high == 1) + led[BLINK_PATTERN_PA_PWR_HIGH] = 1; + + if(mgr->alarms.pa_pwr_max == 1) + led[BLINK_PATTERN_PA_PWR_MAX] = 1; + + if(mgr->alarms.gps_fix_lost == 1) + led[BLINK_PATTERN_GPS_FIX_LOST] = 1; + + return 0; +} diff --git a/src/osmo-bts-lc15/misc/lc15bts_bts.h b/src/osmo-bts-lc15/misc/lc15bts_bts.h new file mode 100644 index 00000000..3918b870 --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_bts.h @@ -0,0 +1,21 @@ +#ifndef _LC15BTS_BTS_H_ +#define _LC15BTS_BTS_H_ + +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> +#include <ctype.h> +#include <string.h> +#include <sys/signal.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <osmo-bts/logging.h> + +/* public function prototypes */ +void check_bts_led_pattern(uint8_t *led); +int check_sensor_led_pattern( struct lc15bts_mgr_instance *mgr, uint8_t *led); + +#endif diff --git a/src/osmo-bts-lc15/misc/lc15bts_clock.c b/src/osmo-bts-lc15/misc/lc15bts_clock.c new file mode 100644 index 00000000..71701496 --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_clock.c @@ -0,0 +1,260 @@ +/* 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 "/var/lc15/clkerr/clkerr1_average" +#define CLKERR_ACC_SYSFS "/var/lc15/clkerr/clkerr1_average_accuracy" +#define CLKERR_INT_SYSFS "/var/lc15/clkerr/clkerr1_average_interval" +#define CLKERR_FLT_SYSFS "/var/lc15/clkerr/clkerr1_fault" +#define CLKERR_RFS_SYSFS "/var/lc15/clkerr/refresh" +#define CLKERR_RST_SYSFS "/var/lc15/clkerr/reset" + +#define OCXODAC_VAL_SYSFS "/var/lc15/ocxo/voltage" +#define OCXODAC_ROM_SYSFS "/var/lc15/ocxo/eeprom" + +/* clock error */ +static int clkerr_fd_err = -1; +static int clkerr_fd_accuracy = -1; +static int clkerr_fd_interval = -1; +static int clkerr_fd_fault = -1; +static int clkerr_fd_refresh = -1; +static int clkerr_fd_reset = -1; + +/* ocxo dac */ +static int ocxodac_fd_value = -1; +static int ocxodac_fd_save = -1; + + +static int sysfs_read_val(int fd, int *val) +{ + int rc; + char szVal[32] = {0}; + + lseek( fd, 0, SEEK_SET ); + + rc = read(fd, szVal, sizeof(szVal) - 1); + if (rc < 0) { + return -errno; + } + + rc = sscanf(szVal, "%d", val); + if (rc != 1) { + return -1; + } + + return 0; +} + +static int sysfs_write_val(int fd, int val) +{ + int n, rc; + char szVal[32] = {0}; + + n = sprintf(szVal, "%d", val); + + lseek(fd, 0, SEEK_SET); + rc = write(fd, szVal, n+1); + if (rc < 0) { + return -errno; + } + return 0; +} + +static int sysfs_write_str(int fd, const char *str) +{ + int rc; + + lseek( fd, 0, SEEK_SET ); + rc = write(fd, str, strlen(str)+1); + if (rc < 0) { + return -errno; + } + return 0; +} + + +int lc15bts_clock_err_open(void) +{ + 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; + } + } + return 0; +} + +void lc15bts_clock_err_close(void) +{ + if (clkerr_fd_err >= 0) { + close(clkerr_fd_err); + clkerr_fd_err = -1; + } + + if (clkerr_fd_accuracy >= 0) { + close(clkerr_fd_accuracy); + clkerr_fd_accuracy = -1; + } + + if (clkerr_fd_interval >= 0) { + close(clkerr_fd_interval); + clkerr_fd_interval = -1; + } + + if (clkerr_fd_fault >= 0) { + close(clkerr_fd_fault); + clkerr_fd_fault = -1; + } + + if (clkerr_fd_refresh >= 0) { + close(clkerr_fd_refresh); + clkerr_fd_refresh = -1; + } + + if (clkerr_fd_reset >= 0) { + close(clkerr_fd_reset); + clkerr_fd_reset = -1; + } +} + +int lc15bts_clock_err_reset(void) +{ + return sysfs_write_val(clkerr_fd_reset, 1); +} + +int lc15bts_clock_err_get(int *fault, int *error_ppt, + int *accuracy_ppq, int *interval_sec) +{ + int rc; + + rc = sysfs_write_str(clkerr_fd_refresh, "once"); + if (rc < 0) { + return -1; + } + + rc = sysfs_read_val(clkerr_fd_fault, fault); + rc |= sysfs_read_val(clkerr_fd_err, error_ppt); + rc |= sysfs_read_val(clkerr_fd_accuracy, accuracy_ppq); + rc |= sysfs_read_val(clkerr_fd_interval, interval_sec); + if (rc) { + return -1; + } + return 0; +} + + +int lc15bts_clock_dac_open(void) +{ + if (ocxodac_fd_value < 0) { + ocxodac_fd_value = open(OCXODAC_VAL_SYSFS, O_RDWR); + if (ocxodac_fd_value < 0) { + lc15bts_clock_dac_close(); + return ocxodac_fd_value; + } + } + + if (ocxodac_fd_save < 0) { + ocxodac_fd_save = open(OCXODAC_ROM_SYSFS, O_WRONLY); + if (ocxodac_fd_save < 0) { + lc15bts_clock_dac_close(); + return ocxodac_fd_save; + } + } + return 0; +} + +void lc15bts_clock_dac_close(void) +{ + if (ocxodac_fd_value >= 0) { + close(ocxodac_fd_value); + ocxodac_fd_value = -1; + } + + if (ocxodac_fd_save >= 0) { + close(ocxodac_fd_save); + ocxodac_fd_save = -1; + } +} + +int lc15bts_clock_dac_get(int *dac_value) +{ + return sysfs_read_val(ocxodac_fd_value, dac_value); +} + +int lc15bts_clock_dac_set(int dac_value) +{ + return sysfs_write_val(ocxodac_fd_value, dac_value); +} + +int lc15bts_clock_dac_save(void) +{ + return sysfs_write_val(ocxodac_fd_save, 1); +} + + diff --git a/src/osmo-bts-lc15/misc/lc15bts_clock.h b/src/osmo-bts-lc15/misc/lc15bts_clock.h new file mode 100644 index 00000000..d9673598 --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_clock.h @@ -0,0 +1,16 @@ +#ifndef _LC15BTS_CLOCK_H +#define _LC15BTS_CLOCK_H + +int lc15bts_clock_err_open(void); +void lc15bts_clock_err_close(void); +int lc15bts_clock_err_reset(void); +int lc15bts_clock_err_get(int *fault, int *error_ppt, + int *accuracy_ppq, int *interval_sec); + +int lc15bts_clock_dac_open(void); +void lc15bts_clock_dac_close(void); +int lc15bts_clock_dac_get(int *dac_value); +int lc15bts_clock_dac_set(int dac_value); +int lc15bts_clock_dac_save(void); + +#endif diff --git a/src/osmo-bts-lc15/misc/lc15bts_led.c b/src/osmo-bts-lc15/misc/lc15bts_led.c new file mode 100644 index 00000000..a93d3fb0 --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_led.c @@ -0,0 +1,333 @@ +/* Copyright (C) 2016 by NuRAN Wireless <support@nuranwireless.com> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +#include "lc15bts_led.h" +#include "lc15bts_bts.h" +#include <osmocom/core/talloc.h> +#include <osmocom/core/linuxlist.h> + +static struct lc15bts_led led_entries[] = { + { + .name = "led0", + .fullname = "led red", + .path = "/var/lc15/leds/led0/brightness" + }, + { + .name = "led1", + .fullname = "led green", + .path = "/var/lc15/leds/led1/brightness" + } +}; + +static const struct value_string lc15bts_led_strs[] = { + { LC15BTS_LED_RED, "LED red" }, + { LC15BTS_LED_GREEN, "LED green" }, + { LC15BTS_LED_ORANGE, "LED orange" }, + { LC15BTS_LED_OFF, "LED off" }, + { 0, NULL } +}; + +static uint8_t led_priority[] = { + BLINK_PATTERN_INIT, + BLINK_PATTERN_INT_PROC_MALFUNC, + BLINK_PATTERN_SUPPLY_PWR_MAX, + BLINK_PATTERN_PA_PWR_MAX, + BLINK_PATTERN_VSWR_MAX, + BLINK_PATTERN_SUPPLY_VOLT_MIN, + BLINK_PATTERN_TEMP_MAX, + BLINK_PATTERN_SUPPLY_PWR_MAX2, + BLINK_PATTERN_EXT_LINK_MALFUNC, + BLINK_PATTERN_SUPPLY_VOLT_LOW, + BLINK_PATTERN_TEMP_HIGH, + BLINK_PATTERN_VSWR_HIGH, + BLINK_PATTERN_SUPPLY_PWR_HIGH, + BLINK_PATTERN_PA_PWR_HIGH, + BLINK_PATTERN_GPS_FIX_LOST, + BLINK_PATTERN_NORMAL +}; + + +char *blink_pattern_command[] = BLINK_PATTERN_COMMAND; + +static int lc15bts_led_write(char *path, char *str) +{ + int fd; + + if ((fd = open(path, O_WRONLY)) == -1) + { + return 0; + } + + write(fd, str, strlen(str)+1); + close(fd); + return 1; +} + +static void led_set_red() +{ + lc15bts_led_write(led_entries[0].path, "1"); + lc15bts_led_write(led_entries[1].path, "0"); +} + +static void led_set_green() +{ + lc15bts_led_write(led_entries[0].path, "0"); + lc15bts_led_write(led_entries[1].path, "1"); +} + +static void led_set_orange() +{ + lc15bts_led_write(led_entries[0].path, "1"); + lc15bts_led_write(led_entries[1].path, "1"); +} + +static void led_set_off() +{ + lc15bts_led_write(led_entries[0].path, "0"); + lc15bts_led_write(led_entries[1].path, "0"); +} + +static void led_sleep( struct lc15bts_mgr_instance *mgr, struct lc15bts_led_timer *led_timer, void (*led_timer_cb)(void *_data)) { + /* Cancel any pending timer */ + osmo_timer_del(&led_timer->timer); + /* Start LED timer */ + led_timer->timer.cb = led_timer_cb; + led_timer->timer.data = mgr; + mgr->lc15bts_leds.active_timer = led_timer->idx; + osmo_timer_schedule(&led_timer->timer, led_timer->param.sleep_sec, led_timer->param.sleep_usec); + LOGP(DTEMP, LOGL_DEBUG,"%s timer scheduled for %d sec + %d usec\n", + get_value_string(lc15bts_led_strs, led_timer->idx), + led_timer->param.sleep_sec, + led_timer->param.sleep_usec); + + switch (led_timer->idx) { + case LC15BTS_LED_RED: + led_set_red(); + break; + case LC15BTS_LED_GREEN: + led_set_green(); + break; + case LC15BTS_LED_ORANGE: + led_set_orange(); + break; + case LC15BTS_LED_OFF: + led_set_off(); + break; + default: + led_set_off(); + } +} + +static void led_sleep_cb(void *_data) { + struct lc15bts_mgr_instance *mgr = _data; + struct lc15bts_led_timer_list *led_list; + + /* make sure the timer list is not empty */ + if (llist_empty(&mgr->lc15bts_leds.list)) + return; + + llist_for_each_entry(led_list, &mgr->lc15bts_leds.list, list) { + if (led_list->led_timer.idx == mgr->lc15bts_leds.active_timer) { + LOGP(DTEMP, LOGL_DEBUG,"Delete expired %s timer %d sec + %d usec\n", + get_value_string(lc15bts_led_strs, led_list->led_timer.idx), + led_list->led_timer.param.sleep_sec, + led_list->led_timer.param.sleep_usec); + + /* Delete current timer */ + osmo_timer_del(&led_list->led_timer.timer); + /* Rotate the timer list */ + llist_move_tail(&led_list->list, &mgr->lc15bts_leds.list); + break; + } + } + + /* Execute next timer */ + led_list = llist_first_entry(&mgr->lc15bts_leds.list, struct lc15bts_led_timer_list, list); + if (led_list) { + LOGP(DTEMP, LOGL_DEBUG,"Execute %s timer %d sec + %d usec, total entries=%d\n", + get_value_string(lc15bts_led_strs, led_list->led_timer.idx), + led_list->led_timer.param.sleep_sec, + led_list->led_timer.param.sleep_usec, + llist_count(&mgr->lc15bts_leds.list)); + + led_sleep(mgr, &led_list->led_timer, led_sleep_cb); + } + +} + +static void delete_led_timer_entries(struct lc15bts_mgr_instance *mgr) +{ + struct lc15bts_led_timer_list *led_list, *led_list2; + + if (llist_empty(&mgr->lc15bts_leds.list)) + return; + + llist_for_each_entry_safe(led_list, led_list2, &mgr->lc15bts_leds.list, list) { + /* Delete the timer in list */ + if (led_list) { + LOGP(DTEMP, LOGL_DEBUG,"Delete %s timer entry from list, %d sec + %d usec\n", + get_value_string(lc15bts_led_strs, led_list->led_timer.idx), + led_list->led_timer.param.sleep_sec, + led_list->led_timer.param.sleep_usec); + + /* Delete current timer */ + osmo_timer_del(&led_list->led_timer.timer); + llist_del(&led_list->list); + talloc_free(led_list); + } + } + return; +} + +static int add_led_timer_entry(struct lc15bts_mgr_instance *mgr, char *cmdstr) +{ + double sec, int_sec, frac_sec; + struct lc15bts_sleep_time led_param; + + led_param.sleep_sec = 0; + led_param.sleep_usec = 0; + + if (strstr(cmdstr, "set red") != NULL) + mgr->lc15bts_leds.led_idx = LC15BTS_LED_RED; + else if (strstr(cmdstr, "set green") != NULL) + mgr->lc15bts_leds.led_idx = LC15BTS_LED_GREEN; + else if (strstr(cmdstr, "set orange") != NULL) + mgr->lc15bts_leds.led_idx = LC15BTS_LED_ORANGE; + else if (strstr(cmdstr, "set off") != NULL) + mgr->lc15bts_leds.led_idx = LC15BTS_LED_OFF; + else if (strstr(cmdstr, "sleep") != NULL) { + sec = atof(cmdstr + 6); + /* split time into integer and fractional of seconds */ + frac_sec = modf(sec, &int_sec) * 1000000.0; + led_param.sleep_sec = (int)int_sec; + led_param.sleep_usec = (int)frac_sec; + + if ((mgr->lc15bts_leds.led_idx >= LC15BTS_LED_RED) && (mgr->lc15bts_leds.led_idx < _LC15BTS_LED_MAX)) { + struct lc15bts_led_timer_list *led_list; + + /* allocate timer entry */ + led_list = talloc_zero(tall_mgr_ctx, struct lc15bts_led_timer_list); + if (led_list) { + led_list->led_timer.idx = mgr->lc15bts_leds.led_idx; + led_list->led_timer.param.sleep_sec = led_param.sleep_sec; + led_list->led_timer.param.sleep_usec = led_param.sleep_usec; + llist_add_tail(&led_list->list, &mgr->lc15bts_leds.list); + + LOGP(DTEMP, LOGL_DEBUG,"Add %s timer to list, %d sec + %d usec, total entries=%d\n", + get_value_string(lc15bts_led_strs, mgr->lc15bts_leds.led_idx), + led_list->led_timer.param.sleep_sec, + led_list->led_timer.param.sleep_usec, + llist_count(&mgr->lc15bts_leds.list)); + } + } + } else + return -1; + + return 0; +} + +static int parse_led_pattern(char *pattern, struct lc15bts_mgr_instance *mgr) +{ + char str[1024]; + char *pstr; + char *sep; + int rc = 0; + + strcpy(str, pattern); + pstr = str; + while ((sep = strsep(&pstr, ";")) != NULL) { + rc = add_led_timer_entry(mgr, sep); + if (rc < 0) { + break; + } + + } + return rc; +} + +/*** led interface ***/ + +void led_set(struct lc15bts_mgr_instance *mgr, int pattern_id) +{ + int rc; + struct lc15bts_led_timer_list *led_list; + + if (pattern_id > BLINK_PATTERN_MAX_ITEM - 1) { + LOGP(DTEMP, LOGL_ERROR, "Invalid LED pattern : %d. LED pattern must be between %d..%d\n", + pattern_id, + BLINK_PATTERN_POWER_ON, + BLINK_PATTERN_MAX_ITEM - 1); + return; + } + if (pattern_id == mgr->lc15bts_leds.last_pattern_id) + return; + + mgr->lc15bts_leds.last_pattern_id = pattern_id; + + LOGP(DTEMP, LOGL_NOTICE, "blink pattern command : %d\n", pattern_id); + LOGP(DTEMP, LOGL_NOTICE, "%s\n", blink_pattern_command[pattern_id]); + + /* Empty existing LED timer in the list */ + delete_led_timer_entries(mgr); + + /* parse LED pattern */ + rc = parse_led_pattern(blink_pattern_command[pattern_id], mgr); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR,"LED pattern not found or invalid LED pattern\n"); + return; + } + + /* make sure the timer list is not empty */ + if (llist_empty(&mgr->lc15bts_leds.list)) + return; + + /* Start the first LED timer in the list */ + led_list = llist_first_entry(&mgr->lc15bts_leds.list, struct lc15bts_led_timer_list, list); + if (led_list) { + LOGP(DTEMP, LOGL_DEBUG,"Execute timer %s for %d sec + %d usec\n", + get_value_string(lc15bts_led_strs, led_list->led_timer.idx), + led_list->led_timer.param.sleep_sec, + led_list->led_timer.param.sleep_usec); + + led_sleep(mgr, &led_list->led_timer, led_sleep_cb); + } + +} + +void select_led_pattern(struct lc15bts_mgr_instance *mgr) +{ + int i; + uint8_t led[BLINK_PATTERN_MAX_ITEM] = {0}; + + /* set normal LED pattern at first */ + led[BLINK_PATTERN_NORMAL] = 1; + + /* check on-board sensors for new LED pattern */ + check_sensor_led_pattern(mgr, led); + + /* check BTS status for new LED pattern */ + check_bts_led_pattern(led); + + /* check by priority */ + for (i = 0; i < sizeof(led_priority)/sizeof(uint8_t); i++) { + if(led[led_priority[i]] == 1) { + led_set(mgr, led_priority[i]); + break; + } + } +} diff --git a/src/osmo-bts-lc15/misc/lc15bts_led.h b/src/osmo-bts-lc15/misc/lc15bts_led.h new file mode 100644 index 00000000..b6d9d28b --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_led.h @@ -0,0 +1,22 @@ +#ifndef _LC15BTS_LED_H +#define _LC15BTS_LED_H + +#include <stdint.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <math.h> +#include <sys/stat.h> + +#include <osmo-bts/logging.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/utils.h> + +#include "lc15bts_mgr.h" + +/* public function prototypes */ +void led_set(struct lc15bts_mgr_instance *mgr, int pattern_id); + +void select_led_pattern(struct lc15bts_mgr_instance *mgr); + +#endif diff --git a/src/osmo-bts-lc15/misc/lc15bts_mgr.c b/src/osmo-bts-lc15/misc/lc15bts_mgr.c new file mode 100644 index 00000000..da21c9d0 --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_mgr.c @@ -0,0 +1,366 @@ +/* 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" +#include "misc/lc15bts_swd.h" + +#include "lc15bts_led.h" + +static int no_rom_write = 0; +static int daemonize = 0; +void *tall_mgr_ctx; + +/* every 6 hours means 365*4 = 1460 rom writes per year (max) */ +#define SENSOR_TIMER_SECS (6 * 3600) + +/* every 1 hours means 365*24 = 8760 rom writes per year (max) */ +#define HOURS_TIMER_SECS (1 * 3600) + + +/* the initial state */ +static struct lc15bts_mgr_instance manager = { + .config_file = "lc15bts-mgr.cfg", + .temp = { + .supply_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -40, + }, + .soc_temp_limit = { + .thresh_warn_max = 95, + .thresh_crit_max = 100, + .thresh_warn_min = -40, + }, + .fpga_temp_limit = { + .thresh_warn_max = 95, + .thresh_crit_max = 100, + .thresh_warn_min = -40, + }, + .rmsdet_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -40, + }, + .ocxo_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -40, + }, + .tx0_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -20, + }, + .tx1_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -20, + }, + .pa0_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -40, + }, + .pa1_temp_limit = { + .thresh_warn_max = 80, + .thresh_crit_max = 85, + .thresh_warn_min = -40, + } + }, + .volt = { + .supply_volt_limit = { + .thresh_warn_max = 30000, + .thresh_crit_max = 30500, + .thresh_warn_min = 19000, + .thresh_crit_min = 17500, + } + }, + .pwr = { + .supply_pwr_limit = { + .thresh_warn_max = 110, + .thresh_crit_max = 120, + }, + .pa0_pwr_limit = { + .thresh_warn_max = 50, + .thresh_crit_max = 60, + }, + .pa1_pwr_limit = { + .thresh_warn_max = 50, + .thresh_crit_max = 60, + } + }, + .vswr = { + .tx0_vswr_limit = { + .thresh_warn_max = 3000, + .thresh_crit_max = 5000, + }, + .tx1_vswr_limit = { + .thresh_warn_max = 3000, + .thresh_crit_max = 5000, + } + }, + .gps = { + .gps_fix_limit = { + .thresh_warn_max = 7, + } + }, + .state = { + .action_norm = SENSOR_ACT_NORM_PA0_ON | SENSOR_ACT_NORM_PA1_ON, + .action_warn = 0, + .action_crit = 0, + .action_comb = 0, + .state = STATE_NORMAL, + } +}; + +static struct osmo_timer_list sensor_timer; +static void check_sensor_timer_cb(void *unused) +{ + lc15bts_check_temp(no_rom_write); + lc15bts_check_power(no_rom_write); + lc15bts_check_vswr(no_rom_write); + osmo_timer_schedule(&sensor_timer, SENSOR_TIMER_SECS, 0); + /* TODO checks if lc15bts_check_temp/lc15bts_check_power/lc15bts_check_vswr went ok */ + lc15bts_swd_event(&manager, SWD_CHECK_SENSOR); +} + +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); + /* TODO: validates if lc15bts_update_hours went correctly */ + lc15bts_swd_event(&manager, SWD_UPDATE_HOURS); +} + +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: + case SIGTERM: + lc15bts_check_temp(no_rom_write); + lc15bts_check_power(no_rom_write); + lc15bts_check_vswr(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_NOTICE, + }, + [DFW] = { + .name = "DFW", + .description = "Firmware management", + .color = "\033[1;36m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DFIND] = { + .name = "DFIND", + .description = "ipaccess-find handling", + .color = "\033[1;37m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DCALIB] = { + .name = "DCALIB", + .description = "Calibration handling", + .color = "\033[1;37m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, + [DSWD] = { + .name = "DSWD", + .description = "Software Watchdog", + .color = "\033[1;37m", + .enabled = 1, .loglevel = LOGL_NOTICE, + }, +}; + +static const struct log_info mgr_log_info = { + .cat = mgr_log_info_cat, + .num_cat = ARRAY_SIZE(mgr_log_info_cat), +}; + +int main(int argc, char **argv) +{ + int rc; + + tall_mgr_ctx = talloc_named_const(NULL, 1, "bts manager"); + msgb_talloc_ctx_init(tall_mgr_ctx, 0); + + osmo_init_logging2(tall_mgr_ctx, &mgr_log_info); + + osmo_init_ignore_signals(); + signal(SIGINT, &signal_handler); + signal(SIGTERM, &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(); + rc = lc15bts_mgr_parse_config(&manager); + if (rc < 0) { + LOGP(DFIND, LOGL_FATAL, "Cannot parse config file\n"); + exit(1); + } + + rc = telnet_init(tall_mgr_ctx, NULL, OSMO_VTY_PORT_BTSMGR); + if (rc < 0) { + fprintf(stderr, "Error initializing telnet\n"); + exit(1); + } + + INIT_LLIST_HEAD(&manager.lc15bts_leds.list); + INIT_LLIST_HEAD(&manager.alarms.list); + + /* Initialize the service watchdog notification for SWD_LAST event(s) */ + if (lc15bts_swd_init(&manager, (int)(SWD_LAST)) != 0) + exit(3); + + /* start temperature check timer */ + sensor_timer.cb = check_sensor_timer_cb; + check_sensor_timer_cb(NULL); + + /* start operational hours timer */ + hours_timer.cb = hours_timer_cb; + hours_timer_cb(NULL); + + /* Enable the PAs */ + rc = lc15bts_power_set(LC15BTS_POWER_PA0, 1); + if (rc < 0) { + exit(3); + } + + rc = lc15bts_power_set(LC15BTS_POWER_PA1, 1); + if (rc < 0) { + exit(3); + } + + /* handle broadcast messages for ipaccess-find */ + if (lc15bts_mgr_nl_init() != 0) + exit(3); + + /* Initialize the sensor control */ + lc15bts_mgr_sensor_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); + lc15bts_swd_event(&manager, SWD_MAINLOOP); + } +} diff --git a/src/osmo-bts-lc15/misc/lc15bts_mgr.h b/src/osmo-bts-lc15/misc/lc15bts_mgr.h new file mode 100644 index 00000000..4bfbdbc9 --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_mgr.h @@ -0,0 +1,422 @@ +#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> + +#define LC15BTS_SENSOR_TIMER_DURATION 60 +#define LC15BTS_PREVENT_TIMER_DURATION 15 * 60 +#define LC15BTS_PREVENT_TIMER_SHORT_DURATION 5 * 60 +#define LC15BTS_PREVENT_TIMER_NONE 0 +#define LC15BTS_PREVENT_RETRY INT_MAX - 1 + +enum BLINK_PATTERN { + BLINK_PATTERN_POWER_ON = 0, //hardware set + BLINK_PATTERN_INIT, + BLINK_PATTERN_NORMAL, + BLINK_PATTERN_EXT_LINK_MALFUNC, + BLINK_PATTERN_INT_PROC_MALFUNC, + BLINK_PATTERN_SUPPLY_VOLT_LOW, + BLINK_PATTERN_SUPPLY_VOLT_MIN, + BLINK_PATTERN_VSWR_HIGH, + BLINK_PATTERN_VSWR_MAX, + BLINK_PATTERN_TEMP_HIGH, + BLINK_PATTERN_TEMP_MAX, + BLINK_PATTERN_SUPPLY_PWR_HIGH, + BLINK_PATTERN_SUPPLY_PWR_MAX, + BLINK_PATTERN_SUPPLY_PWR_MAX2, + BLINK_PATTERN_PA_PWR_HIGH, + BLINK_PATTERN_PA_PWR_MAX, + BLINK_PATTERN_GPS_FIX_LOST, + BLINK_PATTERN_MAX_ITEM +}; + +#define BLINK_PATTERN_COMMAND {\ + "set red; sleep 5.0",\ + "set orange; sleep 5.0",\ + "set green; sleep 2.5; set off; sleep 2.5",\ + "set red; sleep 0.5; set off; sleep 0.5",\ + "set red; sleep 2.5; set off; sleep 2.5",\ + "set green; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5",\ + "set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5 ",\ + "set green; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\ + "set red; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\ + "set orange; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5 ",\ + "set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\ + "set green; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\ + "set red; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\ + "set orange; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\ + "set green; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\ + "set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\ + "set green; sleep 2.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\ +} + +enum { + DTEMP, + DFW, + DFIND, + DCALIB, + DSWD, +}; + +// TODO NTQD: Define new actions like reducing output power, limit ARM core speed, shutdown second TRX/PA, ... +enum { +#if 0 + SENSOR_ACT_PWR_CONTRL = 0x1, +#endif + SENSOR_ACT_PA0_OFF = 0x2, + SENSOR_ACT_PA1_OFF = 0x4, + SENSOR_ACT_BTS_SRV_OFF = 0x10, +}; + +/* actions only for normal state */ +enum { +#if 0 + SENSOR_ACT_NORM_PW_CONTRL = 0x1, +#endif + SENSOR_ACT_NORM_PA0_ON = 0x2, + SENSOR_ACT_NORM_PA1_ON = 0x4, + SENSOR_ACT_NORM_BTS_SRV_ON= 0x10, +}; + +enum lc15bts_sensor_state { + STATE_NORMAL, /* Everything is fine */ + STATE_WARNING_HYST, /* Go back to normal next? */ + STATE_WARNING, /* We are above the warning threshold */ + STATE_CRITICAL, /* We have an issue. Wait for below warning */ +}; + +enum lc15bts_leds_name { + LC15BTS_LED_RED = 0, + LC15BTS_LED_GREEN, + LC15BTS_LED_ORANGE, + LC15BTS_LED_OFF, + _LC15BTS_LED_MAX +}; + +struct lc15bts_led{ + char *name; + char *fullname; + char *path; +}; + +/** + * Temperature Limits. We separate from a threshold + * that will generate a warning and one that is so + * severe that an action will be taken. + */ +struct lc15bts_temp_limit { + int thresh_warn_max; + int thresh_crit_max; + int thresh_warn_min; +}; + +struct lc15bts_volt_limit { + int thresh_warn_max; + int thresh_crit_max; + int thresh_warn_min; + int thresh_crit_min; +}; + +struct lc15bts_pwr_limit { + int thresh_warn_max; + int thresh_crit_max; +}; + +struct lc15bts_vswr_limit { + int thresh_warn_max; + int thresh_crit_max; +}; + +struct lc15bts_gps_fix_limit { + int thresh_warn_max; +}; + +struct lc15bts_sleep_time { + int sleep_sec; + int sleep_usec; +}; + +struct lc15bts_led_timer { + uint8_t idx; + struct osmo_timer_list timer; + struct lc15bts_sleep_time param; +}; + +struct lc15bts_led_timer_list { + struct llist_head list; + struct lc15bts_led_timer led_timer; +}; + +struct lc15bts_preventive_list { + struct llist_head list; + struct lc15bts_sleep_time param; + int action_flag; +}; + +enum mgr_vty_node { + MGR_NODE = _LAST_OSMOVTY_NODE + 1, + + ACT_NORM_NODE, + ACT_WARN_NODE, + ACT_CRIT_NODE, + LIMIT_SUPPLY_TEMP_NODE, + LIMIT_SOC_NODE, + LIMIT_FPGA_NODE, + LIMIT_RMSDET_NODE, + LIMIT_OCXO_NODE, + LIMIT_TX0_TEMP_NODE, + LIMIT_TX1_TEMP_NODE, + LIMIT_PA0_TEMP_NODE, + LIMIT_PA1_TEMP_NODE, + LIMIT_SUPPLY_VOLT_NODE, + LIMIT_TX0_VSWR_NODE, + LIMIT_TX1_VSWR_NODE, + LIMIT_SUPPLY_PWR_NODE, + LIMIT_PA0_PWR_NODE, + LIMIT_PA1_PWR_NODE, + LIMIT_GPS_FIX_NODE, +}; + +enum mgr_vty_limit_type { + MGR_LIMIT_TYPE_TEMP = 0, + MGR_LIMIT_TYPE_VOLT, + MGR_LIMIT_TYPE_VSWR, + MGR_LIMIT_TYPE_PWR, + _MGR_LIMIT_TYPE_MAX, +}; + +struct lc15bts_mgr_instance { + const char *config_file; + + struct { + struct lc15bts_temp_limit supply_temp_limit; + struct lc15bts_temp_limit soc_temp_limit; + struct lc15bts_temp_limit fpga_temp_limit; + struct lc15bts_temp_limit rmsdet_temp_limit; + struct lc15bts_temp_limit ocxo_temp_limit; + struct lc15bts_temp_limit tx0_temp_limit; + struct lc15bts_temp_limit tx1_temp_limit; + struct lc15bts_temp_limit pa0_temp_limit; + struct lc15bts_temp_limit pa1_temp_limit; + } temp; + + struct { + struct lc15bts_volt_limit supply_volt_limit; + } volt; + + struct { + struct lc15bts_pwr_limit supply_pwr_limit; + struct lc15bts_pwr_limit pa0_pwr_limit; + struct lc15bts_pwr_limit pa1_pwr_limit; + } pwr; + + struct { + struct lc15bts_vswr_limit tx0_vswr_limit; + struct lc15bts_vswr_limit tx1_vswr_limit; + int tx0_last_vswr; + int tx1_last_vswr; + } vswr; + + struct { + struct lc15bts_gps_fix_limit gps_fix_limit; + time_t last_update; + } gps; + + struct { + int action_norm; + int action_warn; + int action_crit; + int action_comb; + + enum lc15bts_sensor_state state; + } state; + + struct { + int state; + int calib_from_loop; + struct osmo_timer_list calib_timeout; + } calib; + + struct { + int state; + int swd_from_loop; + unsigned long long int swd_events; + unsigned long long int swd_events_cache; + unsigned long long int swd_eventmasks; + int num_events; + struct osmo_timer_list swd_timeout; + } swd; + + struct { + uint8_t led_idx; + uint8_t last_pattern_id; + uint8_t active_timer; + struct llist_head list; + } lc15bts_leds; + + struct { + int is_up; + uint32_t last_seqno; + struct osmo_timer_list recon_timer; + struct ipa_client_conn *bts_conn; + uint32_t crit_flags; + uint32_t warn_flags; + } lc15bts_ctrl; + + struct lc15bts_alarms { + int temp_high; + int temp_max; + int supply_low; + int supply_min; + int vswr_high; + int vswr_max; + int supply_pwr_high; + int supply_pwr_max; + int supply_pwr_max2; + int pa_pwr_high; + int pa_pwr_max; + int gps_fix_lost; + struct llist_head list; + struct osmo_timer_list preventive_timer; + int preventive_duration; + int preventive_retry; + } alarms; + +}; + +enum lc15bts_mgr_fail_evt_rep_crit_sig { + /* Critical alarms */ + S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM = (1 << 0), + S_MGR_TEMP_SOC_CRIT_MAX_ALARM = (1 << 1), + S_MGR_TEMP_FPGA_CRIT_MAX_ALARM = (1 << 2), + S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM = (1 << 3), + S_MGR_TEMP_OCXO_CRIT_MAX_ALARM = (1 << 4), + S_MGR_TEMP_TRX0_CRIT_MAX_ALARM = (1 << 5), + S_MGR_TEMP_TRX1_CRIT_MAX_ALARM = (1 << 6), + S_MGR_TEMP_PA0_CRIT_MAX_ALARM = (1 << 7), + S_MGR_TEMP_PA1_CRIT_MAX_ALARM = (1 << 8), + S_MGR_SUPPLY_CRIT_MAX_ALARM = (1 << 9), + S_MGR_SUPPLY_CRIT_MIN_ALARM = (1 << 10), + S_MGR_VSWR0_CRIT_MAX_ALARM = (1 << 11), + S_MGR_VSWR1_CRIT_MAX_ALARM = (1 << 12), + S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM = (1 << 13), + S_MGR_PWR_PA0_CRIT_MAX_ALARM = (1 << 14), + S_MGR_PWR_PA1_CRIT_MAX_ALARM = (1 << 15), + _S_MGR_CRIT_ALARM_MAX, +}; + +enum lc15bts_mgr_fail_evt_rep_warn_sig { + /* Warning alarms */ + S_MGR_TEMP_SUPPLY_WARN_MIN_ALARM = (1 << 0), + S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM = (1 << 2), + S_MGR_TEMP_SOC_WARN_MIN_ALARM = (1 << 3), + S_MGR_TEMP_SOC_WARN_MAX_ALARM = (1 << 4), + S_MGR_TEMP_FPGA_WARN_MIN_ALARM = (1 << 5), + S_MGR_TEMP_FPGA_WARN_MAX_ALARM = (1 << 6), + S_MGR_TEMP_RMS_DET_WARN_MIN_ALARM = (1 << 7), + S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM = (1 << 8), + S_MGR_TEMP_OCXO_WARN_MIN_ALARM = (1 << 9), + S_MGR_TEMP_OCXO_WARN_MAX_ALARM = (1 << 10), + S_MGR_TEMP_TRX0_WARN_MIN_ALARM = (1 << 11), + S_MGR_TEMP_TRX0_WARN_MAX_ALARM = (1 << 12), + S_MGR_TEMP_TRX1_WARN_MIN_ALARM = (1 << 13), + S_MGR_TEMP_TRX1_WARN_MAX_ALARM = (1 << 14), + S_MGR_TEMP_PA0_WARN_MIN_ALARM = (1 << 15), + S_MGR_TEMP_PA0_WARN_MAX_ALARM = (1 << 16), + S_MGR_TEMP_PA1_WARN_MIN_ALARM = (1 << 17), + S_MGR_TEMP_PA1_WARN_MAX_ALARM = (1 << 18), + S_MGR_SUPPLY_WARN_MIN_ALARM = (1 << 19), + S_MGR_SUPPLY_WARN_MAX_ALARM = (1 << 20), + S_MGR_VSWR0_WARN_MAX_ALARM = (1 << 21), + S_MGR_VSWR1_WARN_MAX_ALARM = (1 << 22), + S_MGR_PWR_SUPPLY_WARN_MAX_ALARM = (1 << 23), + S_MGR_PWR_PA0_WARN_MAX_ALARM = (1 << 24), + S_MGR_PWR_PA1_WARN_MAX_ALARM = (1 << 25), + S_MGR_GPS_FIX_WARN_ALARM = (1 << 26), + _S_MGR_WARN_ALARM_MAX, +}; + +enum lc15bts_mgr_failure_event_causes { + /* Critical causes */ + NM_EVT_CAUSE_CRIT_TEMP_SUPPLY_MAX_FAIL = 0x4100, + NM_EVT_CAUSE_CRIT_TEMP_FPGA_MAX_FAIL = 0x4101, + NM_EVT_CAUSE_CRIT_TEMP_SOC_MAX_FAIL = 0x4102, + NM_EVT_CAUSE_CRIT_TEMP_RMS_DET_MAX_FAIL = 0x4103, + NM_EVT_CAUSE_CRIT_TEMP_OCXO_MAX_FAIL = 0x4104, + NM_EVT_CAUSE_CRIT_TEMP_TRX0_MAX_FAIL = 0x4105, + NM_EVT_CAUSE_CRIT_TEMP_TRX1_MAX_FAIL = 0x4106, + NM_EVT_CAUSE_CRIT_TEMP_PA0_MAX_FAIL = 0x4107, + NM_EVT_CAUSE_CRIT_TEMP_PA1_MAX_FAIL = 0x4108, + NM_EVT_CAUSE_CRIT_SUPPLY_MAX_FAIL = 0x4109, + NM_EVT_CAUSE_CRIT_SUPPLY_MIN_FAIL = 0x410A, + NM_EVT_CAUSE_CRIT_VSWR0_MAX_FAIL = 0x410B, + NM_EVT_CAUSE_CRIT_VSWR1_MAX_FAIL = 0x410C, + NM_EVT_CAUSE_CRIT_PWR_SUPPLY_MAX_FAIL = 0x410D, + NM_EVT_CAUSE_CRIT_PWR_PA0_MAX_FAIL = 0x410E, + NM_EVT_CAUSE_CRIT_PWR_PA1_MAX_FAIL = 0x410F, + /* Warning causes */ + NM_EVT_CAUSE_WARN_TEMP_SUPPLY_LOW_FAIL = 0x4400, + NM_EVT_CAUSE_WARN_TEMP_SUPPLY_HIGH_FAIL = 0x4401, + NM_EVT_CAUSE_WARN_TEMP_FPGA_LOW_FAIL = 0x4402, + NM_EVT_CAUSE_WARN_TEMP_FPGA_HIGH_FAIL = 0x4403, + NM_EVT_CAUSE_WARN_TEMP_SOC_LOW_FAIL = 0x4404, + NM_EVT_CAUSE_WARN_TEMP_SOC_HIGH_FAIL = 0x4405, + NM_EVT_CAUSE_WARN_TEMP_RMS_DET_LOW_FAIL = 0x4406, + NM_EVT_CAUSE_WARN_TEMP_RMS_DET_HIGH_FAIL= 0x4407, + NM_EVT_CAUSE_WARN_TEMP_OCXO_LOW_FAIL = 0x4408, + NM_EVT_CAUSE_WARN_TEMP_OCXO_HIGH_FAIL = 0x4409, + NM_EVT_CAUSE_WARN_TEMP_TRX0_LOW_FAIL = 0x440A, + NM_EVT_CAUSE_WARN_TEMP_TRX0_HIGH_FAIL = 0x440B, + NM_EVT_CAUSE_WARN_TEMP_TRX1_LOW_FAIL = 0x440C, + NM_EVT_CAUSE_WARN_TEMP_TRX1_HIGH_FAIL = 0x440D, + NM_EVT_CAUSE_WARN_TEMP_PA0_LOW_FAIL = 0x440E, + NM_EVT_CAUSE_WARN_TEMP_PA0_HIGH_FAIL = 0x440F, + NM_EVT_CAUSE_WARN_TEMP_PA1_LOW_FAIL = 0x4410, + NM_EVT_CAUSE_WARN_TEMP_PA1_HIGH_FAIL = 0x4411, + NM_EVT_CAUSE_WARN_SUPPLY_LOW_FAIL = 0x4412, + NM_EVT_CAUSE_WARN_SUPPLY_HIGH_FAIL = 0x4413, + NM_EVT_CAUSE_WARN_VSWR0_HIGH_FAIL = 0x4414, + NM_EVT_CAUSE_WANR_VSWR1_HIGH_FAIL = 0x4415, + NM_EVT_CAUSE_WARN_PWR_SUPPLY_HIGH_FAIL = 0x4416, + NM_EVT_CAUSE_WARN_PWR_PA0_HIGH_FAIL = 0x4417, + NM_EVT_CAUSE_WARN_PWR_PA1_HIGH_FAIL = 0x4418, + NM_EVT_CAUSE_WARN_GPS_FIX_FAIL = 0x4419, +}; + +/* This defines the list of notification events for systemd service watchdog. + all these events must be notified in a certain service defined timeslot + or the service (this app) would be restarted (only if related systemd service + unit file has WatchdogSec!=0). + WARNING: swd events must begin with event 0. Last events must be + SWD_LAST (max 64 events in this list). +*/ +enum mgr_swd_events { + SWD_MAINLOOP = 0, + SWD_CHECK_SENSOR, + SWD_UPDATE_HOURS, + SWD_CHECK_TEMP_SENSOR, + SWD_CHECK_LED_CTRL, + SWD_CHECK_CALIB, + SWD_CHECK_BTS_CONNECTION, + SWD_LAST +}; + +int lc15bts_mgr_vty_init(void); +int lc15bts_mgr_parse_config(struct lc15bts_mgr_instance *mgr); +int lc15bts_mgr_nl_init(void); +int lc15bts_mgr_sensor_init(struct lc15bts_mgr_instance *mgr); +const char *lc15bts_mgr_sensor_get_state(enum lc15bts_sensor_state state); + +int lc15bts_mgr_calib_init(struct lc15bts_mgr_instance *mgr); +int lc15bts_mgr_control_init(struct lc15bts_mgr_instance *mgr); +int lc15bts_mgr_calib_run(struct lc15bts_mgr_instance *mgr); +void lc15bts_mgr_dispatch_alarm(struct lc15bts_mgr_instance *mgr, const int cause, const char *key, const char *text); +extern void *tall_mgr_ctx; + +#endif diff --git a/src/osmo-bts-lc15/misc/lc15bts_mgr_calib.c b/src/osmo-bts-lc15/misc/lc15bts_mgr_calib.c new file mode 100644 index 00000000..badb5455 --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_mgr_calib.c @@ -0,0 +1,292 @@ +/* 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 "misc/lc15bts_swd.h" +#include "misc/lc15bts_par.h" +#include "misc/lc15bts_led.h" +#include "osmo-bts/msg_utils.h" + +#include <osmocom/core/logging.h> +#include <osmocom/core/select.h> + +#include <osmocom/ctrl/control_cmd.h> + +#include <osmocom/gsm/ipa.h> +#include <osmocom/gsm/protocol/ipaccess.h> + +#include <osmocom/abis/abis.h> +#include <osmocom/abis/e1_input.h> +#include <osmocom/abis/ipa.h> + +#include <time.h> + +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); + +static int ocxodac_saved_value = -1; + +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; + int dac_correction; + time_t now; + time_t last_gps_fix; + + 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; + } + + /* get current time */ + now = time(NULL); + + /* first time after start of manager program */ + if (mgr->gps.last_update == 0) + mgr->gps.last_update = now; + + /* read last GPS 3D fix from storage */ + rc = lc15bts_par_get_gps_fix(&last_gps_fix); + if (rc < 0) { + LOGP(DCALIB, LOGL_NOTICE, "Last GPS 3D fix can not read (%d). Last GPS 3D fix sets to zero\n", rc); + last_gps_fix = 0; + } + + if (fault) { + LOGP(DCALIB, LOGL_NOTICE, "GPS has no fix\n"); + calib_state_reset(mgr, CALIB_FAIL_GPSFIX); + return; + } + + /* We got GPS 3D fix */ + LOGP(DCALIB, LOGL_DEBUG, "Got GPS 3D fix warn_flags=0x%08x, last=%lld, now=%lld\n", + mgr->lc15bts_ctrl.warn_flags, + (long long)last_gps_fix, + (long long)now); + + 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; + } + + /* Set OCXO initial dac value */ + if (ocxodac_saved_value < 0) + ocxodac_saved_value = dac_value; + + 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); + + /* Need integration time to correct */ + if (interval_sec) { + /* 1 unit of correction equal about 0.5 - 1 PPB correction */ + dac_correction = (int)(-error_ppt * 0.0015); + new_dac_value = dac_value + dac_correction; + + if (new_dac_value > 4095) + new_dac_value = 4095; + else if (new_dac_value < 0) + new_dac_value = 0; + + /* We have a fix, make sure the measured error is + meaningful (10 times the accuracy) */ + if ((new_dac_value != dac_value) && ((100l * abs(error_ppt)) > accuracy_ppq)) { + + LOGP(DCALIB, LOGL_NOTICE, + "Going to apply %d as new clock setting.\n", + new_dac_value); + + rc = lc15bts_clock_dac_set(new_dac_value); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to set OCXO dac value %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_OCXODAC); + return; + } + rc = lc15bts_clock_err_reset(); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to reset clock error module %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_CLKERR); + return; + } + } + /* New conditions to store DAC value: + * - Resolution accuracy less or equal than 0.01PPB (or 10000 PPQ) + * - Error less or equal than 2PPB (or 2000PPT) + * - Solution different than the last one */ + else if (accuracy_ppq <= 10000) { + if((dac_value != ocxodac_saved_value) && (abs(error_ppt) < 2000)) { + LOGP(DCALIB, LOGL_NOTICE, "Saving OCXO DAC value to memory... val = %d\n", dac_value); + 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); + } else { + ocxodac_saved_value = dac_value; + } + } + + rc = lc15bts_clock_err_reset(); + if (rc < 0) { + LOGP(DCALIB, LOGL_ERROR, + "Failed to reset clock error module %d\n", rc); + calib_state_reset(mgr, CALIB_FAIL_CLKERR); + } + } + } else { + LOGP(DCALIB, LOGL_NOTICE, "Skipping this iteration, no integration time\n"); + } + + 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); + /* TODO: do we want to notify if we got a calibration error, like no gps fix? */ + lc15bts_swd_event(mgr, SWD_CHECK_CALIB); + } + + mgr->calib.state = CALIB_INITIAL; + calib_close(mgr); +} + +static int calib_run(struct lc15bts_mgr_instance *mgr, int from_loop) +{ + if (mgr->calib.state != CALIB_INITIAL) { + LOGP(DCALIB, LOGL_ERROR, "Calib is already in progress.\n"); + return -1; + } + + mgr->calib.calib_from_loop = from_loop; + + /* From now on everything will be handled from the failure */ + mgr->calib.state = CALIB_IN_PROGRESS; + calib_start(mgr); + return 0; +} + +static void calib_loop_run(void *_data) +{ + int rc; + struct lc15bts_mgr_instance *mgr = _data; + + LOGP(DCALIB, LOGL_NOTICE, "Going to calibrate the system.\n"); + rc = calib_run(mgr, 1); + if (rc != 0) { + calib_state_reset(mgr, CALIB_FAIL_START); + } +} + +int lc15bts_mgr_calib_run(struct lc15bts_mgr_instance *mgr) +{ + return calib_run(mgr, 0); +} + +int lc15bts_mgr_calib_init(struct lc15bts_mgr_instance *mgr) +{ + mgr->calib.state = CALIB_INITIAL; + mgr->calib.calib_timeout.data = mgr; + mgr->calib.calib_timeout.cb = calib_loop_run; + osmo_timer_schedule(&mgr->calib.calib_timeout, 0, 0); + + return 0; +} + diff --git a/src/osmo-bts-lc15/misc/lc15bts_mgr_nl.c b/src/osmo-bts-lc15/misc/lc15bts_mgr_nl.c new file mode 100644 index 00000000..3a617dd7 --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_mgr_nl.c @@ -0,0 +1,195 @@ +/* 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 "/var/lc15/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; + + /* 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(tall_mgr_ctx, LC15BTS_PAR_SERNR, &serno); + snprintf(ser_str, sizeof(ser_str), "%d", serno); + + strncpy(model_name, get_hwversion_desc(), sizeof(model_name)-1); + fetched_info = 1; + } + + if (source_for_dest(&src->sin_addr, &loc_addr.sin_addr) != 0) { + LOGP(DFIND, LOGL_ERROR, "Failed to determine local source\n"); + return; + } + + msgb_put_u8(msg, IPAC_MSGT_ID_RESP); + + /* append MAC addr */ + quirk_l16tv_put(msg, strlen(mac_str) + 1, IPAC_IDTAG_MACADDR, (uint8_t *) mac_str); + + /* append ip address */ + inet_ntop(AF_INET, &loc_addr.sin_addr, loc_ip, sizeof(loc_ip)); + quirk_l16tv_put(msg, strlen(loc_ip) + 1, IPAC_IDTAG_IPADDR, (uint8_t *) loc_ip); + + /* append the serial number */ + quirk_l16tv_put(msg, strlen(ser_str) + 1, IPAC_IDTAG_SERNR, (uint8_t *) ser_str); + + /* abuse some flags */ + quirk_l16tv_put(msg, strlen(model_name) + 1, IPAC_IDTAG_UNIT, (uint8_t *) model_name); + + /* ip.access nanoBTS would reply to port==3006 */ + ipaccess_prepend_header_quirk(msg, IPAC_PROTO_IPACCESS); + rc = sendto(fd->fd, msg->data, msg->len, 0, (struct sockaddr *)src, sizeof(*src)); + if (rc != msg->len) + LOGP(DFIND, LOGL_ERROR, + "Failed to send with rc(%d) errno(%d)\n", rc, errno); +} + +static int ipaccess_bcast(struct osmo_fd *fd, unsigned int what) +{ + uint8_t data[2048]; + char src[INET_ADDRSTRLEN]; + struct sockaddr_in addr = {}; + socklen_t len = sizeof(addr); + int rc; + + rc = recvfrom(fd->fd, data, sizeof(data), 0, + (struct sockaddr *) &addr, &len); + if (rc <= 0) { + LOGP(DFIND, LOGL_ERROR, + "Failed to read from socket errno(%d)\n", errno); + return -1; + } + + LOGP(DFIND, LOGL_DEBUG, + "Received request from: %s size %d\n", + inet_ntop(AF_INET, &addr.sin_addr, src, sizeof(src)), rc); + + if (rc < 6) + return 0; + + if (data[2] != IPAC_PROTO_IPACCESS || data[4] != IPAC_MSGT_ID_GET) + return 0; + + respond_to(&addr, fd, data + 6, rc - 6); + return 0; +} + +int lc15bts_mgr_nl_init(void) +{ + int rc; + + nl_fd.cb = ipaccess_bcast; + rc = osmo_sock_init_ofd(&nl_fd, AF_INET, SOCK_DGRAM, IPPROTO_UDP, + "0.0.0.0", 3006, OSMO_SOCK_F_BIND); + if (rc < 0) { + perror("Socket creation"); + return -1; + } + + return 0; +} diff --git a/src/osmo-bts-lc15/misc/lc15bts_mgr_temp.c b/src/osmo-bts-lc15/misc/lc15bts_mgr_temp.c new file mode 100644 index 00000000..9665e1db --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_mgr_temp.c @@ -0,0 +1,378 @@ +/* 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 <inttypes.h> +#include "misc/lc15bts_mgr.h" +#include "misc/lc15bts_misc.h" +#include "misc/lc15bts_temp.h" +#include "misc/lc15bts_power.h" +#include "misc/lc15bts_led.h" +#include "misc/lc15bts_swd.h" +#include "limits.h" + +#include <osmo-bts/logging.h> + +#include <osmocom/core/timer.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/linuxlist.h> + +struct lc15bts_mgr_instance *s_mgr; +static struct osmo_timer_list sensor_ctrl_timer; + +static const struct value_string state_names[] = { + { STATE_NORMAL, "NORMAL" }, + { STATE_WARNING_HYST, "WARNING (HYST)" }, + { STATE_WARNING, "WARNING" }, + { STATE_CRITICAL, "CRITICAL" }, + { 0, NULL } +}; + +const char *lc15bts_mgr_sensor_get_state(enum lc15bts_sensor_state state) +{ + return get_value_string(state_names, state); +} + +static int next_state(enum lc15bts_sensor_state current_state, int critical, int warning) +{ + int next_state = -1; + switch (current_state) { + case STATE_NORMAL: + if (critical) + next_state = STATE_CRITICAL; + else if (warning) + next_state = STATE_WARNING; + break; + case STATE_WARNING_HYST: + if (critical) + next_state = STATE_CRITICAL; + else if (warning) + next_state = STATE_WARNING; + else + next_state = STATE_NORMAL; + break; + case STATE_WARNING: + if (critical) + next_state = STATE_CRITICAL; + else if (!warning) + next_state = STATE_WARNING_HYST; + break; + case STATE_CRITICAL: + if (!critical && !warning) + next_state = STATE_WARNING; + break; + }; + + return next_state; +} + +static void handle_normal_actions(int actions) +{ + /* switch on the PA */ + if (actions & SENSOR_ACT_NORM_PA0_ON) { + if (lc15bts_power_set(LC15BTS_POWER_PA0, 1) != 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to switch on the PA #0\n"); + } else { + LOGP(DTEMP, LOGL_NOTICE, + "Switched on the PA #0 as normal action.\n"); + } + } + + if (actions & SENSOR_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 & SENSOR_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 osmo-bts.service"); + } +} + +static void handle_actions(int actions) +{ + /* switch off the PA */ + if (actions & SENSOR_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 & SENSOR_ACT_PA0_OFF) { + if (lc15bts_power_set(LC15BTS_POWER_PA0, 0) != 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to switch off the PA #0. Stop BTS?\n"); + } else { + LOGP(DTEMP, LOGL_NOTICE, + "Switched off the PA #0 due temperature.\n"); + } + } + + if (actions & SENSOR_ACT_BTS_SRV_OFF) { + LOGP(DTEMP, LOGL_NOTICE, + "Going to switch off the BTS service\n"); + /* + * TODO: use/create something like nspawn that serializes + * and used SIGCHLD/waitpid to pick up the dead processes + * without invoking shell. + */ + system("/bin/systemctl stop osmo-bts.service"); + } +} + +/** + * 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 state.\n"); + handle_normal_actions(manager->state.action_norm); +} + +static void execute_warning_act(struct lc15bts_mgr_instance *manager) +{ + LOGP(DTEMP, LOGL_NOTICE, "System has reached warning state.\n"); + handle_actions(manager->state.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->state.action_crit); +} + +static void lc15bts_mgr_sensor_handle(struct lc15bts_mgr_instance *manager, + int critical, int warning) +{ + int new_state = next_state(manager->state.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->state.state), + get_value_string(state_names, new_state)); + manager->state.state = new_state; + switch (manager->state.state) { + case STATE_NORMAL: + execute_normal_act(manager); + 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 sensor_ctrl_check(struct lc15bts_mgr_instance *mgr) +{ + int rc; + int temp = 0; + 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, &temp); + 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 { + temp = temp / 1000; + if (temp > mgr->temp.supply_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.supply_temp_limit.thresh_crit_max) + 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, &temp); + 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 { + temp = temp / 1000; + if (temp > mgr->temp.soc_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.soc_temp_limit.thresh_crit_max) + 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, &temp); + 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 { + temp = temp / 1000; + if (temp > mgr->temp.fpga_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.fpga_temp_limit.thresh_crit_max) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "FPGA temperature is: %d\n", temp); + } + + /* Read the current RMS detector temperature */ + rc = lc15bts_temp_get(LC15BTS_TEMP_RMSDET, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to read the RMS detector temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.rmsdet_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.rmsdet_temp_limit.thresh_crit_max) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "RMS detector temperature is: %d\n", temp); + } + + /* Read the current OCXO temperature */ + rc = lc15bts_temp_get(LC15BTS_TEMP_OCXO, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to read the OCXO temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.ocxo_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.ocxo_temp_limit.thresh_crit_max) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "OCXO temperature is: %d\n", temp); + } + + /* Read the current TX #0 temperature */ + rc = lc15bts_temp_get(LC15BTS_TEMP_TX0, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to read the TX #0 temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.tx0_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.tx0_temp_limit.thresh_crit_max) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "TX #0 temperature is: %d\n", temp); + } + + /* Read the current TX #1 temperature */ + rc = lc15bts_temp_get(LC15BTS_TEMP_TX1, &temp); + 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 { + temp = temp / 1000; + if (temp > mgr->temp.tx1_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.tx1_temp_limit.thresh_crit_max) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "TX #1 temperature is: %d\n", temp); + } + + /* Read the current PA #0 temperature */ + rc = lc15bts_temp_get(LC15BTS_TEMP_PA0, &temp); + if (rc < 0) { + LOGP(DTEMP, LOGL_ERROR, + "Failed to read the PA #0 temperature. rc=%d\n", rc); + warn_thresh_passed = crit_thresh_passed = 1; + } else { + temp = temp / 1000; + if (temp > mgr->temp.pa0_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.pa0_temp_limit.thresh_crit_max) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "PA #0 temperature is: %d\n", temp); + } + + /* Read the current PA #1 temperature */ + rc = lc15bts_temp_get(LC15BTS_TEMP_PA1, &temp); + 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 { + temp = temp / 1000; + if (temp > mgr->temp.pa1_temp_limit.thresh_warn_max) + warn_thresh_passed = 1; + if (temp > mgr->temp.pa1_temp_limit.thresh_crit_max) + crit_thresh_passed = 1; + LOGP(DTEMP, LOGL_DEBUG, "PA #1 temperature is: %d\n", temp); + } + + lc15bts_mgr_sensor_handle(mgr, crit_thresh_passed, warn_thresh_passed); +} + +static void sensor_ctrl_check_cb(void *_data) +{ + struct lc15bts_mgr_instance *mgr = _data; + sensor_ctrl_check(mgr); + /* Check every minute? XXX make it configurable! */ + osmo_timer_schedule(&sensor_ctrl_timer, LC15BTS_SENSOR_TIMER_DURATION, 0); + LOGP(DTEMP, LOGL_DEBUG,"Check sensors timer expired\n"); + /* TODO: do we want to notify if some sensors could not be read? */ + lc15bts_swd_event(mgr, SWD_CHECK_TEMP_SENSOR); +} + +int lc15bts_mgr_sensor_init(struct lc15bts_mgr_instance *mgr) +{ + s_mgr = mgr; + sensor_ctrl_timer.cb = sensor_ctrl_check_cb; + sensor_ctrl_timer.data = s_mgr; + sensor_ctrl_check_cb(s_mgr); + return 0; +} diff --git a/src/osmo-bts-lc15/misc/lc15bts_mgr_vty.c b/src/osmo-bts-lc15/misc/lc15bts_mgr_vty.c new file mode 100644 index 00000000..424ff18f --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_mgr_vty.c @@ -0,0 +1,1074 @@ +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * sysmobts_mgr_vty.c + * (C) 2014 by sysmocom - s.f.m.c. GmbH + * + * All Rights Reserved + * + * Author: Alvaro Neira Ayuso <anayuso@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> +#include <ctype.h> +#include <string.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <inttypes.h> + +#include <osmocom/vty/vty.h> +#include <osmocom/vty/command.h> +#include <osmocom/vty/misc.h> + +#include <osmo-bts/logging.h> + +#include "lc15bts_misc.h" +#include "lc15bts_mgr.h" +#include "lc15bts_temp.h" +#include "lc15bts_power.h" +#include "lc15bts_led.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 int go_to_parent(struct vty *vty) +{ + switch (vty->node) { + case MGR_NODE: + vty->node = CONFIG_NODE; + break; + case ACT_NORM_NODE: + case ACT_WARN_NODE: + case ACT_CRIT_NODE: + case LIMIT_SUPPLY_TEMP_NODE: + case LIMIT_SOC_NODE: + case LIMIT_FPGA_NODE: + case LIMIT_RMSDET_NODE: + case LIMIT_OCXO_NODE: + case LIMIT_TX0_TEMP_NODE: + case LIMIT_TX1_TEMP_NODE: + case LIMIT_PA0_TEMP_NODE: + case LIMIT_PA1_TEMP_NODE: + case LIMIT_SUPPLY_VOLT_NODE: + case LIMIT_TX0_VSWR_NODE: + case LIMIT_TX1_VSWR_NODE: + case LIMIT_SUPPLY_PWR_NODE: + case LIMIT_PA0_PWR_NODE: + case LIMIT_PA1_PWR_NODE: + vty->node = MGR_NODE; + break; + default: + vty->node = CONFIG_NODE; + } + return vty->node; +} + +static int is_config_node(struct vty *vty, int node) +{ + switch (node) { + case MGR_NODE: + case ACT_NORM_NODE: + case ACT_WARN_NODE: + case ACT_CRIT_NODE: + case LIMIT_SUPPLY_TEMP_NODE: + case LIMIT_SOC_NODE: + case LIMIT_FPGA_NODE: + case LIMIT_RMSDET_NODE: + case LIMIT_OCXO_NODE: + case LIMIT_TX0_TEMP_NODE: + case LIMIT_TX1_TEMP_NODE: + case LIMIT_PA0_TEMP_NODE: + case LIMIT_PA1_TEMP_NODE: + case LIMIT_SUPPLY_VOLT_NODE: + case LIMIT_TX0_VSWR_NODE: + case LIMIT_TX1_VSWR_NODE: + case LIMIT_SUPPLY_PWR_NODE: + case LIMIT_PA0_PWR_NODE: + case LIMIT_PA1_PWR_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(actions-normal)# ", + 1, +}; + +static struct cmd_node act_warn_node = { + ACT_WARN_NODE, + "%s(actions-warn)# ", + 1, +}; + +static struct cmd_node act_crit_node = { + ACT_CRIT_NODE, + "%s(actions-critical)# ", + 1, +}; + +static struct cmd_node limit_supply_temp_node = { + LIMIT_SUPPLY_TEMP_NODE, + "%s(limit-supply-temp)# ", + 1, +}; + +static struct cmd_node limit_soc_node = { + LIMIT_SOC_NODE, + "%s(limit-soc)# ", + 1, +}; + +static struct cmd_node limit_fpga_node = { + LIMIT_FPGA_NODE, + "%s(limit-fpga)# ", + 1, +}; + +static struct cmd_node limit_rmsdet_node = { + LIMIT_RMSDET_NODE, + "%s(limit-rmsdet)# ", + 1, +}; + +static struct cmd_node limit_ocxo_node = { + LIMIT_OCXO_NODE, + "%s(limit-ocxo)# ", + 1, +}; + +static struct cmd_node limit_tx0_temp_node = { + LIMIT_TX0_TEMP_NODE, + "%s(limit-tx0-temp)# ", + 1, +}; +static struct cmd_node limit_tx1_temp_node = { + LIMIT_TX1_TEMP_NODE, + "%s(limit-tx1-temp)# ", + 1, +}; +static struct cmd_node limit_pa0_temp_node = { + LIMIT_PA0_TEMP_NODE, + "%s(limit-pa0-temp)# ", + 1, +}; +static struct cmd_node limit_pa1_temp_node = { + LIMIT_PA1_TEMP_NODE, + "%s(limit-pa1-temp)# ", + 1, +}; +static struct cmd_node limit_supply_volt_node = { + LIMIT_SUPPLY_VOLT_NODE, + "%s(limit-supply-volt)# ", + 1, +}; +static struct cmd_node limit_tx0_vswr_node = { + LIMIT_TX0_VSWR_NODE, + "%s(limit-tx0-vswr)# ", + 1, +}; +static struct cmd_node limit_tx1_vswr_node = { + LIMIT_TX1_VSWR_NODE, + "%s(limit-tx1-vswr)# ", + 1, +}; +static struct cmd_node limit_supply_pwr_node = { + LIMIT_SUPPLY_PWR_NODE, + "%s(limit-supply-pwr)# ", + 1, +}; +static struct cmd_node limit_pa0_pwr_node = { + LIMIT_PA0_PWR_NODE, + "%s(limit-pa0-pwr)# ", + 1, +}; +static struct cmd_node limit_pa1_pwr_node = { + LIMIT_PA1_PWR_NODE, + "%s(limit-pa1-pwr)# ", + 1, +}; + +static struct cmd_node limit_gps_fix_node = { + LIMIT_GPS_FIX_NODE, + "%s(limit-gps-fix)# ", + 1, +}; + +DEFUN(cfg_mgr, cfg_mgr_cmd, + "lc15bts-mgr", + MGR_STR) +{ + vty->node = MGR_NODE; + return CMD_SUCCESS; +} + +static void write_volt_limit(struct vty *vty, const char *name, + struct lc15bts_volt_limit *limit) +{ + vty_out(vty, " %s%s", name, VTY_NEWLINE); + vty_out(vty, " threshold warning min %d%s", + limit->thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " threshold critical min %d%s", + limit->thresh_crit_min, VTY_NEWLINE); +} + +static void write_vswr_limit(struct vty *vty, const char *name, + struct lc15bts_vswr_limit *limit) +{ + vty_out(vty, " %s%s", name, VTY_NEWLINE); + vty_out(vty, " threshold warning max %d%s", + limit->thresh_warn_max, VTY_NEWLINE); +} + +static void write_pwr_limit(struct vty *vty, const char *name, + struct lc15bts_pwr_limit *limit) +{ + vty_out(vty, " %s%s", name, VTY_NEWLINE); + vty_out(vty, " threshold warning max %d%s", + limit->thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " threshold critical max %d%s", + limit->thresh_crit_max, VTY_NEWLINE); +} + +static void write_norm_action(struct vty *vty, const char *name, int actions) +{ + vty_out(vty, " %s%s", name, VTY_NEWLINE); + vty_out(vty, " %spa0-on%s", + (actions & SENSOR_ACT_NORM_PA0_ON) ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %spa1-on%s", + (actions & SENSOR_ACT_NORM_PA1_ON) ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %sbts-service-on%s", + (actions & SENSOR_ACT_NORM_BTS_SRV_ON) ? "" : "no ", VTY_NEWLINE); +} + +static void write_action(struct vty *vty, const char *name, int actions) +{ + vty_out(vty, " %s%s", name, VTY_NEWLINE); + vty_out(vty, " %spa0-off%s", + (actions & SENSOR_ACT_PA0_OFF) ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %spa1-off%s", + (actions & SENSOR_ACT_PA1_OFF) ? "" : "no ", VTY_NEWLINE); + vty_out(vty, " %sbts-service-off%s", + (actions & SENSOR_ACT_BTS_SRV_OFF) ? "" : "no ", VTY_NEWLINE); +} + +static int config_write_mgr(struct vty *vty) +{ + vty_out(vty, "lc15bts-mgr%s", VTY_NEWLINE); + + write_volt_limit(vty, "limits supply_volt", &s_mgr->volt.supply_volt_limit); + write_pwr_limit(vty, "limits supply_pwr", &s_mgr->pwr.supply_pwr_limit); + write_vswr_limit(vty, "limits tx0_vswr", &s_mgr->vswr.tx0_vswr_limit); + write_vswr_limit(vty, "limits tx1_vswr", &s_mgr->vswr.tx1_vswr_limit); + + write_norm_action(vty, "actions normal", s_mgr->state.action_norm); + write_action(vty, "actions warn", s_mgr->state.action_warn); + write_action(vty, "actions critical", s_mgr->state.action_crit); + + return CMD_SUCCESS; +} + +static int config_write_dummy(struct vty *vty) +{ + return CMD_SUCCESS; +} + +#define CFG_LIMIT_TEMP(name, expl, switch_to, variable) \ +DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \ + "limits " #name, \ + "Configure Limits\n" expl) \ +{ \ + vty->node = switch_to; \ + vty->index = &s_mgr->temp.variable; \ + return CMD_SUCCESS; \ +} + +CFG_LIMIT_TEMP(supply_temp, "SUPPLY TEMP\n", LIMIT_SUPPLY_TEMP_NODE, supply_temp_limit) +CFG_LIMIT_TEMP(soc_temp, "SOC TEMP\n", LIMIT_SOC_NODE, soc_temp_limit) +CFG_LIMIT_TEMP(fpga_temp, "FPGA TEMP\n", LIMIT_FPGA_NODE, fpga_temp_limit) +CFG_LIMIT_TEMP(rmsdet_temp, "RMSDET TEMP\n", LIMIT_RMSDET_NODE, rmsdet_temp_limit) +CFG_LIMIT_TEMP(ocxo_temp, "OCXO TEMP\n", LIMIT_OCXO_NODE, ocxo_temp_limit) +CFG_LIMIT_TEMP(tx0_temp, "TX0 TEMP\n", LIMIT_TX0_TEMP_NODE, tx0_temp_limit) +CFG_LIMIT_TEMP(tx1_temp, "TX1 TEMP\n", LIMIT_TX1_TEMP_NODE, tx1_temp_limit) +CFG_LIMIT_TEMP(pa0_temp, "PA0 TEMP\n", LIMIT_PA0_TEMP_NODE, pa0_temp_limit) +CFG_LIMIT_TEMP(pa1_temp, "PA1 TEMP\n", LIMIT_PA1_TEMP_NODE, pa1_temp_limit) +#undef CFG_LIMIT_TEMP + +#define CFG_LIMIT_VOLT(name, expl, switch_to, variable) \ +DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \ + "limits " #name, \ + "Configure Limits\n" expl) \ +{ \ + vty->node = switch_to; \ + vty->index = &s_mgr->volt.variable; \ + return CMD_SUCCESS; \ +} + +CFG_LIMIT_VOLT(supply_volt, "SUPPLY VOLT\n", LIMIT_SUPPLY_VOLT_NODE, supply_volt_limit) +#undef CFG_LIMIT_VOLT + +#define CFG_LIMIT_VSWR(name, expl, switch_to, variable) \ +DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \ + "limits " #name, \ + "Configure Limits\n" expl) \ +{ \ + vty->node = switch_to; \ + vty->index = &s_mgr->vswr.variable; \ + return CMD_SUCCESS; \ +} + +CFG_LIMIT_VSWR(tx0_vswr, "TX0 VSWR\n", LIMIT_TX0_VSWR_NODE, tx0_vswr_limit) +CFG_LIMIT_VSWR(tx1_vswr, "TX1 VSWR\n", LIMIT_TX1_VSWR_NODE, tx1_vswr_limit) +#undef CFG_LIMIT_VSWR + +#define CFG_LIMIT_PWR(name, expl, switch_to, variable) \ +DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \ + "limits " #name, \ + "Configure Limits\n" expl) \ +{ \ + vty->node = switch_to; \ + vty->index = &s_mgr->pwr.variable; \ + return CMD_SUCCESS; \ +} + +CFG_LIMIT_PWR(supply_pwr, "SUPPLY PWR\n", LIMIT_SUPPLY_PWR_NODE, supply_pwr_limit) +CFG_LIMIT_PWR(pa0_pwr, "PA0 PWR\n", LIMIT_PA0_PWR_NODE, pa0_pwr_limit) +CFG_LIMIT_PWR(pa1_pwr, "PA1 PWR\n", LIMIT_PA1_PWR_NODE, pa1_pwr_limit) +#undef CFG_LIMIT_PWR + +#define CFG_LIMIT_GPS_FIX(name, expl, switch_to, variable) \ +DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \ + "limits " #name, \ + "Configure Limits\n" expl) \ +{ \ + vty->node = switch_to; \ + vty->index = &s_mgr->gps.variable; \ + return CMD_SUCCESS; \ +} + +CFG_LIMIT_GPS_FIX(gps_fix, "GPS FIX\n", LIMIT_GPS_FIX_NODE, gps_fix_limit) +#undef CFG_LIMIT_GPS_FIX + +DEFUN(cfg_limit_volt_warn_min, cfg_thresh_volt_warn_min_cmd, + "threshold warning min <0-48000>", + "Threshold to reach\n" "Warning level\n" "Range\n") +{ + struct lc15bts_volt_limit *limit = vty->index; + limit->thresh_warn_min = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_limit_volt_crit_min, cfg_thresh_volt_crit_min_cmd, + "threshold critical min <0-48000>", + "Threshold to reach\n" "Critical level\n" "Range\n") +{ + struct lc15bts_volt_limit *limit = vty->index; + limit->thresh_crit_min = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_limit_vswr_warn_max, cfg_thresh_vswr_warn_max_cmd, + "threshold warning max <1000-200000>", + "Threshold to reach\n" "Warning level\n" "Range\n") +{ + struct lc15bts_vswr_limit *limit = vty->index; + limit->thresh_warn_max = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_limit_vswr_crit_max, cfg_thresh_vswr_crit_max_cmd, + "threshold critical max <1000-200000>", + "Threshold to reach\n" "Critical level\n" "Range\n") +{ + struct lc15bts_vswr_limit *limit = vty->index; + limit->thresh_crit_max = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_limit_pwr_warn_max, cfg_thresh_pwr_warn_max_cmd, + "threshold warning max <0-200>", + "Threshold to reach\n" "Warning level\n" "Range\n") +{ + struct lc15bts_pwr_limit *limit = vty->index; + limit->thresh_warn_max = atoi(argv[0]); + return CMD_SUCCESS; +} + +DEFUN(cfg_limit_pwr_crit_max, cfg_thresh_pwr_crit_max_cmd, + "threshold critical max <0-200>", + "Threshold to reach\n" "Critical level\n" "Range\n") +{ + struct lc15bts_pwr_limit *limit = vty->index; + limit->thresh_crit_max = atoi(argv[0]); + return CMD_SUCCESS; +} + +#define CFG_ACTION(name, expl, switch_to, variable) \ +DEFUN(cfg_action_##name, cfg_action_##name##_cmd, \ + "actions " #name, \ + "Configure Actions\n" expl) \ +{ \ + vty->node = switch_to; \ + vty->index = &s_mgr->state.variable; \ + return CMD_SUCCESS; \ +} +CFG_ACTION(normal, "Normal Actions\n", ACT_NORM_NODE, action_norm) +CFG_ACTION(warn, "Warning Actions\n", ACT_WARN_NODE, action_warn) +CFG_ACTION(critical, "Critical Actions\n", ACT_CRIT_NODE, action_crit) +#undef CFG_ACTION + +DEFUN(cfg_action_pa0_on, cfg_action_pa0_on_cmd, + "pa0-on", + "Switch the Power Amplifier #0 on\n") +{ + int *action = vty->index; + *action |= SENSOR_ACT_NORM_PA0_ON; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_action_pa0_on, cfg_no_action_pa0_on_cmd, + "no pa0-on", + NO_STR "Switch the Power Amplifieri #0 on\n") +{ + int *action = vty->index; + *action &= ~SENSOR_ACT_NORM_PA0_ON; + return CMD_SUCCESS; +} + +DEFUN(cfg_action_pa1_on, cfg_action_pa1_on_cmd, + "pa1-on", + "Switch the Power Amplifier #1 on\n") +{ + int *action = vty->index; + *action |= SENSOR_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 &= ~SENSOR_ACT_NORM_PA1_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 |= SENSOR_ACT_NORM_BTS_SRV_ON; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_action_bts_srv_on, cfg_no_action_bts_srv_on_cmd, + "no bts-service-on", + NO_STR "Start the systemd lc15bts.service\n") +{ + int *action = vty->index; + *action &= ~SENSOR_ACT_NORM_BTS_SRV_ON; + return CMD_SUCCESS; +} + +DEFUN(cfg_action_pa0_off, cfg_action_pa0_off_cmd, + "pa0-off", + "Switch the Power Amplifier #0 off\n") +{ + int *action = vty->index; + *action |= SENSOR_ACT_PA0_OFF; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_action_pa0_off, cfg_no_action_pa0_off_cmd, + "no pa0-off", + NO_STR "Do not switch off the Power Amplifier #0\n") +{ + int *action = vty->index; + *action &= ~SENSOR_ACT_PA0_OFF; + 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 |= SENSOR_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 &= ~SENSOR_ACT_PA1_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 |= SENSOR_ACT_BTS_SRV_OFF; + return CMD_SUCCESS; +} + +DEFUN(cfg_no_action_bts_srv_off, cfg_no_action_bts_srv_off_cmd, + "no bts-service-off", + NO_STR "Stop the systemd lc15bts.service\n") +{ + int *action = vty->index; + *action &= ~SENSOR_ACT_BTS_SRV_OFF; + return CMD_SUCCESS; +} + +DEFUN(show_mgr, show_mgr_cmd, "show manager", + SHOW_STR "Display information about the manager") +{ + int temp, volt, current, power, vswr; + vty_out(vty, "Warning alarm flags: 0x%08x%s", + s_mgr->lc15bts_ctrl.warn_flags, VTY_NEWLINE); + vty_out(vty, "Critical alarm flags: 0x%08x%s", + s_mgr->lc15bts_ctrl.crit_flags, VTY_NEWLINE); + vty_out(vty, "Preventive action retried: %d%s", + s_mgr->alarms.preventive_retry, VTY_NEWLINE); + vty_out(vty, "Temperature control state: %s%s", + lc15bts_mgr_sensor_get_state(s_mgr->state.state), VTY_NEWLINE); + vty_out(vty, "Current Temperatures%s", VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_SUPPLY, &temp); + vty_out(vty, " Main Supply : %4.2f Celsius%s", + temp/ 1000.0f, + VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_SOC, &temp); + vty_out(vty, " SoC : %4.2f Celsius%s", + temp / 1000.0f, + VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_FPGA, &temp); + vty_out(vty, " FPGA : %4.2f Celsius%s", + temp / 1000.0f, + VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_RMSDET, &temp); + vty_out(vty, " RMSDet : %4.2f Celsius%s", + temp / 1000.0f, + VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_OCXO, &temp); + vty_out(vty, " OCXO : %4.2f Celsius%s", + temp / 1000.0f, + VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_TX0, &temp); + vty_out(vty, " TX 0 : %4.2f Celsius%s", + temp / 1000.0f, + VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_TX1, &temp); + vty_out(vty, " TX 1 : %4.2f Celsius%s", + temp / 1000.0f, + VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_PA0, &temp); + vty_out(vty, " Power Amp #0: %4.2f Celsius%s", + temp / 1000.0f, + VTY_NEWLINE); + lc15bts_temp_get(LC15BTS_TEMP_PA1, &temp); + vty_out(vty, " Power Amp #1: %4.2f Celsius%s", + temp / 1000.0f, + VTY_NEWLINE); + + vty_out(vty, "Power Status%s", VTY_NEWLINE); + lc15bts_power_sensor_get(LC15BTS_POWER_SUPPLY, + LC15BTS_POWER_VOLTAGE, &volt); + lc15bts_power_sensor_get(LC15BTS_POWER_SUPPLY, + LC15BTS_POWER_CURRENT, ¤t); + lc15bts_power_sensor_get(LC15BTS_POWER_SUPPLY, + LC15BTS_POWER_POWER, &power); + vty_out(vty, " Main Supply : ON [%6.2f Vdc, %4.2f A, %6.2f W]%s", + volt /1000.0f, + current /1000.0f, + power /1000000.0f, + VTY_NEWLINE); + lc15bts_power_sensor_get(LC15BTS_POWER_PA0, + LC15BTS_POWER_VOLTAGE, &volt); + lc15bts_power_sensor_get(LC15BTS_POWER_PA0, + LC15BTS_POWER_CURRENT, ¤t); + lc15bts_power_sensor_get(LC15BTS_POWER_PA0, + LC15BTS_POWER_POWER, &power); + vty_out(vty, " Power Amp #0: %s [%6.2f Vdc, %4.2f A, %6.2f W]%s", + lc15bts_power_get(LC15BTS_POWER_PA0) ? "ON " : "OFF", + volt /1000.0f, + current /1000.0f, + power /1000000.0f, + VTY_NEWLINE); + lc15bts_power_sensor_get(LC15BTS_POWER_PA1, + LC15BTS_POWER_VOLTAGE, &volt); + lc15bts_power_sensor_get(LC15BTS_POWER_PA1, + LC15BTS_POWER_CURRENT, ¤t); + lc15bts_power_sensor_get(LC15BTS_POWER_PA1, + LC15BTS_POWER_POWER, &power); + 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", + volt /1000.0f, + current /1000.0f, + power /1000000.0f, + VTY_NEWLINE); + vty_out(vty, "VSWR Status%s", VTY_NEWLINE); + lc15bts_vswr_get(LC15BTS_VSWR_TX0, &vswr); + vty_out(vty, " VSWR TX 0: %f %s", + vswr / 1000.0f, + VTY_NEWLINE); + lc15bts_vswr_get(LC15BTS_VSWR_TX1, &vswr); + vty_out(vty, " VSWR TX 1: %f %s", + vswr / 1000.0f, + VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(show_thresh, show_thresh_cmd, "show thresholds", + SHOW_STR "Display information about the thresholds") +{ + vty_out(vty, "Temperature limits (Celsius)%s", VTY_NEWLINE); + vty_out(vty, " Main supply%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.supply_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.supply_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.supply_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " SoC%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.soc_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.soc_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.soc_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " FPGA%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.fpga_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.fpga_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.fpga_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " RMSDet%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " OCXO%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " TX0%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.tx0_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.tx0_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.tx0_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " TX1%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.tx1_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.tx1_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.tx1_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " PA0%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.pa0_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.pa0_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.pa0_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " PA1%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->temp.pa1_temp_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->temp.pa1_temp_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->temp.pa1_temp_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, "Power limits%s", VTY_NEWLINE); + vty_out(vty, " Main supply (mV)%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->volt.supply_volt_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->volt.supply_volt_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " Warning min : %d%s",s_mgr->volt.supply_volt_limit.thresh_warn_min, VTY_NEWLINE); + vty_out(vty, " Critical min : %d%s",s_mgr->volt.supply_volt_limit.thresh_crit_min, VTY_NEWLINE); + vty_out(vty, " Main supply power (W)%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->pwr.supply_pwr_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->pwr.supply_pwr_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " PA0 power (W)%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->pwr.pa0_pwr_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->pwr.pa0_pwr_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " PA1 power (W)%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->pwr.pa1_pwr_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->pwr.pa1_pwr_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, "VSWR limits%s", VTY_NEWLINE); + vty_out(vty, " TX0%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->vswr.tx0_vswr_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->vswr.tx0_vswr_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, " TX1%s", VTY_NEWLINE); + vty_out(vty, " Critical max : %d%s",s_mgr->vswr.tx1_vswr_limit.thresh_crit_max, VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->vswr.tx1_vswr_limit.thresh_warn_max, VTY_NEWLINE); + vty_out(vty, "Days since last GPS 3D fix%s", VTY_NEWLINE); + vty_out(vty, " Warning max : %d%s",s_mgr->gps.gps_fix_limit.thresh_warn_max, VTY_NEWLINE); + + return CMD_SUCCESS; +} + +DEFUN(calibrate_clock, calibrate_clock_cmd, + "calibrate clock", + "Calibration commands\n" + "Calibrate clock against GPS PPS\n") +{ + if (lc15bts_mgr_calib_run(s_mgr) < 0) { + vty_out(vty, "%%Failed to start calibration.%s", VTY_NEWLINE); + return CMD_WARNING; + } + return CMD_SUCCESS; +} + +DEFUN(set_led_pattern, set_led_pattern_cmd, + "set led pattern <0-255>", + "Set LED pattern\n" + "Set LED pattern for debugging purpose only. This pattern will be overridden after 60 seconds by LED pattern of actual system state\n") +{ + int pattern_id = atoi(argv[0]); + + if ((pattern_id < 0) || (pattern_id > BLINK_PATTERN_MAX_ITEM)) { + vty_out(vty, "%%Invalid LED pattern ID. It must be in range of %d..%d %s", 0, BLINK_PATTERN_MAX_ITEM - 1, VTY_NEWLINE); + return CMD_WARNING; + } + + led_set(s_mgr, pattern_id); + return CMD_SUCCESS; +} + +DEFUN(force_mgr_state, force_mgr_state_cmd, + "force manager state <0-255>", + "Force BTS manager state\n" + "Force BTS manager state for debugging purpose only\n") +{ + int state = atoi(argv[0]); + + if ((state < 0) || (state > STATE_CRITICAL)) { + vty_out(vty, "%%Invalid BTS manager state. It must be in range of %d..%d %s", 0, STATE_CRITICAL, VTY_NEWLINE); + return CMD_WARNING; + } + + s_mgr->state.state = state; + return CMD_SUCCESS; +} + +#define LIMIT_TEMP(name, limit, expl, variable, criticity, min_max) \ +DEFUN(limit_temp_##name##_##variable, limit_temp_##name##_##variable##_cmd, \ + "limit temp " #name " " #criticity " " #min_max " <-200-200>", \ + "Limit to reach\n" expl) \ +{ \ + s_mgr->temp.limit.variable = atoi(argv[0]); \ + return CMD_SUCCESS; \ +} + +LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(tx0, tx0_temp_limit, "TX0 TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(tx0, tx0_temp_limit, "TX0 TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(tx0, tx0_temp_limit, "TX0 TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(tx1, tx1_temp_limit, "TX1 TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(tx1, tx1_temp_limit, "TX1 TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(tx1, tx1_temp_limit, "TX1 TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(pa0, pa0_temp_limit, "PA0 TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(pa0, pa0_temp_limit, "PA0 TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(pa0, pa0_temp_limit, "PA0 TEMP\n", thresh_warn_min, warning, min) +LIMIT_TEMP(pa1, pa1_temp_limit, "PA1 TEMP\n", thresh_warn_max, warning, max) +LIMIT_TEMP(pa1, pa1_temp_limit, "PA1 TEMP\n", thresh_crit_max, critical, max) +LIMIT_TEMP(pa1, pa1_temp_limit, "PA1 TEMP\n", thresh_warn_min, warning, min) +#undef LIMIT_TEMP + +#define LIMIT_VOLT(name, limit, expl, variable, criticity, min_max) \ +DEFUN(limit_volt_##name##_##variable, limit_volt_##name##_##variable##_cmd, \ + "limit " #name " " #criticity " " #min_max " <0-48000>", \ + "Limit to reach\n" expl) \ +{ \ + s_mgr->volt.limit.variable = atoi(argv[0]); \ + return CMD_SUCCESS; \ +} + +LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_warn_max, warning, max) +LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_crit_max, critical, max) +LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_warn_min, warning, min) +LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_crit_min, critical, min) +#undef LIMIT_VOLT + +#define LIMIT_PWR(name, limit, expl, variable, criticity, min_max) \ + DEFUN(limit_pwr_##name##_##variable, limit_pwr_##name##_##variable##_cmd, \ + "limit power " #name " " #criticity " " #min_max " <0-200>", \ + "Limit to reach\n" expl) \ +{ \ + s_mgr->pwr.limit.variable = atoi(argv[0]); \ + return CMD_SUCCESS; \ +} + +LIMIT_PWR(supply, supply_pwr_limit, "SUPPLY PWR\n", thresh_warn_max, warning, max) +LIMIT_PWR(supply, supply_pwr_limit, "SUPPLY PWR\n", thresh_crit_max, critical, max) +LIMIT_PWR(pa0, pa0_pwr_limit, "PA0 PWR\n", thresh_warn_max, warning, max) +LIMIT_PWR(pa0, pa0_pwr_limit, "PA0 PWR\n", thresh_crit_max, critical, max) +LIMIT_PWR(pa1, pa1_pwr_limit, "PA1 PWR\n", thresh_warn_max, warning, max) +LIMIT_PWR(pa1, pa1_pwr_limit, "PA1 PWR\n", thresh_crit_max, critical, max) +#undef LIMIT_PWR + +#define LIMIT_VSWR(name, limit, expl, variable, criticity, min_max) \ +DEFUN(limit_vswr_##name##_##variable, limit_vswr_##name##_##variable##_cmd, \ + "limit vswr " #name " " #criticity " " #min_max " <1000-200000>", \ + "Limit to reach\n" expl) \ +{ \ + s_mgr->vswr.limit.variable = atoi(argv[0]); \ + return CMD_SUCCESS; \ +} + +LIMIT_VSWR(tx0, tx0_vswr_limit, "TX0 VSWR\n", thresh_warn_max, warning, max) +LIMIT_VSWR(tx0, tx0_vswr_limit, "TX0 VSWR\n", thresh_crit_max, critical, max) +LIMIT_VSWR(tx1, tx1_vswr_limit, "TX1 VSWR\n", thresh_warn_max, warning, max) +LIMIT_VSWR(tx1, tx1_vswr_limit, "TX1 VSWR\n", thresh_crit_max, critical, max) +#undef LIMIT_VSWR + +#define LIMIT_GPSFIX(limit, expl, variable, criticity, min_max) \ +DEFUN(limit_gpsfix_##variable, limit_gpsfix_##variable##_cmd, \ + "limit gpsfix " #criticity " " #min_max " <0-365>", \ + "Limit to reach\n" expl) \ +{ \ + s_mgr->gps.limit.variable = atoi(argv[0]); \ + return CMD_SUCCESS; \ +} + +LIMIT_GPSFIX(gps_fix_limit, "GPS FIX\n", thresh_warn_max, warning, max) +#undef LIMIT_GPSFIX + +static void register_limit(int limit, uint32_t unit) +{ + switch (unit) { + case MGR_LIMIT_TYPE_VOLT: + install_element(limit, &cfg_thresh_volt_warn_min_cmd); + install_element(limit, &cfg_thresh_volt_crit_min_cmd); + break; + case MGR_LIMIT_TYPE_VSWR: + install_element(limit, &cfg_thresh_vswr_warn_max_cmd); + install_element(limit, &cfg_thresh_vswr_crit_max_cmd); + break; + case MGR_LIMIT_TYPE_PWR: + install_element(limit, &cfg_thresh_pwr_warn_max_cmd); + install_element(limit, &cfg_thresh_pwr_crit_max_cmd); + break; + default: + break; + } +} + +static void register_normal_action(int act) +{ + install_element(act, &cfg_action_pa0_on_cmd); + install_element(act, &cfg_no_action_pa0_on_cmd); + install_element(act, &cfg_action_pa1_on_cmd); + install_element(act, &cfg_no_action_pa1_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_pa0_off_cmd); + install_element(act, &cfg_no_action_pa0_off_cmd); + install_element(act, &cfg_action_pa1_off_cmd); + install_element(act, &cfg_no_action_pa1_off_cmd); + install_element(act, &cfg_action_bts_srv_off_cmd); + install_element(act, &cfg_no_action_bts_srv_off_cmd); +} + +static void register_hidden_commands() +{ + install_element(ENABLE_NODE, &limit_temp_supply_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_supply_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_supply_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_soc_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_soc_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_soc_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_fpga_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_fpga_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_fpga_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_tx0_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_tx0_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_tx0_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_tx1_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_tx1_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_tx1_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_pa0_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_pa0_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_pa0_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_temp_pa1_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_temp_pa1_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_temp_pa1_thresh_warn_min_cmd); + + install_element(ENABLE_NODE, &limit_volt_supply_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_volt_supply_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_volt_supply_thresh_warn_min_cmd); + install_element(ENABLE_NODE, &limit_volt_supply_thresh_crit_min_cmd); + + install_element(ENABLE_NODE, &limit_pwr_supply_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_pwr_supply_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_pwr_pa0_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_pwr_pa0_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_pwr_pa1_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_pwr_pa1_thresh_crit_max_cmd); + + install_element(ENABLE_NODE, &limit_vswr_tx0_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_vswr_tx0_thresh_crit_max_cmd); + install_element(ENABLE_NODE, &limit_vswr_tx1_thresh_warn_max_cmd); + install_element(ENABLE_NODE, &limit_vswr_tx1_thresh_crit_max_cmd); + + install_element(ENABLE_NODE, &limit_gpsfix_thresh_warn_max_cmd); +} + +int lc15bts_mgr_vty_init(void) +{ + vty_init(&vty_info); + + install_element_ve(&show_mgr_cmd); + install_element_ve(&show_thresh_cmd); + + install_element(ENABLE_NODE, &calibrate_clock_cmd); + + install_node(&mgr_node, config_write_mgr); + install_element(CONFIG_NODE, &cfg_mgr_cmd); + + /* install the limit nodes */ + install_node(&limit_supply_temp_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_supply_temp_cmd); + + install_node(&limit_soc_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_soc_temp_cmd); + + install_node(&limit_fpga_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_fpga_temp_cmd); + + install_node(&limit_rmsdet_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_rmsdet_temp_cmd); + + install_node(&limit_ocxo_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_ocxo_temp_cmd); + + install_node(&limit_tx0_temp_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_tx0_temp_cmd); + + install_node(&limit_tx1_temp_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_tx1_temp_cmd); + + install_node(&limit_pa0_temp_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_pa0_temp_cmd); + + install_node(&limit_pa1_temp_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_pa1_temp_cmd); + + install_node(&limit_supply_volt_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_supply_volt_cmd); + register_limit(LIMIT_SUPPLY_VOLT_NODE, MGR_LIMIT_TYPE_VOLT); + + install_node(&limit_tx0_vswr_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_tx0_vswr_cmd); + register_limit(LIMIT_TX0_VSWR_NODE, MGR_LIMIT_TYPE_VSWR); + + install_node(&limit_tx1_vswr_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_tx1_vswr_cmd); + register_limit(LIMIT_TX1_VSWR_NODE, MGR_LIMIT_TYPE_VSWR); + + install_node(&limit_supply_pwr_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_supply_pwr_cmd); + register_limit(LIMIT_SUPPLY_PWR_NODE, MGR_LIMIT_TYPE_PWR); + + install_node(&limit_pa0_pwr_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_pa0_pwr_cmd); + register_limit(LIMIT_PA0_PWR_NODE, MGR_LIMIT_TYPE_PWR); + + install_node(&limit_pa1_pwr_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_pa1_pwr_cmd); + register_limit(LIMIT_PA1_PWR_NODE, MGR_LIMIT_TYPE_PWR); + + install_node(&limit_gps_fix_node, config_write_dummy); + install_element(MGR_NODE, &cfg_limit_gps_fix_cmd); + + /* 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); + + install_node(&act_crit_node, config_write_dummy); + install_element(MGR_NODE, &cfg_action_critical_cmd); + register_action(ACT_CRIT_NODE); + + /* install LED pattern command for debugging purpose */ + install_element_ve(&set_led_pattern_cmd); + install_element_ve(&force_mgr_state_cmd); + + register_hidden_commands(); + + return 0; +} + +int lc15bts_mgr_parse_config(struct lc15bts_mgr_instance *manager) +{ + int rc; + + s_mgr = manager; + rc = vty_read_config_file(s_mgr->config_file, NULL); + if (rc < 0) { + fprintf(stderr, "Failed to parse the config file: '%s'\n", + s_mgr->config_file); + return rc; + } + + return 0; +} diff --git a/src/osmo-bts-lc15/misc/lc15bts_misc.c b/src/osmo-bts-lc15/misc/lc15bts_misc.c new file mode 100644 index 00000000..467b9d0a --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_misc.c @@ -0,0 +1,383 @@ +/* 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 "lc15bts_mgr.h" +#include "btsconfig.h" +#include "lc15bts_misc.h" +#include "lc15bts_par.h" +#include "lc15bts_mgr.h" +#include "lc15bts_temp.h" +#include "lc15bts_power.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_temp", + .has_max = 1, + .sensor = LC15BTS_TEMP_SUPPLY, + .ee_par = LC15BTS_PAR_TEMP_SUPPLY_MAX, + }, { + .name = "soc_temp", + .has_max = 0, + .sensor = LC15BTS_TEMP_SOC, + .ee_par = LC15BTS_PAR_TEMP_SOC_MAX, + }, { + .name = "fpga_temp", + .has_max = 0, + .sensor = LC15BTS_TEMP_FPGA, + .ee_par = LC15BTS_PAR_TEMP_FPGA_MAX, + + }, { + .name = "rmsdet_temp", + .has_max = 1, + .sensor = LC15BTS_TEMP_RMSDET, + .ee_par = LC15BTS_PAR_TEMP_RMSDET_MAX, + }, { + .name = "ocxo_temp", + .has_max = 1, + .sensor = LC15BTS_TEMP_OCXO, + .ee_par = LC15BTS_PAR_TEMP_OCXO_MAX, + }, { + .name = "tx0_temp", + .has_max = 0, + .sensor = LC15BTS_TEMP_TX0, + .ee_par = LC15BTS_PAR_TEMP_TX0_MAX, + }, { + .name = "tx1_temp", + .has_max = 0, + .sensor = LC15BTS_TEMP_TX1, + .ee_par = LC15BTS_PAR_TEMP_TX1_MAX, + }, { + .name = "pa0_temp", + .has_max = 1, + .sensor = LC15BTS_TEMP_PA0, + .ee_par = LC15BTS_PAR_TEMP_PA0_MAX, + }, { + .name = "pa1_temp", + .has_max = 1, + .sensor = LC15BTS_TEMP_PA1, + .ee_par = LC15BTS_PAR_TEMP_PA1_MAX, + } +}; + +static const struct { + const char *name; + int has_max; + enum lc15bts_power_source sensor_source; + enum lc15bts_power_type sensor_type; + enum lc15bts_par ee_par; +} power_data[] = { + { + .name = "supply_volt", + .has_max = 1, + .sensor_source = LC15BTS_POWER_SUPPLY, + .sensor_type = LC15BTS_POWER_VOLTAGE, + .ee_par = LC15BTS_PAR_VOLT_SUPPLY_MAX, + }, { + .name = "supply_pwr", + .has_max = 1, + .sensor_source = LC15BTS_POWER_SUPPLY, + .sensor_type = LC15BTS_POWER_POWER, + .ee_par = LC15BTS_PAR_PWR_SUPPLY_MAX, + }, { + .name = "pa0_pwr", + .has_max = 1, + .sensor_source = LC15BTS_POWER_PA0, + .sensor_type = LC15BTS_POWER_POWER, + .ee_par = LC15BTS_PAR_PWR_PA0_MAX, + }, { + .name = "pa1_pwr", + .has_max = 1, + .sensor_source = LC15BTS_POWER_PA1, + .sensor_type = LC15BTS_POWER_POWER, + .ee_par = LC15BTS_PAR_PWR_PA1_MAX, + } +}; + +static const struct { + const char *name; + int has_max; + enum lc15bts_vswr_sensor sensor; + enum lc15bts_par ee_par; +} vswr_data[] = { + { + .name = "tx0_vswr", + .has_max = 0, + .sensor = LC15BTS_VSWR_TX0, + .ee_par = LC15BTS_PAR_VSWR_TX0_MAX, + }, { + .name = "tx1_vswr", + .has_max = 0, + .sensor = LC15BTS_VSWR_TX1, + .ee_par = LC15BTS_PAR_VSWR_TX1_MAX, + } +}; + +static const struct value_string power_unit_strs[] = { + { LC15BTS_POWER_POWER, "W" }, + { LC15BTS_POWER_VOLTAGE, "V" }, + { 0, NULL } +}; + +void lc15bts_check_temp(int no_rom_write) +{ + int temp_old[ARRAY_SIZE(temp_data)]; + int temp_cur[ARRAY_SIZE(temp_data)]; + int i, rc; + + for (i = 0; i < ARRAY_SIZE(temp_data); i++) { + int ret; + rc = lc15bts_par_get_int(tall_mgr_ctx, temp_data[i].ee_par, &ret); + temp_old[i] = ret * 1000; + + lc15bts_temp_get(temp_data[i].sensor, &temp_cur[i]); + if (temp_cur[i] < 0 && temp_cur[i] > -1000) { + LOGP(DTEMP, LOGL_ERROR, "Error reading temperature (%d): unexpected value %d\n", + temp_data[i].sensor, temp_cur[i]); + continue; + } + + LOGP(DTEMP, LOGL_DEBUG, "Current %s temperature: %d.%d C\n", + temp_data[i].name, temp_cur[i]/1000, temp_cur[i]%1000); + + if (temp_cur[i] > temp_old[i]) { + LOGP(DTEMP, LOGL_NOTICE, "New maximum %s " + "temperature: %d.%d C\n", temp_data[i].name, + temp_cur[i]/1000, temp_old[i]%1000); + + if (!no_rom_write) { + rc = lc15bts_par_set_int(tall_mgr_ctx, temp_data[i].ee_par, temp_cur[i]/1000); + if (rc < 0) + LOGP(DTEMP, LOGL_ERROR, "error writing new %s " + "max temp %d (%s)\n", temp_data[i].name, + rc, strerror(errno)); + } + } + } +} + +void lc15bts_check_power(int no_rom_write) +{ + int power_old[ARRAY_SIZE(power_data)]; + int power_cur[ARRAY_SIZE(power_data)]; + int i, rc; + int div_ratio; + + for (i = 0; i < ARRAY_SIZE(power_data); i++) { + int ret; + rc = lc15bts_par_get_int(tall_mgr_ctx, power_data[i].ee_par, &ret); + switch(power_data[i].sensor_type) { + case LC15BTS_POWER_VOLTAGE: + div_ratio = 1000; + break; + case LC15BTS_POWER_POWER: + div_ratio = 1000000; + break; + default: + div_ratio = 1000; + } + power_old[i] = ret * div_ratio; + + lc15bts_power_sensor_get(power_data[i].sensor_source, power_data[i].sensor_type, &power_cur[i]); + if (power_cur[i] < 0 && power_cur[i] > -1000) { + LOGP(DTEMP, LOGL_ERROR, "Error reading power (%d) (%d)\n", power_data[i].sensor_source, + power_data[i].sensor_type); + continue; + } + LOGP(DTEMP, LOGL_DEBUG, "Current %s power: %d.%d %s\n", + power_data[i].name, power_cur[i]/div_ratio, power_cur[i]%div_ratio, + get_value_string(power_unit_strs, power_data[i].sensor_type)); + + if (power_cur[i] > power_old[i]) { + LOGP(DTEMP, LOGL_NOTICE, "New maximum %s " + "power: %d.%d %s\n", power_data[i].name, + power_cur[i]/div_ratio, power_cur[i]%div_ratio, + get_value_string(power_unit_strs, power_data[i].sensor_type)); + + if (!no_rom_write) { + rc = lc15bts_par_set_int(tall_mgr_ctx, power_data[i].ee_par, power_cur[i]/div_ratio); + if (rc < 0) + LOGP(DTEMP, LOGL_ERROR, "error writing new %s " + "max power %d (%s)\n", power_data[i].name, + rc, strerror(errno)); + } + } + } +} + +void lc15bts_check_vswr(int no_rom_write) +{ + int vswr_old[ARRAY_SIZE(vswr_data)]; + int vswr_cur[ARRAY_SIZE(vswr_data)]; + int i, rc; + + for (i = 0; i < ARRAY_SIZE(vswr_data); i++) { + int ret; + rc = lc15bts_par_get_int(tall_mgr_ctx, vswr_data[i].ee_par, &ret); + vswr_old[i] = ret * 1000; + + lc15bts_vswr_get(vswr_data[i].sensor, &vswr_cur[i]); + if (vswr_cur[i] < 0 && vswr_cur[i] > -1000) { + LOGP(DTEMP, LOGL_ERROR, "Error reading vswr (%d)\n", vswr_data[i].sensor); + continue; + } + + LOGP(DTEMP, LOGL_DEBUG, "Current %s vswr: %d.%d\n", + vswr_data[i].name, vswr_cur[i]/1000, vswr_cur[i]%1000); + + if (vswr_cur[i] > vswr_old[i]) { + LOGP(DTEMP, LOGL_NOTICE, "New maximum %s " + "vswr: %d.%d C\n", vswr_data[i].name, + vswr_cur[i]/1000, vswr_old[i]%1000); + + if (!no_rom_write) { + rc = lc15bts_par_set_int(tall_mgr_ctx, vswr_data[i].ee_par, vswr_cur[i]/1000); + if (rc < 0) + LOGP(DTEMP, LOGL_ERROR, "error writing new %s " + "max vswr %d (%s)\n", vswr_data[i].name, + rc, strerror(errno)); + } + } + } +} + +/********************************************************************* + * Hours handling + *********************************************************************/ +static time_t last_update; + +int 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(tall_mgr_ctx, 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(tall_mgr_ctx, 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(tall_mgr_ctx, 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 to open firmware device %s: %s\n", + fw_sysfs[type], strerror(errno)); + close(fd); + return fd; + } + rc = write(fd, "restart", 8); + if (rc < 8) { + LOGP(DFW, LOGL_ERROR, "short write during " + "fw write to %s\n", fw_sysfs[type]); + close(fd); + return -EIO; + } + close(fd); + default: + return -EINVAL; + } + return 0; +} diff --git a/src/osmo-bts-lc15/misc/lc15bts_misc.h b/src/osmo-bts-lc15/misc/lc15bts_misc.h new file mode 100644 index 00000000..79e9e686 --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_misc.h @@ -0,0 +1,18 @@ +#ifndef _LC15BTS_MISC_H +#define _LC15BTS_MISC_H + +#include <stdint.h> + +void lc15bts_check_temp(int no_rom_write); +void lc15bts_check_power(int no_rom_write); +void lc15bts_check_vswr(int no_rom_write); + +int lc15bts_update_hours(int no_rom_write); + +enum lc15bts_firmware_type { + LC15BTS_FW_DSP0, + LC15BTS_FW_DSP1, + _NUM_FW +}; + +#endif diff --git a/src/osmo-bts-lc15/misc/lc15bts_nl.c b/src/osmo-bts-lc15/misc/lc15bts_nl.c new file mode 100644 index 00000000..39f64aae --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_nl.c @@ -0,0 +1,123 @@ +/* Helper for netlink */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * sysmobts_nl.c + * (C) 2014 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <arpa/inet.h> +#include <netinet/ip.h> + +#include <sys/socket.h> + +#include <linux/netlink.h> +#include <linux/rtnetlink.h> + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#define NLMSG_TAIL(nmsg) \ + ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) + +/** + * In case one binds to 0.0.0.0/INADDR_ANY and wants to know which source + * address will be used when sending a message this function can be used. + * It will ask the routing code of the kernel for the PREFSRC + */ +int source_for_dest(const struct in_addr *dest, struct in_addr *loc_source) +{ + int fd, rc; + struct rtmsg *r; + struct rtattr *rta; + struct { + struct nlmsghdr n; + struct rtmsg r; + char buf[1024]; + } req; + + memset(&req, 0, sizeof(req)); + + fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_ROUTE); + if (fd < 0) { + perror("nl socket"); + return -1; + } + + /* Send a rtmsg and ask for a response */ + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + req.n.nlmsg_type = RTM_GETROUTE; + req.n.nlmsg_seq = 1; + + /* Prepare the routing request */ + req.r.rtm_family = AF_INET; + + /* set the dest */ + rta = NLMSG_TAIL(&req.n); + rta->rta_type = RTA_DST; + rta->rta_len = RTA_LENGTH(sizeof(*dest)); + memcpy(RTA_DATA(rta), dest, sizeof(*dest)); + + /* update sizes for dest */ + req.r.rtm_dst_len = sizeof(*dest) * 8; + req.n.nlmsg_len = NLMSG_ALIGN(req.n.nlmsg_len) + RTA_ALIGN(rta->rta_len); + + rc = send(fd, &req, req.n.nlmsg_len, 0); + if (rc != req.n.nlmsg_len) { + perror("short write"); + close(fd); + return -2; + } + + + /* now receive a response and parse it */ + rc = recv(fd, &req, sizeof(req), 0); + if (rc <= 0) { + perror("short read"); + close(fd); + return -3; + } + + if (!NLMSG_OK(&req.n, rc) || req.n.nlmsg_type != RTM_NEWROUTE) { + close(fd); + return -4; + } + + r = NLMSG_DATA(&req.n); + rc -= NLMSG_LENGTH(sizeof(*r)); + rta = RTM_RTA(r); + while (RTA_OK(rta, rc)) { + if (rta->rta_type != RTA_PREFSRC) { + rta = RTA_NEXT(rta, rc); + continue; + } + + /* we are done */ + memcpy(loc_source, RTA_DATA(rta), RTA_PAYLOAD(rta)); + close(fd); + return 0; + } + + close(fd); + return -5; +} diff --git a/src/osmo-bts-lc15/misc/lc15bts_nl.h b/src/osmo-bts-lc15/misc/lc15bts_nl.h new file mode 100644 index 00000000..340cf117 --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_nl.h @@ -0,0 +1,27 @@ +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * sysmobts_nl.h + * (C) 2014 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +#pragma once + +struct in_addr; + +int source_for_dest(const struct in_addr *dest, struct in_addr *loc_source); diff --git a/src/osmo-bts-lc15/misc/lc15bts_par.c b/src/osmo-bts-lc15/misc/lc15bts_par.c new file mode 100644 index 00000000..af9d030f --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_par.c @@ -0,0 +1,232 @@ +/* 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 <osmocom/core/talloc.h> + +#include "lc15bts_par.h" + +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_RMSDET_MAX, "temp-rmsdet-max" }, + { LC15BTS_PAR_TEMP_OCXO_MAX, "temp-ocxo-max" }, + { LC15BTS_PAR_TEMP_TX0_MAX, "temp-tx0-max" }, + { LC15BTS_PAR_TEMP_TX1_MAX, "temp-tx1-max" }, + { LC15BTS_PAR_TEMP_PA0_MAX, "temp-pa0-max" }, + { LC15BTS_PAR_TEMP_PA1_MAX, "temp-pa1-max" }, + { LC15BTS_PAR_VOLT_SUPPLY_MAX, "volt-supply-max" }, + { LC15BTS_PAR_PWR_SUPPLY_MAX, "pwr-supply-max" }, + { LC15BTS_PAR_PWR_PA0_MAX, "pwr-pa0-max" }, + { LC15BTS_PAR_PWR_PA1_MAX, "pwr-pa1-max" }, + { LC15BTS_PAR_VSWR_TX0_MAX, "vswr-tx0-max" }, + { LC15BTS_PAR_VSWR_TX1_MAX, "vswr-tx1-max" }, + { LC15BTS_PAR_GPS_FIX, "gps-fix" }, + { 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_RMSDET_MAX: + case LC15BTS_PAR_TEMP_OCXO_MAX: + case LC15BTS_PAR_TEMP_TX0_MAX: + case LC15BTS_PAR_TEMP_TX1_MAX: + case LC15BTS_PAR_TEMP_PA0_MAX: + case LC15BTS_PAR_TEMP_PA1_MAX: + case LC15BTS_PAR_VOLT_SUPPLY_MAX: + case LC15BTS_PAR_VSWR_TX0_MAX: + case LC15BTS_PAR_VSWR_TX1_MAX: + case LC15BTS_PAR_SERNR: + case LC15BTS_PAR_HOURS: + case LC15BTS_PAR_BOOTS: + case LC15BTS_PAR_PWR_SUPPLY_MAX: + case LC15BTS_PAR_PWR_PA0_MAX: + case LC15BTS_PAR_PWR_PA1_MAX: + return 1; + default: + return 0; + } +} + +FILE *lc15bts_par_get_path(void *ctx, enum lc15bts_par par, const char* mode) +{ + char *fpath; + FILE *fp; + + if (par >= _NUM_LC15BTS_PAR) + return NULL; + + fpath = talloc_asprintf(ctx, "%s/%s", USER_ROM_PATH, get_value_string(lc15bts_par_names, par)); + if (!fpath) + return NULL; + + fp = fopen(fpath, mode); + if (!fp) + fprintf(stderr, "Failed to open %s due to '%s' error\n", fpath, strerror(errno)); + + talloc_free(fpath); + + return fp; +} + +int lc15bts_par_get_int(void *ctx, enum lc15bts_par par, int *ret) +{ + FILE *fp = lc15bts_par_get_path(ctx, par, "r"); + int rc; + + 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(void *ctx, enum lc15bts_par par, int val) +{ + FILE *fp = lc15bts_par_get_path(ctx, par, "w"); + int rc; + + if (fp == NULL) { + return -errno; + } + + rc = fprintf(fp, "%d", val); + if (rc < 0) { + fclose(fp); + return -EIO; + } + + fsync(fileno(fp)); + fclose(fp); + return 0; +} + +int lc15bts_par_get_buf(void *ctx, enum lc15bts_par par, uint8_t *buf, unsigned int size) +{ + FILE *fp = lc15bts_par_get_path(ctx, par, "rb"); + int rc; + + if (fp == NULL) { + return -errno; + } + + rc = fread(buf, 1, size, fp); + + fclose(fp); + + return rc; +} + +int lc15bts_par_set_buf(void *ctx, enum lc15bts_par par, const uint8_t *buf, unsigned int size) +{ + FILE *fp = lc15bts_par_get_path(ctx, par, "wb"); + int rc; + + if (fp == NULL) { + return -errno; + } + + rc = fwrite(buf, 1, size, fp); + + fsync(fileno(fp)); + fclose(fp); + + return rc; +} + +int lc15bts_par_get_gps_fix(time_t *ret) +{ + char fpath[PATH_MAX]; + FILE *fp; + int rc; + + snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(lc15bts_par_names, LC15BTS_PAR_GPS_FIX)); + fpath[sizeof(fpath)-1] = '\0'; + + fp = fopen(fpath, "r"); + if (fp == NULL) { + return -errno; + } + + rc = fscanf(fp, "%lld", (long long *)ret); + if (rc != 1) { + fclose(fp); + return -EIO; + } + fclose(fp); + + return 0; +} + +int lc15bts_par_set_gps_fix(time_t val) +{ + char fpath[PATH_MAX]; + FILE *fp; + int rc; + + snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(lc15bts_par_names, LC15BTS_PAR_GPS_FIX)); + fpath[sizeof(fpath)-1] = '\0'; + + fp = fopen(fpath, "w"); + if (fp == NULL) { + return -errno; + } + + rc = fprintf(fp, "%lld", (long long)val); + if (rc < 0) { + fclose(fp); + return -EIO; + } + fsync(fileno(fp)); + fclose(fp); + + return 0; +} diff --git a/src/osmo-bts-lc15/misc/lc15bts_par.h b/src/osmo-bts-lc15/misc/lc15bts_par.h new file mode 100644 index 00000000..74295653 --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_par.h @@ -0,0 +1,43 @@ +#ifndef _LC15BTS_PAR_H +#define _LC15BTS_PAR_H + +#include <osmocom/core/utils.h> + +#define USER_ROM_PATH "/mnt/storage/var/run/lc15bts-mgr" + +enum lc15bts_par { + LC15BTS_PAR_TEMP_SUPPLY_MAX, + LC15BTS_PAR_TEMP_SOC_MAX, + LC15BTS_PAR_TEMP_FPGA_MAX, + LC15BTS_PAR_TEMP_RMSDET_MAX, + LC15BTS_PAR_TEMP_OCXO_MAX, + LC15BTS_PAR_TEMP_TX0_MAX, + LC15BTS_PAR_TEMP_TX1_MAX, + LC15BTS_PAR_TEMP_PA0_MAX, + LC15BTS_PAR_TEMP_PA1_MAX, + LC15BTS_PAR_VOLT_SUPPLY_MAX, + LC15BTS_PAR_PWR_SUPPLY_MAX, + LC15BTS_PAR_PWR_PA0_MAX, + LC15BTS_PAR_PWR_PA1_MAX, + LC15BTS_PAR_VSWR_TX0_MAX, + LC15BTS_PAR_VSWR_TX1_MAX, + LC15BTS_PAR_GPS_FIX, + 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(void *ctx, enum lc15bts_par par, int *ret); +int lc15bts_par_set_int(void *ctx, enum lc15bts_par par, int val); +int lc15bts_par_get_buf(void *ctx, enum lc15bts_par par, uint8_t *buf, unsigned int size); +int lc15bts_par_set_buf(void *ctx, enum lc15bts_par par, const uint8_t *buf, unsigned int size); + +int lc15bts_par_is_int(enum lc15bts_par par); +int lc15bts_par_get_gps_fix(time_t *ret); +int lc15bts_par_set_gps_fix(time_t val); + +#endif diff --git a/src/osmo-bts-lc15/misc/lc15bts_power.c b/src/osmo-bts-lc15/misc/lc15bts_power.c new file mode 100644 index 00000000..1a37d8e6 --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_power.c @@ -0,0 +1,210 @@ +/* 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 "/var/lc15/pa-supply/min_microvolts" +#define PA_SUPPLY_MAX_SYSFS "/var/lc15/pa-supply/max_microvolts" + +static const char *power_enable_devs[_NUM_POWER_SOURCES] = { + [LC15BTS_POWER_PA0] = "/var/lc15/pa-state/pa0/state", + [LC15BTS_POWER_PA1] = "/var/lc15/pa-state/pa1/state", +}; + +static const char *power_sensor_devs[_NUM_POWER_SOURCES] = { + [LC15BTS_POWER_SUPPLY] = "/var/lc15/pwr-sense/main-supply/", + [LC15BTS_POWER_PA0] = "/var/lc15/pwr-sense/pa0/", + [LC15BTS_POWER_PA1] = "/var/lc15/pwr-sense/pa1/", +}; + +static const char *power_sensor_type_str[_NUM_POWER_TYPES] = { + [LC15BTS_POWER_POWER] = "power", + [LC15BTS_POWER_VOLTAGE] = "voltage", + [LC15BTS_POWER_CURRENT] = "current", +}; + +int lc15bts_power_sensor_get( + enum lc15bts_power_source source, + enum lc15bts_power_type type, + int *power) +{ + char buf[PATH_MAX]; + char pwrstr[10]; + int fd, rc; + + if (source >= _NUM_POWER_SOURCES) + return -EINVAL; + + if (type >= _NUM_POWER_TYPES) + return -EINVAL; + + snprintf(buf, sizeof(buf)-1, "%s%s", power_sensor_devs[source], power_sensor_type_str[type]); + buf[sizeof(buf)-1] = '\0'; + + fd = open(buf, O_RDONLY); + if (fd < 0) + return fd; + + rc = read(fd, pwrstr, sizeof(pwrstr)); + pwrstr[sizeof(pwrstr)-1] = '\0'; + if (rc < 0) { + close(fd); + return rc; + } + if (rc == 0) { + close(fd); + return -EIO; + } + close(fd); + *power = atoi(pwrstr); + return 0; +} + + +int lc15bts_power_set( + enum lc15bts_power_source source, + int en) +{ + int fd; + int rc; + + if ((source != LC15BTS_POWER_PA0) + && (source != LC15BTS_POWER_PA1) ) { + 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; + int retVal = 0; + char enstr[10]; + + fd = open(power_enable_devs[source], O_RDONLY); + if (fd < 0) { + return fd; + } + + rc = read(fd, enstr, sizeof(enstr)); + enstr[rc-1] = '\0'; + + close(fd); + + if (rc < 0) { + return rc; + } + if (rc == 0) { + return -EIO; + } + + rc = strcmp(enstr, "enabled"); + if(rc == 0) { + retVal = 1; + } + + return retVal; +} + +static const char *vswr_devs[_NUM_VSWR_SENSORS] = { + [LC15BTS_VSWR_TX0] = "/var/lc15/vswr/tx0/vswr", + [LC15BTS_VSWR_TX1] = "/var/lc15/vswr/tx1/vswr", +}; + +int lc15bts_vswr_get(enum lc15bts_vswr_sensor sensor, int *vswr) +{ + char buf[PATH_MAX]; + char vswrstr[8]; + int fd, rc; + + if (sensor < 0 || sensor >= _NUM_VSWR_SENSORS) + return -EINVAL; + + snprintf(buf, sizeof(buf)-1, "%s", vswr_devs[sensor]); + buf[sizeof(buf)-1] = '\0'; + + fd = open(buf, O_RDONLY); + if (fd < 0) + return fd; + + rc = read(fd, vswrstr, sizeof(vswrstr)); + vswrstr[sizeof(vswrstr)-1] = '\0'; + if (rc < 0) { + close(fd); + return rc; + } + if (rc == 0) { + close(fd); + return -EIO; + } + close(fd); + *vswr = atoi(vswrstr); + return 0; +} diff --git a/src/osmo-bts-lc15/misc/lc15bts_power.h b/src/osmo-bts-lc15/misc/lc15bts_power.h new file mode 100644 index 00000000..b48cfdcd --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_power.h @@ -0,0 +1,38 @@ +#ifndef _LC15BTS_POWER_H +#define _LC15BTS_POWER_H + +enum lc15bts_power_source { + LC15BTS_POWER_SUPPLY, + LC15BTS_POWER_PA0, + LC15BTS_POWER_PA1, + _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 *volt); + +int lc15bts_power_set( + enum lc15bts_power_source source, + int en); + +int lc15bts_power_get( + enum lc15bts_power_source source); + +enum lc15bts_vswr_sensor { + LC15BTS_VSWR_TX0, + LC15BTS_VSWR_TX1, + _NUM_VSWR_SENSORS +}; + +int lc15bts_vswr_get(enum lc15bts_vswr_sensor sensor, int *vswr); + +#endif diff --git a/src/osmo-bts-lc15/misc/lc15bts_swd.c b/src/osmo-bts-lc15/misc/lc15bts_swd.c new file mode 100644 index 00000000..eac64d66 --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_swd.c @@ -0,0 +1,178 @@ +/* Systemd service wd notification for Litecell 1.5 BTS management daemon */ + +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "misc/lc15bts_mgr.h" +#include "misc/lc15bts_swd.h" +#include <osmocom/core/logging.h> + +/* Needed for service watchdog notification */ +#include <systemd/sd-daemon.h> + +/* This is the period used to verify if all events have been registered to be allowed + to notify the systemd service watchdog +*/ +#define SWD_PERIOD 30 + +static void swd_start(struct lc15bts_mgr_instance *mgr); +static void swd_process(struct lc15bts_mgr_instance *mgr); +static void swd_close(struct lc15bts_mgr_instance *mgr); +static void swd_state_reset(struct lc15bts_mgr_instance *mgr, int reason); +static int swd_run(struct lc15bts_mgr_instance *mgr, int from_loop); +static void swd_loop_run(void *_data); + +enum swd_state { + SWD_INITIAL, + SWD_IN_PROGRESS, +}; + +enum swd_result { + SWD_FAIL_START, + SWD_FAIL_NOTIFY, + SWD_SUCCESS, +}; + +static void swd_start(struct lc15bts_mgr_instance *mgr) +{ + swd_process(mgr); +} + +static void swd_process(struct lc15bts_mgr_instance *mgr) +{ + int rc = 0, notify = 0; + + /* Did we get all needed conditions ? */ + if (mgr->swd.swd_eventmasks == mgr->swd.swd_events) { + /* Ping systemd service wd if enabled */ + rc = sd_notify(0, "WATCHDOG=1"); + LOGP(DSWD, LOGL_NOTICE, "Watchdog notification attempt\n"); + notify = 1; + } + else { + LOGP(DSWD, LOGL_NOTICE, "Missing watchdog events: e:0x%016llx,m:0x%016llx\n",mgr->swd.swd_events,mgr->swd.swd_eventmasks); + } + + if (rc < 0) { + LOGP(DSWD, LOGL_ERROR, + "Failed to notify system service watchdog: %d\n", rc); + swd_state_reset(mgr, SWD_FAIL_NOTIFY); + return; + } + else { + /* Did we notified the watchdog? */ + if (notify) { + mgr->swd.swd_events = 0; + /* Makes sure we really cleared it in case any event was notified at this same moment (it would be lost) */ + if (mgr->swd.swd_events != 0) + mgr->swd.swd_events = 0; + } + } + + swd_state_reset(mgr, SWD_SUCCESS); + return; +} + +static void swd_close(struct lc15bts_mgr_instance *mgr) +{ +} + +static void swd_state_reset(struct lc15bts_mgr_instance *mgr, int outcome) +{ + if (mgr->swd.swd_from_loop) { + mgr->swd.swd_timeout.data = mgr; + mgr->swd.swd_timeout.cb = swd_loop_run; + osmo_timer_schedule(&mgr->swd.swd_timeout, SWD_PERIOD, 0); + } + + mgr->swd.state = SWD_INITIAL; + swd_close(mgr); +} + +static int swd_run(struct lc15bts_mgr_instance *mgr, int from_loop) +{ + if (mgr->swd.state != SWD_INITIAL) { + LOGP(DSWD, LOGL_ERROR, "Swd is already in progress.\n"); + return -1; + } + + mgr->swd.swd_from_loop = from_loop; + + /* From now on everything will be handled from the failure */ + mgr->swd.state = SWD_IN_PROGRESS; + swd_start(mgr); + return 0; +} + +static void swd_loop_run(void *_data) +{ + int rc; + struct lc15bts_mgr_instance *mgr = _data; + + LOGP(DSWD, LOGL_NOTICE, "Going to check for watchdog notification.\n"); + rc = swd_run(mgr, 1); + if (rc != 0) { + swd_state_reset(mgr, SWD_FAIL_START); + } +} + +/* 'swd_num_events' configures the number of events to be monitored before notifying the + systemd service watchdog. It must be in the range of [1,64]. Events are notified + through the function 'lc15bts_swd_event' +*/ +int lc15bts_swd_init(struct lc15bts_mgr_instance *mgr, int swd_num_events) +{ + /* Checks for a valid number of events to validate */ + if (swd_num_events < 1 || swd_num_events > 64) + return(-1); + + mgr->swd.state = SWD_INITIAL; + mgr->swd.swd_timeout.data = mgr; + mgr->swd.swd_timeout.cb = swd_loop_run; + osmo_timer_schedule(&mgr->swd.swd_timeout, 0, 0); + + if (swd_num_events == 64){ + mgr->swd.swd_eventmasks = 0xffffffffffffffffULL; + } + else { + mgr->swd.swd_eventmasks = ((1ULL << swd_num_events) - 1); + } + mgr->swd.swd_events = 0; + mgr->swd.num_events = swd_num_events; + + return 0; +} + +/* Notifies that the specified event 'swd_event' happened correctly; + the value must be in the range of [0,'swd_num_events'[ (see lc15bts_swd_init). + For example, if 'swd_num_events' was 64, 'swd_event' events are numbered 0 to 63. + WARNING: if this function can be used from multiple threads at the same time, + it must be protected with a kind of mutex to avoid losing event notification. +*/ +int lc15bts_swd_event(struct lc15bts_mgr_instance *mgr, enum mgr_swd_events swd_event) +{ + /* Checks for a valid specified event (smaller than max possible) */ + if ((int)(swd_event) < 0 || (int)(swd_event) >= mgr->swd.num_events) + return(-1); + + mgr->swd.swd_events = mgr->swd.swd_events | ((unsigned long long int)(1) << (int)(swd_event)); + + /* !!! Uncomment following line to debug events notification */ + LOGP(DSWD, LOGL_DEBUG,"Swd event notified: %d\n", (int)(swd_event)); + + return 0; +} diff --git a/src/osmo-bts-lc15/misc/lc15bts_swd.h b/src/osmo-bts-lc15/misc/lc15bts_swd.h new file mode 100644 index 00000000..b78a2c2a --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_swd.h @@ -0,0 +1,7 @@ +#ifndef _LC15BTS_SWD_H +#define _LC15BTS_SWD_H + +int lc15bts_swd_init(struct lc15bts_mgr_instance *mgr, int swd_num_events); +int lc15bts_swd_event(struct lc15bts_mgr_instance *mgr, enum mgr_swd_events swd_event); + +#endif diff --git a/src/osmo-bts-lc15/misc/lc15bts_temp.c b/src/osmo-bts-lc15/misc/lc15bts_temp.c new file mode 100644 index 00000000..45602dcc --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_temp.c @@ -0,0 +1,74 @@ +/* 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] = "/var/lc15/temp/main-supply/temp", + [LC15BTS_TEMP_SOC] = "/var/lc15/temp/cpu/temp", + [LC15BTS_TEMP_FPGA] = "/var/lc15/temp/fpga/temp", + [LC15BTS_TEMP_RMSDET] = "/var/lc15/temp/rmsdet/temp", + [LC15BTS_TEMP_OCXO] = "/var/lc15/temp/ocxo/temp", + [LC15BTS_TEMP_TX0] = "/var/lc15/temp/tx0/temp", + [LC15BTS_TEMP_TX1] = "/var/lc15/temp/tx1/temp", + [LC15BTS_TEMP_PA0] = "/var/lc15/temp/pa0/temp", + [LC15BTS_TEMP_PA1] = "/var/lc15/temp/pa1/temp", +}; + +int lc15bts_temp_get(enum lc15bts_temp_sensor sensor, int *temp) +{ + char buf[PATH_MAX]; + char tempstr[8]; + int fd, rc; + + if (sensor < 0 || sensor >= _NUM_TEMP_SENSORS) + return -EINVAL; + + snprintf(buf, sizeof(buf)-1, "%s", temp_devs[sensor]); + buf[sizeof(buf)-1] = '\0'; + + fd = open(buf, O_RDONLY); + if (fd < 0) + return fd; + + rc = read(fd, tempstr, sizeof(tempstr)); + tempstr[sizeof(tempstr)-1] = '\0'; + if (rc < 0) { + close(fd); + return rc; + } + if (rc == 0) { + close(fd); + return -EIO; + } + close(fd); + *temp = atoi(tempstr); + return 0; +} + diff --git a/src/osmo-bts-lc15/misc/lc15bts_temp.h b/src/osmo-bts-lc15/misc/lc15bts_temp.h new file mode 100644 index 00000000..35d81f1b --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_temp.h @@ -0,0 +1,28 @@ +#ifndef _LC15BTS_TEMP_H +#define _LC15BTS_TEMP_H + +enum lc15bts_temp_sensor { + LC15BTS_TEMP_SUPPLY, + LC15BTS_TEMP_SOC, + LC15BTS_TEMP_FPGA, + LC15BTS_TEMP_RMSDET, + LC15BTS_TEMP_OCXO, + LC15BTS_TEMP_TX0, + LC15BTS_TEMP_TX1, + LC15BTS_TEMP_PA0, + LC15BTS_TEMP_PA1, + _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, int *temp); + + +#endif diff --git a/src/osmo-bts-lc15/misc/lc15bts_util.c b/src/osmo-bts-lc15/misc/lc15bts_util.c new file mode 100644 index 00000000..430ce0f7 --- /dev/null +++ b/src/osmo-bts-lc15/misc/lc15bts_util.c @@ -0,0 +1,164 @@ +/* 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 <osmocom/core/talloc.h> +#include <osmocom/core/msgb.h> + +#include "lc15bts_par.h" + +void *tall_util_ctx; + +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; + + tall_util_ctx = talloc_named_const(NULL, 1, "lc15 utils"); + msgb_talloc_ctx_init(tall_util_ctx, 0); + + 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(tall_util_ctx, 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(tall_util_ctx, 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(tall_util_ctx, par, atoi(write_arg)); + if (rc < 0) { + fprintf(stderr, "Error %d\n", rc); + goto err; + } + printf("Success setting %s=%d\n", parname, + atoi(write_arg)); + break; + default: + fprintf(stderr, "Unsupported action\n"); + goto err; + } + + exit(0); + +err: + exit(1); +} + diff --git a/src/osmo-bts-lc15/oml.c b/src/osmo-bts-lc15/oml.c new file mode 100644 index 00000000..1fe7205a --- /dev/null +++ b/src/osmo-bts-lc15/oml.c @@ -0,0 +1,2112 @@ +/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com> + * + * Based on sysmoBTS: + * (C) 2011 by Harald Welte <laforge@gnumonks.org> + * (C) 2013-2014 by Holger Hans Peter Freyther + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <errno.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/fsm.h> + +#include <nrw/litecell15/gsml1prim.h> +#include <nrw/litecell15/gsml1const.h> +#include <nrw/litecell15/gsml1types.h> +#include <nrw/litecell15/litecell15.h> + +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/logging.h> +#include <osmo-bts/oml.h> +#include <osmo-bts/rsl.h> +#include <osmo-bts/amr.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/bts_model.h> +#include <osmo-bts/phy_link.h> +#include <osmo-bts/handover.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/nm_common_fsm.h> + +#include "l1_if.h" +#include "lc15bts.h" +#include "utils.h" + +static int mph_info_chan_confirm(struct gsm_lchan *lchan, + enum osmo_mph_info_type type, uint8_t cause) +{ + struct osmo_phsap_prim l1sap; + + memset(&l1sap, 0, sizeof(l1sap)); + osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_MPH_INFO, PRIM_OP_CONFIRM, + NULL); + l1sap.u.info.type = type; + l1sap.u.info.u.act_cnf.chan_nr = gsm_lchan2chan_nr(lchan); + l1sap.u.info.u.act_cnf.cause = cause; + + return l1sap_up(lchan->ts->trx, &l1sap); +} + +enum sapi_cmd_type { + SAPI_CMD_ACTIVATE, + SAPI_CMD_CONFIG_CIPHERING, + SAPI_CMD_CONFIG_LOGCH_PARAM, + SAPI_CMD_SACCH_REL_MARKER, + SAPI_CMD_REL_MARKER, + SAPI_CMD_DEACTIVATE, +}; + +struct sapi_cmd { + struct llist_head entry; + GsmL1_Sapi_t sapi; + GsmL1_Dir_t dir; + enum sapi_cmd_type type; + int (*callback)(struct gsm_lchan *lchan, int status); +}; + +static const enum GsmL1_LogChComb_t pchan_to_logChComb[_GSM_PCHAN_MAX] = { + [GSM_PCHAN_NONE] = GsmL1_LogChComb_0, + [GSM_PCHAN_CCCH] = GsmL1_LogChComb_IV, + [GSM_PCHAN_CCCH_SDCCH4] = GsmL1_LogChComb_V, + [GSM_PCHAN_CCCH_SDCCH4_CBCH] = GsmL1_LogChComb_V, + [GSM_PCHAN_TCH_F] = GsmL1_LogChComb_I, + [GSM_PCHAN_TCH_H] = GsmL1_LogChComb_II, + [GSM_PCHAN_SDCCH8_SACCH8C] = GsmL1_LogChComb_VII, + [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = GsmL1_LogChComb_VII, + [GSM_PCHAN_PDCH] = GsmL1_LogChComb_XIII, + [GSM_PCHAN_UNKNOWN] = GsmL1_LogChComb_0, + /* + * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_TCH_F_TCH_H_PDCH should not be + * part of this, only "real" pchan values will be looked up here. + * See the callers of ts_connect_as(). + */ +}; + +static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb); + +static void *prim_init(GsmL1_Prim_t *prim, GsmL1_PrimId_t id, struct lc15l1_hdl *gl1, + uint32_t hLayer3_uint32) +{ + HANDLE hLayer3; + prim->id = id; + + osmo_static_assert(sizeof(HANDLE) >= 4, l1p_handle_is_at_least_32bit); + hLayer3 = (void*)hLayer3_uint32; + + switch (id) { + case GsmL1_PrimId_MphInitReq: + //prim->u.mphInitReq.hLayer1 = (HANDLE)gl1->hLayer1; + prim->u.mphInitReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphCloseReq: + prim->u.mphCloseReq.hLayer1 = gl1->hLayer1; + prim->u.mphCloseReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConnectReq: + prim->u.mphConnectReq.hLayer1 = gl1->hLayer1; + prim->u.mphConnectReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDisconnectReq: + prim->u.mphDisconnectReq.hLayer1 = gl1->hLayer1; + prim->u.mphDisconnectReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphActivateReq: + prim->u.mphActivateReq.hLayer1 = gl1->hLayer1; + prim->u.mphActivateReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDeactivateReq: + prim->u.mphDeactivateReq.hLayer1 = gl1->hLayer1; + prim->u.mphDeactivateReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConfigReq: + prim->u.mphConfigReq.hLayer1 = gl1->hLayer1; + prim->u.mphConfigReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphMeasureReq: + prim->u.mphMeasureReq.hLayer1 = gl1->hLayer1; + prim->u.mphMeasureReq.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphInitCnf: + prim->u.mphInitCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphCloseCnf: + prim->u.mphCloseCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConnectCnf: + prim->u.mphConnectCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDisconnectCnf: + prim->u.mphDisconnectCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphActivateCnf: + prim->u.mphActivateCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphDeactivateCnf: + prim->u.mphDeactivateCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphConfigCnf: + prim->u.mphConfigCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphMeasureCnf: + prim->u.mphMeasureCnf.hLayer3 = hLayer3; + break; + case GsmL1_PrimId_MphTimeInd: + break; + case GsmL1_PrimId_MphSyncInd: + break; + case GsmL1_PrimId_PhEmptyFrameReq: + prim->u.phEmptyFrameReq.hLayer1 = gl1->hLayer1; + break; + case GsmL1_PrimId_PhDataReq: + prim->u.phDataReq.hLayer1 = gl1->hLayer1; + break; + case GsmL1_PrimId_PhConnectInd: + break; + case GsmL1_PrimId_PhReadyToSendInd: + break; + case GsmL1_PrimId_PhDataInd: + break; + case GsmL1_PrimId_PhRaInd: + break; + default: + LOGP(DL1C, LOGL_ERROR, "unknown L1 primitive %u\n", id); + break; + } + return &prim->u; +} + +static uint32_t l1p_handle_for_trx(struct gsm_bts_trx *trx) +{ + struct gsm_bts *bts = trx->bts; + + osmo_static_assert(sizeof(trx->nr) == 1, trx_nr_is_8bit); + osmo_static_assert(sizeof(bts->nr) == 1, bts_nr_is_8bit); + + return bts->nr << 24 + | trx->nr << 16; +} + +static uint32_t l1p_handle_for_ts(struct gsm_bts_trx_ts *ts) +{ + osmo_static_assert(sizeof(ts->nr) == 1, ts_nr_is_8bit); + + return l1p_handle_for_trx(ts->trx) + | ts->nr << 8; +} + + +static uint32_t l1p_handle_for_lchan(struct gsm_lchan *lchan) +{ + osmo_static_assert(sizeof(lchan->nr) == 1, lchan_nr_is_8bit); + + return l1p_handle_for_ts(lchan->ts) + | lchan->nr; +} + +GsmL1_Status_t prim_status(GsmL1_Prim_t *prim) +{ + switch (prim->id) { + case GsmL1_PrimId_MphInitCnf: + return prim->u.mphInitCnf.status; + case GsmL1_PrimId_MphCloseCnf: + return prim->u.mphCloseCnf.status; + case GsmL1_PrimId_MphConnectCnf: + return prim->u.mphConnectCnf.status; + case GsmL1_PrimId_MphDisconnectCnf: + return prim->u.mphDisconnectCnf.status; + case GsmL1_PrimId_MphActivateCnf: + return prim->u.mphActivateCnf.status; + case GsmL1_PrimId_MphDeactivateCnf: + return prim->u.mphDeactivateCnf.status; + case GsmL1_PrimId_MphConfigCnf: + return prim->u.mphConfigCnf.status; + case GsmL1_PrimId_MphMeasureCnf: + return prim->u.mphMeasureCnf.status; + default: + break; + } + return GsmL1_Status_Success; +} + +#if 0 +static int compl_cb_send_oml_msg(struct msgb *l1_msg, void *data) +{ + struct msgb *resp_msg = data; + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + + if (prim_status(l1p) != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_ERROR, "Rx %s, status: %s\n", + get_value_string(lc15bts_l1prim_names, l1p->id), + get_value_string(lc15bts_l1status_names, cc->status)); + return 0; + } + + msgb_free(l1_msg); + + return abis_nm_sendmsg(msg); +} +#endif + +int lchan_activate(struct gsm_lchan *lchan); + +static int opstart_compl(struct gsm_abis_mo *mo, struct msgb *l1_msg) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_Status_t status = prim_status(l1p); + struct gsm_bts_trx *trx = gsm_bts_trx_num(mo->bts, mo->obj_inst.trx_nr); + + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_ERROR, "Rx %s, status: %s\n", + get_value_string(lc15bts_l1prim_names, l1p->id), + get_value_string(lc15bts_l1status_names, status)); + msgb_free(l1_msg); + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_NACK, + (void*)(intptr_t)NM_NACK_CANT_PERFORM); + case NM_OC_CHANNEL: + return osmo_fsm_inst_dispatch(trx->ts[mo->obj_inst.ts_nr].mo.fi, NM_EV_OPSTART_NACK, + (void*)(intptr_t)NM_NACK_CANT_PERFORM); + default: + OSMO_ASSERT(0); + } + } + + msgb_free(l1_msg); + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_ACK, NULL); + case NM_OC_CHANNEL: + /* ugly hack to auto-activate all SAPIs for the BCCH/CCCH on TS0 */ + if (mo->obj_inst.trx_nr == 0 && + mo->obj_inst.ts_nr == 0) { + struct gsm_lchan *cbch = gsm_bts_get_cbch(mo->bts); + DEBUGP(DL1C, "====> trying to activate lchans of BCCH\n"); + mo->bts->c0->ts[0].lchan[CCCH_LCHAN].rel_act_kind = + LCHAN_REL_ACT_OML; + lchan_activate(&mo->bts->c0->ts[0].lchan[CCCH_LCHAN]); + if (cbch) { + cbch->rel_act_kind = LCHAN_REL_ACT_OML; + lchan_activate(cbch); + } + } + return osmo_fsm_inst_dispatch(trx->ts[mo->obj_inst.ts_nr].mo.fi, + NM_EV_OPSTART_ACK, NULL); + default: + OSMO_ASSERT(0); + } +} + +static int opstart_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + struct gsm_abis_mo *mo; + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphConnectCnf_t *cnf = &l1p->u.mphConnectCnf; + + mo = &trx->ts[cnf->u8Tn].mo; + return opstart_compl(mo, l1_msg); +} + +static int trx_mute_on_init_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + Litecell15_Prim_t *sysp = msgb_sysprim(resp); + GsmL1_Status_t status; + + status = sysp->u.muteRfCnf.status; + + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_FATAL, "Rx RF-MUTE.conf status=%s\n", + get_value_string(lc15bts_l1status_names, status)); + bts_shutdown(trx->bts, "RF-MUTE failure"); + } + + msgb_free(resp); + + return 0; +} + +static int trx_init_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphInitCnf_t *ic = &l1p->u.mphInitCnf; + + LOGP(DL1C, LOGL_INFO, "Rx MPH-INIT.conf (status=%s)\n", + get_value_string(lc15bts_l1status_names, ic->status)); + + /* store layer1 handle */ + if (ic->status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_FATAL, "Rx MPH-INIT.conf status=%s\n", + get_value_string(lc15bts_l1status_names, ic->status)); + bts_shutdown(trx->bts, "MPH-INIT failure"); + } + + fl1h->hLayer1 = ic->hLayer1; + + /* If the TRX was already locked the MphInit would have undone it */ + if (trx->mo.nm_state.administrative == NM_STATE_LOCKED) + trx_rf_lock(trx, 1, trx_mute_on_init_cb); + + /* Begin to ramp up the power */ + power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0, NULL); + + return opstart_compl(&trx->mo, l1_msg); +} + +int gsm_abis_mo_check_attr(const struct gsm_abis_mo *mo, const uint8_t *attr_ids, + unsigned int num_attr_ids) +{ + unsigned int i; + + if (!mo->nm_attr) + return 0; + + for (i = 0; i < num_attr_ids; i++) { + if (!TLVP_PRESENT(mo->nm_attr, attr_ids[i])) + return 0; + } + return 1; +} + +static const uint8_t trx_rqd_attr[] = { NM_ATT_RF_MAXPOWR_R }; + +/* initialize the layer1 */ +static int trx_init(struct gsm_bts_trx *trx) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + struct msgb *msg; + GsmL1_MphInitReq_t *mi_req; + GsmL1_DeviceParam_t *dev_par; + int rc, lc15_band; + + if (!gsm_abis_mo_check_attr(&trx->mo, trx_rqd_attr, + ARRAY_SIZE(trx_rqd_attr))) { + /* HACK: spec says we need to decline, but openbsc + * doesn't deal with this very well */ + return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_ACK, NULL); + //return osmo_fsm_inst_dispatch(trx->mo.fi, NM_EV_OPSTART_NACK, + // (void*)(intptr_t)NM_NACK_CANT_PERFORM); + } + + /* Update TRX band */ + rc = gsm_arfcn2band_rc(trx->arfcn, &trx->bts->band); + if (rc) { + /* FIXME: abort initialization? */ + LOGP(DL1C, LOGL_ERROR, "Could not pick GSM band " + "for ARFCN %u\n", trx->arfcn); + trx->bts->band = 0x00; + } + + lc15_band = lc15bts_select_lc15_band(trx, trx->arfcn); + if (lc15_band < 0) { + LOGP(DL1C, LOGL_ERROR, "Unsupported GSM band %s\n", + gsm_band_name(trx->bts->band)); + } + + msg = l1p_msgb_alloc(); + mi_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphInitReq, fl1h, + l1p_handle_for_trx(trx)); + dev_par = &mi_req->deviceParam; + dev_par->devType = GsmL1_DevType_TxdRxu; + dev_par->freqBand = lc15_band; + dev_par->u16Arfcn = trx->arfcn; + dev_par->u16BcchArfcn = trx->bts->c0->arfcn; + dev_par->u8NbTsc = trx->bts->bsic & 7; + dev_par->fRxPowerLevel = trx_ms_pwr_ctrl_is_osmo(trx) + ? 0.0 : trx->bts->ul_power_target; + + dev_par->fTxPowerLevel = 0.0; + LOGP(DL1C, LOGL_NOTICE, "Init TRX (Band %d, ARFCN %u, TSC %u, RxPower % 2f dBm, " + "TxPower % 2.2f dBm\n", dev_par->freqBand, dev_par->u16Arfcn, dev_par->u8NbTsc, + dev_par->fRxPowerLevel, dev_par->fTxPowerLevel); + + /* send MPH-INIT-REQ, wait for MPH-INIT-CNF */ + return l1if_gsm_req_compl(fl1h, msg, trx_init_compl_cb, NULL); +} + +uint32_t trx_get_hlayer1(const struct gsm_bts_trx *trx) +{ + const struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + + return fl1h->hLayer1; +} + +static int trx_close_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + msgb_free(l1_msg); + bts_model_trx_close_cb(trx, 0); + return 0; +} + +void bts_model_trx_close(struct gsm_bts_trx *trx) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + struct msgb *msg; + int rc; + + msg = l1p_msgb_alloc(); + prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphCloseReq, fl1h, + l1p_handle_for_trx(trx)); + LOGP(DL1C, LOGL_NOTICE, "Close TRX %u\n", trx->nr); + + rc = l1if_gsm_req_compl(fl1h, msg, trx_close_compl_cb, NULL); + if (rc < 0) + bts_model_trx_close_cb(trx, rc); +} + +static int trx_rf_lock(struct gsm_bts_trx *trx, int locked, l1if_compl_cb *cb) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + uint8_t mute[8]; + int i; + + for (i = 0; i < ARRAY_SIZE(mute); ++i) + mute[i] = locked ? 1 : 0; + + return l1if_mute_rf(fl1h, mute, cb); +} + +int oml_mo_rf_lock_chg(struct gsm_abis_mo *mo, uint8_t mute_state[8], + int success) +{ + if (success) { + int i; + int is_locked = 1; + + for (i = 0; i < 8; ++i) + if (!mute_state[i]) + is_locked = 0; + + mo->nm_state.administrative = + is_locked ? NM_STATE_LOCKED : NM_STATE_UNLOCKED; + mo->procedure_pending = 0; + return oml_mo_statechg_ack(mo); + } else { + mo->procedure_pending = 0; + return oml_mo_statechg_nack(mo, NM_NACK_REQ_NOT_GRANT); + } +} + +static int ts_connect_as(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config pchan, + l1if_compl_cb *cb, void *data) +{ + struct msgb *msg = l1p_msgb_alloc(); + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(ts->trx); + GsmL1_MphConnectReq_t *cr; + + if (pchan == GSM_PCHAN_TCH_F_PDCH + || pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) { + LOGP(DL1C, LOGL_ERROR, + "%s Requested TS connect as %s," + " expected a specific pchan instead\n", + gsm_ts_and_pchan_name(ts), gsm_pchan_name(pchan)); + return -EINVAL; + } + + cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConnectReq, fl1h, + l1p_handle_for_ts(ts)); + cr->u8Tn = ts->nr; + cr->logChComb = pchan_to_logChComb[pchan]; + + return l1if_gsm_req_compl(fl1h, msg, cb, NULL); +} + +static int ts_opstart(struct gsm_bts_trx_ts *ts) +{ + enum gsm_phys_chan_config pchan = ts->pchan; + switch (pchan) { + case GSM_PCHAN_TCH_F_TCH_H_PDCH: + ts->dyn.pchan_is = ts->dyn.pchan_want = GSM_PCHAN_NONE; + /* First connect as NONE, until first RSL CHAN ACT. */ + pchan = GSM_PCHAN_NONE; + break; + case GSM_PCHAN_TCH_F_PDCH: + /* First connect as TCH/F, expecting PDCH ACT. */ + pchan = GSM_PCHAN_TCH_F; + break; + default: + /* simply use ts->pchan */ + break; + } + return ts_connect_as(ts, pchan, opstart_compl_cb, NULL); +} + +GsmL1_Sapi_t lchan_to_GsmL1_Sapi_t(const struct gsm_lchan *lchan) +{ + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + return GsmL1_Sapi_TchF; + case GSM_LCHAN_TCH_H: + return GsmL1_Sapi_TchH; + default: + LOGP(DL1C, LOGL_NOTICE, "%s cannot determine L1 SAPI\n", + gsm_lchan_name(lchan)); + break; + } + return GsmL1_Sapi_Idle; +} + +GsmL1_SubCh_t lchan_to_GsmL1_SubCh_t(const struct gsm_lchan *lchan) +{ + enum gsm_phys_chan_config pchan = lchan->ts->pchan; + + if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH) + pchan = lchan->ts->dyn.pchan_want; + + switch (pchan) { + case GSM_PCHAN_CCCH_SDCCH4: + case GSM_PCHAN_CCCH_SDCCH4_CBCH: + if (lchan->type == GSM_LCHAN_CCCH) + return GsmL1_SubCh_NA; + /* fall-through */ + case GSM_PCHAN_TCH_H: + case GSM_PCHAN_SDCCH8_SACCH8C: + case GSM_PCHAN_SDCCH8_SACCH8C_CBCH: + return lchan->nr; + case GSM_PCHAN_NONE: + case GSM_PCHAN_CCCH: + case GSM_PCHAN_TCH_F: + case GSM_PCHAN_PDCH: + case GSM_PCHAN_UNKNOWN: + default: + /* case GSM_PCHAN_TCH_F_TCH_H_PDCH: is caught above */ + return GsmL1_SubCh_NA; + } + + return GsmL1_SubCh_NA; +} + +struct sapi_dir { + GsmL1_Sapi_t sapi; + GsmL1_Dir_t dir; +}; + +static const struct sapi_dir ccch_sapis[] = { + { GsmL1_Sapi_Fcch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Sch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Bcch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Agch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Pch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink }, +}; + +static const struct sapi_dir tchf_sapis[] = { + { GsmL1_Sapi_TchF, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_TchF, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_FacchF, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_FacchF, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink }, +}; + +static const struct sapi_dir tchh_sapis[] = { + { GsmL1_Sapi_TchH, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_TchH, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_FacchH, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_FacchH, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink }, +}; + +static const struct sapi_dir sdcch_sapis[] = { + { GsmL1_Sapi_Sdcch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Sdcch, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Sacch, GsmL1_Dir_RxUplink }, +}; + +static const struct sapi_dir cbch_sapis[] = { + { GsmL1_Sapi_Cbch, GsmL1_Dir_TxDownlink }, + /* Does the CBCH really have a SACCH in Downlink? */ + { GsmL1_Sapi_Sacch, GsmL1_Dir_TxDownlink }, +}; + +static const struct sapi_dir pdtch_sapis[] = { + { GsmL1_Sapi_Pdtch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Pdtch, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_Ptcch, GsmL1_Dir_TxDownlink }, + { GsmL1_Sapi_Prach, GsmL1_Dir_RxUplink }, +#if 0 + { GsmL1_Sapi_Ptcch, GsmL1_Dir_RxUplink }, + { GsmL1_Sapi_Pacch, GsmL1_Dir_TxDownlink }, +#endif +}; + +static const struct sapi_dir ho_sapis[] = { + { GsmL1_Sapi_Rach, GsmL1_Dir_RxUplink }, +}; + +struct lchan_sapis { + const struct sapi_dir *sapis; + unsigned int num_sapis; +}; + +static const struct lchan_sapis sapis_for_lchan[_GSM_LCHAN_MAX] = { + [GSM_LCHAN_SDCCH] = { + .sapis = sdcch_sapis, + .num_sapis = ARRAY_SIZE(sdcch_sapis), + }, + [GSM_LCHAN_TCH_F] = { + .sapis = tchf_sapis, + .num_sapis = ARRAY_SIZE(tchf_sapis), + }, + [GSM_LCHAN_TCH_H] = { + .sapis = tchh_sapis, + .num_sapis = ARRAY_SIZE(tchh_sapis), + }, + [GSM_LCHAN_CCCH] = { + .sapis = ccch_sapis, + .num_sapis = ARRAY_SIZE(ccch_sapis), + }, + [GSM_LCHAN_PDTCH] = { + .sapis = pdtch_sapis, + .num_sapis = ARRAY_SIZE(pdtch_sapis), + }, + [GSM_LCHAN_CBCH] = { + .sapis = cbch_sapis, + .num_sapis = ARRAY_SIZE(cbch_sapis), + }, +}; + +static const struct lchan_sapis sapis_for_ho = { + .sapis = ho_sapis, + .num_sapis = ARRAY_SIZE(ho_sapis), +}; + +static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd); +static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd); +static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd); +static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cmd); + +static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir); +static int lchan_deactivate_sapis(struct gsm_lchan *lchan); + +/** + * Execute the first SAPI command of the queue. In case of the markers + * this method is re-entrant so we need to make sure to remove a command + * from the list before calling a function that will queue a command. + * + * \return 0 in case no Gsm Request was sent, 1 otherwise + */ +static int sapi_queue_exeute(struct gsm_lchan *lchan) +{ + int res; + struct sapi_cmd *cmd; + + cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry); + + switch (cmd->type) { + case SAPI_CMD_ACTIVATE: + mph_send_activate_req(lchan, cmd); + res = 1; + break; + case SAPI_CMD_CONFIG_CIPHERING: + mph_send_config_ciphering(lchan, cmd); + res = 1; + break; + case SAPI_CMD_CONFIG_LOGCH_PARAM: + mph_send_config_logchpar(lchan, cmd); + res = 1; + break; + case SAPI_CMD_SACCH_REL_MARKER: + llist_del(&cmd->entry); + talloc_free(cmd); + res = check_sapi_release(lchan, GsmL1_Sapi_Sacch, + GsmL1_Dir_TxDownlink); + res |= check_sapi_release(lchan, GsmL1_Sapi_Sacch, + GsmL1_Dir_RxUplink); + break; + case SAPI_CMD_REL_MARKER: + llist_del(&cmd->entry); + talloc_free(cmd); + res = lchan_deactivate_sapis(lchan); + break; + case SAPI_CMD_DEACTIVATE: + mph_send_deactivate_req(lchan, cmd); + res = 1; + break; + default: + LOGP(DL1C, LOGL_NOTICE, + "Unimplemented command type %d\n", cmd->type); + llist_del(&cmd->entry); + talloc_free(cmd); + res = 0; + abort(); + break; + } + + return res; +} + +static void sapi_queue_send(struct gsm_lchan *lchan) +{ + int res; + + do { + res = sapi_queue_exeute(lchan); + } while (res == 0 && !llist_empty(&lchan->sapi_cmds)); +} + +static void sapi_queue_dispatch(struct gsm_lchan *lchan, int status) +{ + int end; + struct sapi_cmd *cmd = llist_entry(lchan->sapi_cmds.next, + struct sapi_cmd, entry); + llist_del(&cmd->entry); + end = llist_empty(&lchan->sapi_cmds); + + if (cmd->callback) + cmd->callback(lchan, status); + talloc_free(cmd); + + if (end || llist_empty(&lchan->sapi_cmds)) { + LOGP(DL1C, LOGL_DEBUG, + "%s End of SAPI cmd queue encountered.%s\n", + gsm_lchan_name(lchan), + llist_empty(&lchan->sapi_cmds) + ? " Queue is now empty." + : " More pending."); + return; + } + + sapi_queue_send(lchan); +} + +/** + * Queue and possible execute a SAPI command. Return 1 in case the command was + * already executed and 0 in case if it was only put into the queue + */ +static int queue_sapi_command(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + int start = llist_empty(&lchan->sapi_cmds); + llist_add_tail(&cmd->entry, &lchan->sapi_cmds); + + if (!start) + return 0; + + sapi_queue_send(lchan); + return 1; +} + +static int lchan_act_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + enum lchan_sapi_state status; + struct sapi_cmd *cmd; + struct gsm_lchan *lchan; + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphActivateCnf_t *ic = &l1p->u.mphActivateCnf; + + /* get the lchan from the information we supplied */ + lchan = l1if_hLayer_to_lchan(trx, (uint32_t)ic->hLayer3); + if (!lchan) { + LOGP(DL1C, LOGL_ERROR, + "Failed to find lchan for hLayer3=0x%x\n", (uint32_t)ic->hLayer3); + goto err; + } + + LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.conf (%s ", + gsm_lchan_name(lchan), + get_value_string(lc15bts_l1sapi_names, ic->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(lc15bts_dir_names, ic->dir)); + + if (ic->status == GsmL1_Status_Success) { + DEBUGP(DL1C, "Successful activation of L1 SAPI %s on TS %u\n", + get_value_string(lc15bts_l1sapi_names, ic->sapi), ic->u8Tn); + status = LCHAN_SAPI_S_ASSIGNED; + } else { + LOGP(DL1C, LOGL_ERROR, "Error activating L1 SAPI %s on TS %u: %s\n", + get_value_string(lc15bts_l1sapi_names, ic->sapi), ic->u8Tn, + get_value_string(lc15bts_l1status_names, ic->status)); + status = LCHAN_SAPI_S_ERROR; + } + + if (ic->dir & GsmL1_Dir_TxDownlink) + lchan->sapis_dl[ic->sapi] = status; + if (ic->dir & GsmL1_Dir_RxUplink) + lchan->sapis_ul[ic->sapi] = status; + + if (llist_empty(&lchan->sapi_cmds)) { + LOGP(DL1C, LOGL_ERROR, + "%s Got activation confirmation with empty queue\n", + gsm_lchan_name(lchan)); + goto err; + } + + cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry); + if (cmd->sapi != ic->sapi || cmd->dir != ic->dir || + cmd->type != SAPI_CMD_ACTIVATE) { + LOGP(DL1C, LOGL_ERROR, + "%s Confirmation mismatch (%d, %d) (%d, %d)\n", + gsm_lchan_name(lchan), cmd->sapi, cmd->dir, + ic->sapi, ic->dir); + goto err; + } + + sapi_queue_dispatch(lchan, ic->status); + +err: + msgb_free(l1_msg); + + return 0; +} + +uint32_t l1if_lchan_to_hLayer(struct gsm_lchan *lchan) +{ + return 0xBB + | (lchan->nr << 8) + | (lchan->ts->nr << 16) + | (lchan->ts->trx->nr << 24); +} + +/* obtain a ptr to the lapdm_channel for a given hLayer */ +struct gsm_lchan * +l1if_hLayer_to_lchan(struct gsm_bts_trx *trx, uint32_t hLayer2) +{ + uint8_t magic = hLayer2 & 0xff; + uint8_t ts_nr = (hLayer2 >> 16) & 0xff; + uint8_t lchan_nr = (hLayer2 >> 8)& 0xff; + struct gsm_bts_trx_ts *ts; + + if (magic != 0xBB) + return NULL; + + /* FIXME: if we actually run on the BTS, the 32bit field is large + * enough to simply put a pointer inside. */ + if (ts_nr >= ARRAY_SIZE(trx->ts)) + return NULL; + + ts = &trx->ts[ts_nr]; + + if (lchan_nr >= ARRAY_SIZE(ts->lchan)) + return NULL; + + return &ts->lchan[lchan_nr]; +} + +/* we regularly check if the DSP L1 is still sending us primitives. + * if not, we simply stop the BTS program (and be re-spawned) */ +static void alive_timer_cb(void *data) +{ + struct lc15l1_hdl *fl1h = data; + + if (fl1h->alive_prim_cnt == 0) { + LOGP(DL1C, LOGL_FATAL, "DSP L1 is no longer sending primitives!\n"); + exit(23); + } + fl1h->alive_prim_cnt = 0; + osmo_timer_schedule(&fl1h->alive_timer, 5, 0); +} + +static void clear_amr_params(GsmL1_LogChParam_t *lch_par) +{ + int j; + /* common for the SIGN, V1 and EFR: */ + lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_NA; + lch_par->tch.amrInitCodecMode = GsmL1_AmrCodecMode_Unset; + for (j = 0; j < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); j++) + lch_par->tch.amrActiveCodecSet[j] = GsmL1_AmrCodec_Unset; +} + +static void set_payload_format(GsmL1_LogChParam_t *lch_par) +{ + lch_par->tch.tchPlFmt = GsmL1_TchPlFmt_Rtp; +} + +static void lchan2lch_par(GsmL1_LogChParam_t *lch_par, struct gsm_lchan *lchan) +{ + struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr; + struct gsm48_multi_rate_conf *mr_conf = + (struct gsm48_multi_rate_conf *) amr_mrc->gsm48_ie; + int j; + + LOGP(DL1C, LOGL_INFO, "%s: %s tch_mode=0x%02x\n", + gsm_lchan_name(lchan), __FUNCTION__, lchan->tch_mode); + + switch (lchan->tch_mode) { + case GSM48_CMODE_SIGN: + /* we have to set some TCH payload type even if we don't + * know yet what codec we will use later on */ + if (lchan->type == GSM_LCHAN_TCH_F) + lch_par->tch.tchPlType = GsmL1_TchPlType_Fr; + else + lch_par->tch.tchPlType = GsmL1_TchPlType_Hr; + clear_amr_params(lch_par); + break; + case GSM48_CMODE_SPEECH_V1: + if (lchan->type == GSM_LCHAN_TCH_F) + lch_par->tch.tchPlType = GsmL1_TchPlType_Fr; + else + lch_par->tch.tchPlType = GsmL1_TchPlType_Hr; + set_payload_format(lch_par); + clear_amr_params(lch_par); + break; + case GSM48_CMODE_SPEECH_EFR: + lch_par->tch.tchPlType = GsmL1_TchPlType_Efr; + set_payload_format(lch_par); + clear_amr_params(lch_par); + break; + case GSM48_CMODE_SPEECH_AMR: + lch_par->tch.tchPlType = GsmL1_TchPlType_Amr; + set_payload_format(lch_par); + lch_par->tch.amrCmiPhase = GsmL1_AmrCmiPhase_Odd; /* FIXME? */ + lch_par->tch.amrInitCodecMode = amr_get_initial_mode(lchan); + + /* initialize to clean state */ + for (j = 0; j < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); j++) + lch_par->tch.amrActiveCodecSet[j] = GsmL1_AmrCodec_Unset; + + j = 0; + if (mr_conf->m4_75) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_4_75; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + + if (mr_conf->m5_15) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_5_15; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + + if (mr_conf->m5_90) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_5_9; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + + if (mr_conf->m6_70) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_6_7; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + + if (mr_conf->m7_40) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_7_4; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + + if (mr_conf->m7_95) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_7_95; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + + if (mr_conf->m10_2) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_10_2; + if (j >= ARRAY_SIZE(lch_par->tch.amrActiveCodecSet)) + break; + if (mr_conf->m12_2) + lch_par->tch.amrActiveCodecSet[j++] = GsmL1_AmrCodec_12_2; + break; + case GSM48_CMODE_DATA_14k5: + case GSM48_CMODE_DATA_12k0: + case GSM48_CMODE_DATA_6k0: + case GSM48_CMODE_DATA_3k6: + LOGP(DL1C, LOGL_ERROR, "%s: CSD not supported!\n", + gsm_lchan_name(lchan)); + break; + } +} + +static int mph_send_activate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(lchan->ts->trx); + struct msgb *msg = l1p_msgb_alloc(); + int sapi = cmd->sapi; + int dir = cmd->dir; + GsmL1_MphActivateReq_t *act_req; + GsmL1_LogChParam_t *lch_par; + + act_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphActivateReq, + fl1h, l1p_handle_for_lchan(lchan)); + lch_par = &act_req->logChPrm; + act_req->u8Tn = lchan->ts->nr; + act_req->subCh = lchan_to_GsmL1_SubCh_t(lchan); + act_req->dir = dir; + act_req->sapi = sapi; + act_req->hLayer2 = (HANDLE *)l1if_lchan_to_hLayer(lchan); + act_req->hLayer3 = act_req->hLayer2; + + switch (act_req->sapi) { + case GsmL1_Sapi_Rach: + lch_par->rach.u8Bsic = lchan->ts->trx->bts->bsic; + break; + case GsmL1_Sapi_Agch: + lch_par->agch.u8NbrOfAgch = num_agch(lchan->ts->trx, lchan->name); + break; + case GsmL1_Sapi_TchH: + case GsmL1_Sapi_TchF: + lchan2lch_par(lch_par, lchan); + /* + * Be sure that every packet is received, even if it + * fails. In this case the length might be lower or 0. + */ + act_req->fBFILevel = -200.0f; + break; + case GsmL1_Sapi_Ptcch: + lch_par->ptcch.u8Bsic = lchan->ts->trx->bts->bsic; + break; + case GsmL1_Sapi_Prach: + lch_par->prach.u8Bsic = lchan->ts->trx->bts->bsic; + break; + case GsmL1_Sapi_Sacch: + /* + * For the SACCH we need to set the u8MsPowerLevel when + * doing manual MS power control. + */ + if (trx_ms_pwr_ctrl_is_osmo(lchan->ts->trx)) + lch_par->sacch.u8MsPowerLevel = lchan->ms_power_ctrl.current; + /* fall through */ + case GsmL1_Sapi_Pdtch: + case GsmL1_Sapi_Pacch: + /* + * Be sure that every packet is received, even if it + * fails. In this case the length might be lower or 0. + */ + act_req->fBFILevel = -200.0f; + break; + default: + break; + } + + LOGP(DL1C, LOGL_INFO, "%s MPH-ACTIVATE.req (hL2=0x%08x, %s ", + gsm_lchan_name(lchan), (uint32_t)act_req->hLayer2, + get_value_string(lc15bts_l1sapi_names, act_req->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(lc15bts_dir_names, act_req->dir)); + + /* send the primitive for all GsmL1_Sapi_* that match the LCHAN */ + return l1if_gsm_req_compl(fl1h, msg, lchan_act_compl_cb, NULL); +} + +static void sapi_clear_queue(struct llist_head *queue) +{ + struct sapi_cmd *next, *tmp; + + llist_for_each_entry_safe(next, tmp, queue, entry) { + llist_del(&next->entry); + talloc_free(next); + } +} + +static int sapi_activate_cb(struct gsm_lchan *lchan, int status) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(lchan->ts->trx); + + /* FIXME: Error handling */ + if (status != GsmL1_Status_Success) { + LOGP(DL1C, LOGL_ERROR, + "%s act failed mark broken due status: %d\n", + gsm_lchan_name(lchan), status); + lchan_set_state(lchan, LCHAN_S_BROKEN); + sapi_clear_queue(&lchan->sapi_cmds); + mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, RSL_ERR_PROCESSOR_OVERLOAD); + return -1; + } + + if (!llist_empty(&lchan->sapi_cmds)) + return 0; + + if (lchan->state != LCHAN_S_ACT_REQ) + return 0; + + lchan_set_state(lchan, LCHAN_S_ACTIVE); + mph_info_chan_confirm(lchan, PRIM_INFO_ACTIVATE, 0); + + /* set the initial ciphering parameters for both directions */ + l1if_set_ciphering(fl1h, lchan, 1); + l1if_set_ciphering(fl1h, lchan, 0); + if (lchan->encr.alg_id) + lchan->ciph_state = LCHAN_CIPH_RXTX_REQ; + else + lchan->ciph_state = LCHAN_CIPH_NONE; + + return 0; +} + +static void enqueue_sapi_act_cmd(struct gsm_lchan *lchan, int sapi, int dir) +{ + struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + + cmd->sapi = sapi; + cmd->dir = dir; + cmd->type = SAPI_CMD_ACTIVATE; + cmd->callback = sapi_activate_cb; + queue_sapi_command(lchan, cmd); +} + +int lchan_activate(struct gsm_lchan *lchan) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(lchan->ts->trx); + const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type]; + unsigned int i; + + lchan_set_state(lchan, LCHAN_S_ACT_REQ); + + if (!llist_empty(&lchan->sapi_cmds)) + LOGP(DL1C, LOGL_ERROR, + "%s Trying to activate lchan, but commands in queue\n", + gsm_lchan_name(lchan)); + + /* override the regular SAPIs if this is the first hand-over + * related activation of the LCHAN */ + if (lchan->ho.active == HANDOVER_ENABLED) + s4l = &sapis_for_ho; + + for (i = 0; i < s4l->num_sapis; i++) { + int sapi = s4l->sapis[i].sapi; + int dir = s4l->sapis[i].dir; + + if (sapi == GsmL1_Sapi_Sch) { + /* once we activate the SCH, we should get MPH-TIME.ind */ + fl1h->alive_timer.cb = alive_timer_cb; + fl1h->alive_timer.data = fl1h; + fl1h->alive_prim_cnt = 0; + osmo_timer_schedule(&fl1h->alive_timer, 5, 0); + } + enqueue_sapi_act_cmd(lchan, sapi, dir); + } + +#warning "FIXME: Should this be in sapi_activate_cb?" + lchan_init_lapdm(lchan); + + return 0; +} + +const struct value_string lc15bts_l1cfgt_names[] = { + { GsmL1_ConfigParamId_SetNbTsc, "Set NB TSC" }, + { GsmL1_ConfigParamId_SetTxPowerLevel, "Set Tx power level" }, + { GsmL1_ConfigParamId_SetLogChParams, "Set logical channel params" }, + { GsmL1_ConfigParamId_SetCipheringParams,"Configure ciphering params" }, +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) + { GsmL1_ConfigParamId_Set8pskPowerReduction, "Set 8PSK Tx power reduction" }, +#endif + { 0, NULL } +}; + +static void dump_lch_par(int logl, GsmL1_LogChParam_t *lch_par, GsmL1_Sapi_t sapi) +{ + int i; + + switch (sapi) { + case GsmL1_Sapi_Rach: + LOGPC(DL1C, logl, "BSIC=0x%08x", lch_par->rach.u8Bsic); + break; + case GsmL1_Sapi_Agch: + LOGPC(DL1C, logl, "BS_AG_BLKS_RES=%u ", + lch_par->agch.u8NbrOfAgch); + break; + case GsmL1_Sapi_Sacch: + LOGPC(DL1C, logl, "MS Power Level 0x%02x", + lch_par->sacch.u8MsPowerLevel); + break; + case GsmL1_Sapi_TchF: + case GsmL1_Sapi_TchH: + LOGPC(DL1C, logl, "amrCmiPhase=0x%02x amrInitCodec=0x%02x (", + lch_par->tch.amrCmiPhase, + lch_par->tch.amrInitCodecMode); + for (i = 0; i < ARRAY_SIZE(lch_par->tch.amrActiveCodecSet); i++) { + LOGPC(DL1C, logl, "%x ", + lch_par->tch.amrActiveCodecSet[i]); + } + break; + /* FIXME: PRACH / PTCCH */ + default: + break; + } + LOGPC(DL1C, logl, ")\n"); +} + +static int chmod_txpower_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf; + + LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ", + gsm_trx_name(trx), + get_value_string(lc15bts_l1cfgt_names, cc->cfgParamId)); + + LOGPC(DL1C, LOGL_INFO, "setTxPower %f dBm\n", + cc->cfgParams.setTxPowerLevel.fTxPowerLevel); + + power_trx_change_compl(trx, + (int) (cc->cfgParams.setTxPowerLevel.fTxPowerLevel * 1000)); + + msgb_free(l1_msg); + + return 0; +} + +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) +static int chmod_txpower_backoff_8psk_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf; + + LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ", + gsm_trx_name(trx), + get_value_string(lc15bts_l1cfgt_names, cc->cfgParamId)); + + LOGPC(DL1C, LOGL_INFO, "Backoff %u dB\n", + cc->cfgParams.set8pskPowerReduction.u8PowerReduction); + + msgb_free(l1_msg); + + return 0; +} + +static int chmod_max_cell_size_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + Litecell15_Prim_t *sysp = msgb_sysprim(resp); + Litecell15_SetMaxCellSizeCnf_t *sac = &sysp->u.setMaxCellSizeCnf; + + LOGP(DL1C, LOGL_INFO, "%s Rx SYS prim %s -> %s\n", + gsm_trx_name(trx), + get_value_string(lc15bts_sysprim_names, sysp->id), + get_value_string(lc15bts_l1status_names, sac->status)); + + msgb_free(resp); + + return 0; +} + +static int chmod_c0_idle_pwr_red_compl_cb(struct gsm_bts_trx *trx, struct msgb *resp, + void *data) +{ + Litecell15_Prim_t *sysp = msgb_sysprim(resp); + Litecell15_SetC0IdleSlotPowerReductionCnf_t *sac = &sysp->u.setC0IdleSlotPowerReductionCnf; + + LOGP(DL1C, LOGL_INFO, "%s Rx SYS prim %s -> %s\n", + gsm_trx_name(trx), + get_value_string(lc15bts_sysprim_names, sysp->id), + get_value_string(lc15bts_l1status_names, sac->status)); + + msgb_free(resp); + + return 0; +} +#endif + +static int chmod_modif_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + struct gsm_lchan *lchan; + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphConfigCnf_t *cc = &l1p->u.mphConfigCnf; + + /* get the lchan from the information we supplied */ + lchan = l1if_hLayer_to_lchan(trx, (uint32_t)cc->hLayer3); + if (!lchan) { + LOGP(DL1C, LOGL_ERROR, + "Failed to find lchan for hLayer3=0x%x\n", (uint32_t)cc->hLayer3); + goto err; + } + + LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.conf (%s) ", + gsm_lchan_name(lchan), + get_value_string(lc15bts_l1cfgt_names, cc->cfgParamId)); + + switch (cc->cfgParamId) { + case GsmL1_ConfigParamId_SetLogChParams: + dump_lch_par(LOGL_INFO, + &cc->cfgParams.setLogChParams.logChParams, + cc->cfgParams.setLogChParams.sapi); + + sapi_queue_dispatch(lchan, cc->status); + break; + case GsmL1_ConfigParamId_SetCipheringParams: + switch (lchan->ciph_state) { + case LCHAN_CIPH_RX_REQ: + LOGPC(DL1C, LOGL_INFO, "RX_REQ -> RX_CONF\n"); + lchan->ciph_state = LCHAN_CIPH_RX_CONF; + break; + case LCHAN_CIPH_RX_CONF_TX_REQ: + LOGPC(DL1C, LOGL_INFO, "RX_CONF_TX_REQ -> RXTX_CONF\n"); + lchan->ciph_state = LCHAN_CIPH_RXTX_CONF; + break; + case LCHAN_CIPH_RXTX_REQ: + LOGPC(DL1C, LOGL_INFO, "RXTX_REQ -> RX_CONF_TX_REQ\n"); + lchan->ciph_state = LCHAN_CIPH_RX_CONF_TX_REQ; + break; + case LCHAN_CIPH_NONE: + LOGPC(DL1C, LOGL_INFO, "\n"); + break; + default: + LOGPC(DL1C, LOGL_INFO, "unhandled state %u\n", lchan->ciph_state); + break; + } + if (llist_empty(&lchan->sapi_cmds)) { + LOGP(DL1C, LOGL_ERROR, + "%s Got ciphering conf with empty queue\n", + gsm_lchan_name(lchan)); + goto err; + } + + sapi_queue_dispatch(lchan, cc->status); + break; + case GsmL1_ConfigParamId_SetNbTsc: + default: + LOGPC(DL1C, LOGL_INFO, "\n"); + break; + } + +err: + msgb_free(l1_msg); + + return 0; +} + +static int mph_send_config_logchpar(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + struct gsm_bts_trx *trx = lchan->ts->trx; + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + struct msgb *msg = l1p_msgb_alloc(); + GsmL1_MphConfigReq_t *conf_req; + GsmL1_LogChParam_t *lch_par; + + /* channel mode, encryption and/or multirate have changed */ + + /* update multi-rate config */ + conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h, + l1p_handle_for_lchan(lchan)); + conf_req->cfgParamId = GsmL1_ConfigParamId_SetLogChParams; + conf_req->cfgParams.setLogChParams.sapi = cmd->sapi; + conf_req->cfgParams.setLogChParams.u8Tn = lchan->ts->nr; + conf_req->cfgParams.setLogChParams.subCh = lchan_to_GsmL1_SubCh_t(lchan); + conf_req->cfgParams.setLogChParams.dir = cmd->dir; + conf_req->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan); + + lch_par = &conf_req->cfgParams.setLogChParams.logChParams; + lchan2lch_par(lch_par, lchan); + + /* Update the MS Power Level */ + if (cmd->sapi == GsmL1_Sapi_Sacch && trx_ms_pwr_ctrl_is_osmo(trx)) + lch_par->sacch.u8MsPowerLevel = lchan->ms_power_ctrl.current; + + /* FIXME: update encryption */ + + LOGP(DL1C, LOGL_INFO, "%s MPH-CONFIG.req (%s) ", + gsm_lchan_name(lchan), + get_value_string(lc15bts_l1sapi_names, + conf_req->cfgParams.setLogChParams.sapi)); + LOGPC(DL1C, LOGL_INFO, "cfgParams Tn=%u, subCh=%u, dir=0x%x ", + conf_req->cfgParams.setLogChParams.u8Tn, + conf_req->cfgParams.setLogChParams.subCh, + conf_req->cfgParams.setLogChParams.dir); + dump_lch_par(LOGL_INFO, + &conf_req->cfgParams.setLogChParams.logChParams, + conf_req->cfgParams.setLogChParams.sapi); + + return l1if_gsm_req_compl(fl1h, msg, chmod_modif_compl_cb, NULL); +} + +static void enqueue_sapi_logchpar_cmd(struct gsm_lchan *lchan, int dir, GsmL1_Sapi_t sapi) +{ + struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + + cmd->dir = dir; + cmd->sapi = sapi; + cmd->type = SAPI_CMD_CONFIG_LOGCH_PARAM; + queue_sapi_command(lchan, cmd); +} + +static int tx_confreq_logchpar(struct gsm_lchan *lchan, uint8_t direction) +{ + enqueue_sapi_logchpar_cmd(lchan, direction, lchan_to_GsmL1_Sapi_t(lchan)); + return 0; +} + +int l1if_set_txpower(struct lc15l1_hdl *fl1h, float tx_power) +{ + struct msgb *msg = l1p_msgb_alloc(); + GsmL1_MphConfigReq_t *conf_req; + + conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h, 0); + conf_req->cfgParamId = GsmL1_ConfigParamId_SetTxPowerLevel; + conf_req->cfgParams.setTxPowerLevel.fTxPowerLevel = tx_power; + + return l1if_gsm_req_compl(fl1h, msg, chmod_txpower_compl_cb, NULL); +} + +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) +int l1if_set_txpower_backoff_8psk(struct lc15l1_hdl *fl1h, uint8_t backoff) +{ + struct msgb *msg = l1p_msgb_alloc(); + GsmL1_MphConfigReq_t *conf_req; + + conf_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h, 0); + conf_req->cfgParamId = GsmL1_ConfigParamId_Set8pskPowerReduction; + conf_req->cfgParams.set8pskPowerReduction.u8PowerReduction = backoff; + + return l1if_gsm_req_compl(fl1h, msg, chmod_txpower_backoff_8psk_compl_cb, NULL); +} + +int l1if_set_max_cell_size(struct lc15l1_hdl *fl1h, uint8_t cell_size) +{ + struct msgb *msg = sysp_msgb_alloc(); + Litecell15_Prim_t *sys_prim = msgb_sysprim(msg); + sys_prim->id = Litecell15_PrimId_SetMaxCellSizeReq; + sys_prim->u.setMaxCellSizeReq.u8MaxCellSize = cell_size; + + LOGP(DL1C, LOGL_INFO, "%s Set max cell size = %d qbits\n", + gsm_trx_name(fl1h->phy_inst->trx), + cell_size); + + return l1if_req_compl(fl1h, msg, chmod_max_cell_size_compl_cb, NULL); + +} + +int l1if_set_txpower_c0_idle_pwr_red(struct lc15l1_hdl *fl1h, uint8_t red) +{ + struct msgb *msg = sysp_msgb_alloc(); + Litecell15_Prim_t *sys_prim = msgb_sysprim(msg); + sys_prim->id = Litecell15_PrimId_SetC0IdleSlotPowerReductionReq; + sys_prim->u.setC0IdleSlotPowerReductionReq.u8PowerReduction = red; + + LOGP(DL1C, LOGL_INFO, "%s Set C0 idle slot power reduction = %d dB\n", + gsm_trx_name(fl1h->phy_inst->trx), + red); + + return l1if_req_compl(fl1h, msg, chmod_c0_idle_pwr_red_compl_cb, NULL); +} +#endif + +const enum GsmL1_CipherId_t rsl2l1_ciph[] = { + [0] = GsmL1_CipherId_A50, + [1] = GsmL1_CipherId_A50, + [2] = GsmL1_CipherId_A51, + [3] = GsmL1_CipherId_A52, + [4] = GsmL1_CipherId_A53, +}; + +static int mph_send_config_ciphering(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(lchan->ts->trx); + struct msgb *msg = l1p_msgb_alloc(); + struct GsmL1_MphConfigReq_t *cfgr; + + cfgr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConfigReq, fl1h, + l1p_handle_for_lchan(lchan)); + + cfgr->cfgParamId = GsmL1_ConfigParamId_SetCipheringParams; + cfgr->cfgParams.setCipheringParams.u8Tn = lchan->ts->nr; + cfgr->cfgParams.setCipheringParams.subCh = lchan_to_GsmL1_SubCh_t(lchan); + cfgr->cfgParams.setCipheringParams.dir = cmd->dir; + cfgr->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan); + + if (lchan->encr.alg_id >= ARRAY_SIZE(rsl2l1_ciph)) + return -EINVAL; + cfgr->cfgParams.setCipheringParams.cipherId = rsl2l1_ciph[lchan->encr.alg_id]; + + LOGP(DL1C, LOGL_NOTICE, "%s SET_CIPHERING (ALG=%u %s)\n", + gsm_lchan_name(lchan), + cfgr->cfgParams.setCipheringParams.cipherId, + get_value_string(lc15bts_dir_names, + cfgr->cfgParams.setCipheringParams.dir)); + + memcpy(cfgr->cfgParams.setCipheringParams.u8Kc, + lchan->encr.key, lchan->encr.key_len); + + return l1if_gsm_req_compl(fl1h, msg, chmod_modif_compl_cb, NULL); +} + +static void enqueue_sapi_ciphering_cmd(struct gsm_lchan *lchan, int dir) +{ + struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + + cmd->dir = dir; + cmd->type = SAPI_CMD_CONFIG_CIPHERING; + queue_sapi_command(lchan, cmd); +} + +int l1if_set_ciphering(struct lc15l1_hdl *fl1h, + struct gsm_lchan *lchan, + int dir_downlink) +{ + int dir; + + /* ignore the request when the channel is not active */ + if (lchan->state != LCHAN_S_ACTIVE) + return -1; + + if (dir_downlink) + dir = GsmL1_Dir_TxDownlink; + else + dir = GsmL1_Dir_RxUplink; + + enqueue_sapi_ciphering_cmd(lchan, dir); + + return 0; +} + +int bts_model_adjst_ms_pwr(struct gsm_lchan *lchan) +{ + if (lchan->state != LCHAN_S_ACTIVE) + return -1; + + enqueue_sapi_logchpar_cmd(lchan, GsmL1_Dir_RxUplink, GsmL1_Sapi_Sacch); + return 0; +} + +int l1if_rsl_mode_modify(struct gsm_lchan *lchan) +{ + if (lchan->state != LCHAN_S_ACTIVE) + return -1; + + /* channel mode, encryption and/or multirate have changed */ + + /* update multi-rate config */ + tx_confreq_logchpar(lchan, GsmL1_Dir_RxUplink); + tx_confreq_logchpar(lchan, GsmL1_Dir_TxDownlink); + + /* FIXME: update encryption */ + + return 0; +} + +static int lchan_deact_compl_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + enum lchan_sapi_state status; + struct sapi_cmd *cmd; + struct gsm_lchan *lchan; + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphDeactivateCnf_t *ic = &l1p->u.mphDeactivateCnf; + + lchan = l1if_hLayer_to_lchan(trx, (uint32_t)ic->hLayer3); + if (!lchan) { + LOGP(DL1C, LOGL_ERROR, + "Failed to find lchan for hLayer3=0x%x\n", (uint32_t)ic->hLayer3); + goto err; + } + + LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.conf (%s ", + gsm_lchan_name(lchan), + get_value_string(lc15bts_l1sapi_names, ic->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(lc15bts_dir_names, ic->dir)); + + if (ic->status == GsmL1_Status_Success) { + DEBUGP(DL1C, "Successful deactivation of L1 SAPI %s on TS %u\n", + get_value_string(lc15bts_l1sapi_names, ic->sapi), ic->u8Tn); + status = LCHAN_SAPI_S_NONE; + } else { + LOGP(DL1C, LOGL_ERROR, "Error deactivating L1 SAPI %s on TS %u: %s\n", + get_value_string(lc15bts_l1sapi_names, ic->sapi), ic->u8Tn, + get_value_string(lc15bts_l1status_names, ic->status)); + status = LCHAN_SAPI_S_ERROR; + } + + if (ic->dir & GsmL1_Dir_TxDownlink) + lchan->sapis_dl[ic->sapi] = status; + if (ic->dir & GsmL1_Dir_RxUplink) + lchan->sapis_ul[ic->sapi] = status; + + + if (llist_empty(&lchan->sapi_cmds)) { + LOGP(DL1C, LOGL_ERROR, + "%s Got de-activation confirmation with empty queue\n", + gsm_lchan_name(lchan)); + goto err; + } + + cmd = llist_entry(lchan->sapi_cmds.next, struct sapi_cmd, entry); + if (cmd->sapi != ic->sapi || cmd->dir != ic->dir || + cmd->type != SAPI_CMD_DEACTIVATE) { + LOGP(DL1C, LOGL_ERROR, + "%s Confirmation mismatch (%d, %d) (%d, %d)\n", + gsm_lchan_name(lchan), cmd->sapi, cmd->dir, + ic->sapi, ic->dir); + goto err; + } + + sapi_queue_dispatch(lchan, ic->status); + +err: + msgb_free(l1_msg); + return 0; +} + +static int mph_send_deactivate_req(struct gsm_lchan *lchan, struct sapi_cmd *cmd) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(lchan->ts->trx); + struct msgb *msg = l1p_msgb_alloc(); + GsmL1_MphDeactivateReq_t *deact_req; + + deact_req = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDeactivateReq, + fl1h, l1p_handle_for_lchan(lchan)); + deact_req->u8Tn = lchan->ts->nr; + deact_req->subCh = lchan_to_GsmL1_SubCh_t(lchan); + deact_req->dir = cmd->dir; + deact_req->sapi = cmd->sapi; + deact_req->hLayer3 = (HANDLE)l1if_lchan_to_hLayer(lchan); + + LOGP(DL1C, LOGL_INFO, "%s MPH-DEACTIVATE.req (%s ", + gsm_lchan_name(lchan), + get_value_string(lc15bts_l1sapi_names, deact_req->sapi)); + LOGPC(DL1C, LOGL_INFO, "%s)\n", + get_value_string(lc15bts_dir_names, deact_req->dir)); + + /* send the primitive for all GsmL1_Sapi_* that match the LCHAN */ + return l1if_gsm_req_compl(fl1h, msg, lchan_deact_compl_cb, NULL); +} + +static int sapi_deactivate_cb(struct gsm_lchan *lchan, int status) +{ + /* FIXME: Error handling. There is no NACK... */ + if (status != GsmL1_Status_Success && lchan->state == LCHAN_S_REL_REQ) { + LOGP(DL1C, LOGL_ERROR, "%s is now broken. Stopping the release.\n", + gsm_lchan_name(lchan)); + lchan_set_state(lchan, LCHAN_S_BROKEN); + sapi_clear_queue(&lchan->sapi_cmds); + mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0); + return -1; + } + + if (!llist_empty(&lchan->sapi_cmds)) + return 0; + + /* Don't send an REL ACK on SACCH deactivate */ + if (lchan->state != LCHAN_S_REL_REQ) + return 0; + + lchan_set_state(lchan, LCHAN_S_NONE); + mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0); + + /* Reactivate CCCH due to SI3 update in RSL */ + if (lchan->rel_act_kind == LCHAN_REL_ACT_REACT) { + lchan->rel_act_kind = LCHAN_REL_ACT_RSL; + lchan_activate(lchan); + } + return 0; +} + +static int enqueue_sapi_deact_cmd(struct gsm_lchan *lchan, int sapi, int dir) +{ + struct sapi_cmd *cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + + cmd->sapi = sapi; + cmd->dir = dir; + cmd->type = SAPI_CMD_DEACTIVATE; + cmd->callback = sapi_deactivate_cb; + return queue_sapi_command(lchan, cmd); +} + +/* + * Release the SAPI if it was allocated. E.g. the SACCH might be already + * deactivated or during a hand-over the TCH was not allocated yet. + */ +static int check_sapi_release(struct gsm_lchan *lchan, int sapi, int dir) +{ + /* check if we should schedule a release */ + if (dir & GsmL1_Dir_TxDownlink) { + if (lchan->sapis_dl[sapi] != LCHAN_SAPI_S_ASSIGNED) + return 0; + lchan->sapis_dl[sapi] = LCHAN_SAPI_S_REL; + } else if (dir & GsmL1_Dir_RxUplink) { + if (lchan->sapis_ul[sapi] != LCHAN_SAPI_S_ASSIGNED) + return 0; + lchan->sapis_ul[sapi] = LCHAN_SAPI_S_REL; + } + + /* now schedule the command and maybe dispatch it */ + return enqueue_sapi_deact_cmd(lchan, sapi, dir); +} + +static int release_sapis_for_ho(struct gsm_lchan *lchan) +{ + int res = 0; + int i; + + const struct lchan_sapis *s4l = &sapis_for_ho; + + for (i = s4l->num_sapis-1; i >= 0; i--) + res |= check_sapi_release(lchan, + s4l->sapis[i].sapi, s4l->sapis[i].dir); + return res; +} + +static int lchan_deactivate_sapis(struct gsm_lchan *lchan) +{ + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(lchan->ts->trx); + const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type]; + int i, res; + + res = 0; + + /* The order matters.. the Facch needs to be released first */ + for (i = s4l->num_sapis-1; i >= 0; i--) { + /* Stop the alive timer once we deactivate the SCH */ + if (s4l->sapis[i].sapi == GsmL1_Sapi_Sch) + osmo_timer_del(&fl1h->alive_timer); + + /* Release if it was allocated */ + res |= check_sapi_release(lchan, s4l->sapis[i].sapi, s4l->sapis[i].dir); + } + + /* always attempt to disable the RACH burst */ + res |= release_sapis_for_ho(lchan); + + /* nothing was queued */ + if (res == 0) { + LOGP(DL1C, LOGL_ERROR, "%s all SAPIs already released?\n", + gsm_lchan_name(lchan)); + lchan_set_state(lchan, LCHAN_S_BROKEN); + mph_info_chan_confirm(lchan, PRIM_INFO_DEACTIVATE, 0); + } + + return res; +} + +static void enqueue_rel_marker(struct gsm_lchan *lchan) +{ + struct sapi_cmd *cmd; + + /* remember we need to release all active SAPIs */ + cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + cmd->type = SAPI_CMD_REL_MARKER; + queue_sapi_command(lchan, cmd); +} + +int bts_model_lchan_deactivate(struct gsm_lchan *lchan) +{ + lchan_set_state(lchan, LCHAN_S_REL_REQ); + enqueue_rel_marker(lchan); + return 0; +} + +static void enqueue_sacch_rel_marker(struct gsm_lchan *lchan) +{ + struct sapi_cmd *cmd; + + /* remember we need to check if the SACCH is allocated */ + cmd = talloc_zero(lchan->ts->trx, struct sapi_cmd); + cmd->type = SAPI_CMD_SACCH_REL_MARKER; + queue_sapi_command(lchan, cmd); +} + +int bts_model_lchan_deactivate_sacch(struct gsm_lchan *lchan) +{ + enqueue_sacch_rel_marker(lchan); + return 0; +} + +/* callback from OML */ +int bts_model_check_oml(struct gsm_bts *bts, uint8_t msg_type, + struct tlv_parsed *old_attr, struct tlv_parsed *new_attr, + void *obj) +{ + /* FIXME: more checks if the attributes are valid */ + + switch (msg_type) { + case NM_MT_SET_CHAN_ATTR: + /* our L1 only supports one global TSC for all channels + * one one TRX, so we need to make sure not to activate + * channels with a different TSC!! */ + if (TLVP_PRESENT(new_attr, NM_ATT_TSC) && + TLVP_LEN(new_attr, NM_ATT_TSC) >= 1 && + *TLVP_VAL(new_attr, NM_ATT_TSC) != (bts->bsic & 7)) { + LOGP(DOML, LOGL_ERROR, "Channel TSC %u != BSIC-TSC %u\n", + *TLVP_VAL(new_attr, NM_ATT_TSC), bts->bsic & 7); + return -NM_NACK_PARAM_RANGE; + } + break; + } + return 0; +} + +/* callback from OML */ +int bts_model_apply_oml(struct gsm_bts *bts, struct msgb *msg, + struct tlv_parsed *new_attr, int kind, void *obj) +{ + if (kind == NM_OC_RADIO_CARRIER) { + struct gsm_bts_trx *trx = obj; + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(trx); + /* convert max TA to max cell size in qbits */ + uint8_t cell_size = bts->max_ta << 2; + +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) + /* We do not need to check for L1 handle + * because the max cell size parameter can receive before MphInit */ + if (fl1h->phy_inst->u.lc15.max_cell_size != cell_size) { + /* instruct L1 to apply max cell size */ + l1if_set_max_cell_size(fl1h, cell_size); + /* update current max cell size */ + fl1h->phy_inst->u.lc15.max_cell_size = cell_size; + } +#endif + + /* Did we go through MphInit yet? If yes fire and forget */ + if (fl1h->hLayer1) { + power_ramp_start(trx, get_p_target_mdBm(trx, 0), 0, NULL); +#if LITECELL15_API_VERSION >= LITECELL15_API(2,1,7) + if (fl1h->phy_inst->u.lc15.tx_pwr_red_8psk != trx->max_power_backoff_8psk) { + /* update current Tx power backoff for 8-PSK */ + fl1h->phy_inst->u.lc15.tx_pwr_red_8psk = trx->max_power_backoff_8psk; + /* instruct L1 to apply Tx power backoff for 8 PSK */ + l1if_set_txpower_backoff_8psk(fl1h, fl1h->phy_inst->u.lc15.tx_pwr_red_8psk); + } + + if (fl1h->phy_inst->u.lc15.tx_c0_idle_pwr_red != trx->c0_idle_power_red) { + /* update current C0 idle slot Tx power reduction */ + fl1h->phy_inst->u.lc15.tx_c0_idle_pwr_red = trx->c0_idle_power_red; + /* instruct L1 to apply C0 idle slot power reduction */ + l1if_set_txpower_c0_idle_pwr_red(fl1h, fl1h->phy_inst->u.lc15.tx_c0_idle_pwr_red); + } +#endif + } + } + /* FIXME: we actually need to send a ACK or NACK for the OML message */ + return oml_fom_ack_nack(msg, 0); +} + +/* callback from OML */ +int bts_model_opstart(struct gsm_bts *bts, struct gsm_abis_mo *mo, + void *obj) +{ + struct gsm_bts_bb_trx *bb_transc; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + int rc; + + switch (mo->obj_class) { + case NM_OC_SITE_MANAGER: + rc = osmo_fsm_inst_dispatch(bts->site_mgr.mo.fi, NM_EV_OPSTART_ACK, NULL); + break; + case NM_OC_BTS: + rc = osmo_fsm_inst_dispatch(bts->mo.fi, NM_EV_OPSTART_ACK, NULL); + break; + case NM_OC_RADIO_CARRIER: + trx = (struct gsm_bts_trx *) obj; + rc = trx_init(trx); + break; + case NM_OC_BASEB_TRANSC: + bb_transc = (struct gsm_bts_bb_trx *) obj; + rc = osmo_fsm_inst_dispatch(bb_transc->mo.fi, NM_EV_OPSTART_ACK, NULL); + break; + case NM_OC_CHANNEL: + ts = (struct gsm_bts_trx_ts*) obj; + rc = ts_opstart(ts); + break; + case NM_OC_GPRS_NSE: + case NM_OC_GPRS_CELL: + case NM_OC_GPRS_NSVC: + oml_mo_state_chg(mo, NM_OPSTATE_ENABLED, -1); + rc = oml_mo_opstart_ack(mo); + if (mo->obj_class == NM_OC_BTS) { + oml_mo_state_chg(&bts->mo, -1, NM_AVSTATE_OK); + oml_mo_state_chg(&bts->gprs.nse.mo, -1, NM_AVSTATE_OK); + oml_mo_state_chg(&bts->gprs.cell.mo, -1, NM_AVSTATE_OK); + oml_mo_state_chg(&bts->gprs.nsvc[0].mo, -1, NM_AVSTATE_OK); + } + break; + default: + rc = oml_mo_opstart_nack(mo, NM_NACK_OBJCLASS_NOTSUPP); + } + return rc; +} + +int bts_model_chg_adm_state(struct gsm_bts *bts, struct gsm_abis_mo *mo, + void *obj, uint8_t adm_state) +{ + int rc = -EINVAL; + int granted = 0; + + switch (mo->obj_class) { + case NM_OC_RADIO_CARRIER: + + if (mo->procedure_pending) { + LOGP(DL1C, LOGL_ERROR, "Discarding adm change command: " + "pending procedure on RC %d\n", + ((struct gsm_bts_trx *)obj)->nr); + return 0; + } + mo->procedure_pending = 1; + switch (adm_state) { + case NM_STATE_LOCKED: + rc = trx_rf_lock(obj, 1, NULL); + break; + case NM_STATE_UNLOCKED: + rc = trx_rf_lock(obj, 0, NULL); + break; + default: + granted = 1; + break; + } + + if (!granted && rc == 0) + /* in progress, will send ack/nack after completion */ + return 0; + + mo->procedure_pending = 0; + + break; + default: + /* blindly accept all state changes */ + granted = 1; + break; + } + + if (granted) { + mo->nm_state.administrative = adm_state; + return oml_mo_statechg_ack(mo); + } else + return oml_mo_statechg_nack(mo, NM_NACK_REQ_NOT_GRANT); + +} + +int l1if_rsl_chan_act(struct gsm_lchan *lchan) +{ + //uint8_t mode = *TLVP_VAL(tp, RSL_IE_CHAN_MODE); + //uint8_t type = *TLVP_VAL(tp, RSL_IE_ACT_TYPE); + lchan_activate(lchan); + return 0; +} + +/** + * Modify the given lchan in the handover scenario. This is a lot like + * second channel activation but with some additional activation. + */ +int l1if_rsl_chan_mod(struct gsm_lchan *lchan) +{ + const struct lchan_sapis *s4l = &sapis_for_lchan[lchan->type]; + unsigned int i; + + if (lchan->ho.active == HANDOVER_NONE) + return -1; + + LOGP(DHO, LOGL_ERROR, "%s modifying channel for handover\n", + gsm_lchan_name(lchan)); + + /* Give up listening to RACH bursts */ + release_sapis_for_ho(lchan); + + /* Activate the normal SAPIs */ + for (i = 0; i < s4l->num_sapis; i++) { + int sapi = s4l->sapis[i].sapi; + int dir = s4l->sapis[i].dir; + enqueue_sapi_act_cmd(lchan, sapi, dir); + } + + return 0; +} + +int l1if_rsl_chan_rel(struct gsm_lchan *lchan) +{ + /* A duplicate RF Release Request, ignore it */ + if (lchan->state == LCHAN_S_REL_REQ) { + LOGP(DL1C, LOGL_ERROR, "%s already in release request state.\n", + gsm_lchan_name(lchan)); + return 0; + } + + lchan_deactivate(lchan); + return 0; +} + +int l1if_rsl_deact_sacch(struct gsm_lchan *lchan) +{ + /* Only de-activate the SACCH if the lchan is active */ + if (lchan->state != LCHAN_S_ACTIVE) + return 0; + return bts_model_lchan_deactivate_sacch(lchan); +} + +int bts_model_trx_deact_rf(struct gsm_bts_trx *trx) +{ + struct lc15l1_hdl *fl1 = trx_lc15l1_hdl(trx); + + return l1if_activate_rf(fl1, 0); +} + +int bts_model_change_power(struct gsm_bts_trx *trx, int p_trxout_mdBm) +{ + return l1if_set_txpower(trx_lc15l1_hdl(trx), ((float) p_trxout_mdBm)/1000.0); +} + +static int ts_disconnect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphDisconnectCnf_t *cnf = &l1p->u.mphDisconnectCnf; + struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn]; + OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS); + + LOGP(DL1C, LOGL_DEBUG, "%s Rx mphDisconnectCnf\n", + gsm_lchan_name(ts->lchan)); + + cb_ts_disconnected(ts); + + msgb_free(l1_msg); + + return 0; +} + +int bts_model_ts_disconnect(struct gsm_bts_trx_ts *ts) +{ + struct msgb *msg = l1p_msgb_alloc(); + struct lc15l1_hdl *fl1h = trx_lc15l1_hdl(ts->trx); + GsmL1_MphDisconnectReq_t *cr; + + DEBUGP(DRSL, "%s TS disconnect\n", gsm_lchan_name(ts->lchan)); + cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphDisconnectReq, fl1h, + l1p_handle_for_ts(ts)); + cr->u8Tn = ts->nr; + + return l1if_gsm_req_compl(fl1h, msg, ts_disconnect_cb, NULL); +} + +static int ts_connect_cb(struct gsm_bts_trx *trx, struct msgb *l1_msg, + void *data) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1_msg); + GsmL1_MphConnectCnf_t *cnf = &l1p->u.mphConnectCnf; + struct gsm_bts_trx_ts *ts = &trx->ts[cnf->u8Tn]; + OSMO_ASSERT(cnf->u8Tn < TRX_NR_TS); + + DEBUGP(DL1C, "%s %s Rx mphConnectCnf flags=%s%s%s\n", + gsm_lchan_name(ts->lchan), + gsm_pchan_name(ts->pchan), + ts->flags & TS_F_PDCH_ACTIVE ? "ACTIVE " : "", + ts->flags & TS_F_PDCH_ACT_PENDING ? "ACT_PENDING " : "", + ts->flags & TS_F_PDCH_DEACT_PENDING ? "DEACT_PENDING " : ""); + + cb_ts_connected(ts, 0); + + msgb_free(l1_msg); + + return 0; +} + +void bts_model_ts_connect(struct gsm_bts_trx_ts *ts, + enum gsm_phys_chan_config as_pchan) +{ + int rc; + + rc = ts_connect_as(ts, as_pchan, ts_connect_cb, NULL); + if (rc) + cb_ts_connected(ts, rc); +} diff --git a/src/osmo-bts-lc15/tch.c b/src/osmo-bts-lc15/tch.c new file mode 100644 index 00000000..526b2ddf --- /dev/null +++ b/src/osmo-bts-lc15/tch.c @@ -0,0 +1,533 @@ +/* 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 <stdbool.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/select.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/bits.h> +#include <osmocom/gsm/gsm_utils.h> + +#include <osmo-bts/logging.h> +#include <osmo-bts/bts.h> +#include <osmo-bts/gsm_data.h> +#include <osmo-bts/msg_utils.h> +#include <osmo-bts/measurement.h> +#include <osmo-bts/amr.h> +#include <osmo-bts/l1sap.h> +#include <osmo-bts/dtx_dl_amr_fsm.h> + +#include <nrw/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" + +static struct msgb *l1_to_rtppayload_fr(uint8_t *l1_payload, uint8_t payload_len, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + + /* new L1 can deliver bits like we need them */ + cur = msgb_put(msg, GSM_FR_BYTES); + memcpy(cur, l1_payload, GSM_FR_BYTES); + + lchan_set_marker(osmo_fr_check_sid(l1_payload, payload_len), lchan); + + return msg; +} + +/*! \brief convert GSM-FR from RTP payload to L1 format + * \param[out] l1_payload payload part of L1 buffer + * \param[in] rtp_payload pointer to RTP payload data + * \param[in] payload_len length of \a rtp_payload + * \returns number of \a l1_payload bytes filled + */ +static int rtppayload_to_l1_fr(uint8_t *l1_payload, const uint8_t *rtp_payload, + unsigned int payload_len) +{ + /* new L1 can deliver bits like we need them */ + memcpy(l1_payload, rtp_payload, GSM_FR_BYTES); + return GSM_FR_BYTES; +} + +static struct msgb *l1_to_rtppayload_efr(uint8_t *l1_payload, + uint8_t payload_len, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + + /* new L1 can deliver bits like we need them */ + cur = msgb_put(msg, GSM_EFR_BYTES); + memcpy(cur, l1_payload, GSM_EFR_BYTES); + enum osmo_amr_type ft; + enum osmo_amr_quality bfi; + uint8_t cmr; + int8_t sti, cmi; + osmo_amr_rtp_dec(l1_payload, payload_len, &cmr, &cmi, &ft, &bfi, &sti); + lchan_set_marker(ft == AMR_GSM_EFR_SID, lchan); + + return msg; +} + +static int rtppayload_to_l1_efr(uint8_t *l1_payload, const uint8_t *rtp_payload, + unsigned int payload_len) +{ + memcpy(l1_payload, rtp_payload, payload_len); + + return payload_len; +} + +static struct msgb *l1_to_rtppayload_hr(uint8_t *l1_payload, uint8_t payload_len, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + + if (payload_len != GSM_HR_BYTES) { + LOGP(DL1P, LOGL_ERROR, "L1 HR frame length %u != expected %u\n", + payload_len, GSM_HR_BYTES); + return NULL; + } + + cur = msgb_put(msg, GSM_HR_BYTES); + memcpy(cur, l1_payload, GSM_HR_BYTES); + + lchan_set_marker(osmo_hr_check_sid(l1_payload, payload_len), lchan); + + return msg; +} + +/*! \brief convert GSM-FR from RTP payload to L1 format + * \param[out] l1_payload payload part of L1 buffer + * \param[in] rtp_payload pointer to RTP payload data + * \param[in] payload_len length of \a rtp_payload + * \returns number of \a l1_payload bytes filled + */ +static int rtppayload_to_l1_hr(uint8_t *l1_payload, const uint8_t *rtp_payload, + unsigned int payload_len) +{ + + if (payload_len != GSM_HR_BYTES) { + LOGP(DL1P, LOGL_ERROR, "RTP HR frame length %u != expected %u\n", + payload_len, GSM_HR_BYTES); + return 0; + } + + memcpy(l1_payload, rtp_payload, GSM_HR_BYTES); + + return GSM_HR_BYTES; +} + +static struct msgb *l1_to_rtppayload_amr(uint8_t *l1_payload, uint8_t payload_len, + struct gsm_lchan *lchan) +{ + struct msgb *msg; + uint8_t amr_if2_len = payload_len - 2; + uint8_t *cur; + + msg = msgb_alloc_headroom(1024, 128, "L1P-to-RTP"); + if (!msg) + return NULL; + + cur = msgb_put(msg, amr_if2_len); + memcpy(cur, l1_payload+2, amr_if2_len); + + /* + * Audiocode's MGW doesn't like receiving CMRs that are not + * the same as the previous one. This means we need to patch + * the content here. + */ + if ((cur[0] & 0xF0) == 0xF0) + cur[0]= lchan->tch.last_cmr << 4; + else + lchan->tch.last_cmr = cur[0] >> 4; + + return msg; +} + +/*! \brief convert AMR from RTP payload to L1 format + * \param[out] l1_payload payload part of L1 buffer + * \param[in] rtp_payload pointer to RTP payload data + * \param[in] payload_len length of \a rtp_payload + * \returns number of \a l1_payload bytes filled + */ +static int rtppayload_to_l1_amr(uint8_t *l1_payload, const uint8_t *rtp_payload, + uint8_t payload_len, uint8_t ft) +{ + memcpy(l1_payload, rtp_payload, payload_len); + return payload_len; +} + +#define RTP_MSGB_ALLOC_SIZE 512 + +/*! \brief function for incoming RTP via TCH.req + * \param[in] rtp_pl buffer containing RTP payload + * \param[in] rtp_pl_len length of \a rtp_pl + * \param[in] use_cache Use cached payload instead of parsing RTP + * \param[in] marker RTP header Marker bit (indicates speech onset) + * \returns 0 if encoding result can be sent further to L1 without extra actions + * positive value if data is ready AND extra actions are required + * negative value otherwise (no data for L1 encoded) + * + * This function prepares a msgb with a L1 PH-DATA.req primitive and + * queues it into lchan->dl_tch_queue. + * + * Note that the actual L1 primitive header is not fully initialized + * yet, as things like the frame number, etc. are unknown at the time we + * pre-fill the primtive. + */ +int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len, + const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn, + bool use_cache, bool marker) +{ + uint8_t *payload_type; + uint8_t *l1_payload, ft; + int rc = 0; + bool is_sid = false; + + DEBUGP(DRTP, "%s RTP IN: %s\n", gsm_lchan_name(lchan), + osmo_hexdump(rtp_pl, rtp_pl_len)); + + payload_type = &data[0]; + l1_payload = &data[1]; + + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + if (lchan->type == GSM_LCHAN_TCH_F) { + *payload_type = GsmL1_TchPlType_Fr; + rc = rtppayload_to_l1_fr(l1_payload, + rtp_pl, rtp_pl_len); + if (rc && lchan->ts->trx->bts->dtxd) + is_sid = osmo_fr_check_sid(rtp_pl, rtp_pl_len); + } else{ + *payload_type = GsmL1_TchPlType_Hr; + rc = rtppayload_to_l1_hr(l1_payload, + rtp_pl, rtp_pl_len); + if (rc && lchan->ts->trx->bts->dtxd) + is_sid = osmo_hr_check_sid(rtp_pl, rtp_pl_len); + } + if (is_sid) + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, -1); + break; + case GSM48_CMODE_SPEECH_EFR: + *payload_type = GsmL1_TchPlType_Efr; + rc = rtppayload_to_l1_efr(l1_payload, rtp_pl, + rtp_pl_len); + /* FIXME: detect and save EFR SID */ + break; + case GSM48_CMODE_SPEECH_AMR: + if (use_cache) { + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload, lchan->tch.dtx.cache, + lchan->tch.dtx.len, ft); + *len = lchan->tch.dtx.len + 1; + return 0; + } + + rc = dtx_dl_amr_fsm_step(lchan, rtp_pl, rtp_pl_len, fn, + l1_payload, marker, len, &ft); + if (rc < 0) + return rc; + if (!dtx_dl_amr_enabled(lchan)) { + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + } + + /* DTX DL-specific logic below: */ + switch (lchan->tch.dtx.dl_amr_fsm->state) { + case ST_ONSET_V: + *payload_type = GsmL1_TchPlType_Amr_Onset; + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0); + *len = 3; + return 1; + case ST_VOICE: + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + case ST_SID_F1: + if (lchan->type == GSM_LCHAN_TCH_H) { /* AMR HR */ + *payload_type = GsmL1_TchPlType_Amr_SidFirstP1; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, + rtp_pl_len, ft); + return 0; + } + /* AMR FR */ + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + case ST_SID_F2: + *payload_type = GsmL1_TchPlType_Amr; + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len, + ft); + return 0; + case ST_F1_INH_V: + *payload_type = GsmL1_TchPlType_Amr_SidFirstInH; + *len = 3; + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0); + return 1; + case ST_U_INH_V: + *payload_type = GsmL1_TchPlType_Amr_SidUpdateInH; + *len = 3; + dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0); + return 1; + case ST_SID_U: + case ST_U_NOINH: + return -EAGAIN; + case ST_FACCH: + return -EBADMSG; + default: + LOGP(DRTP, LOGL_ERROR, "Unhandled DTX DL AMR FSM state " + "%d\n", lchan->tch.dtx.dl_amr_fsm->state); + return -EINVAL; + } + break; + default: + /* we don't support CSD modes */ + rc = -1; + break; + } + + if (rc < 0) { + LOGP(DRTP, LOGL_ERROR, "%s unable to parse RTP payload\n", + gsm_lchan_name(lchan)); + return -EBADMSG; + } + + *len = rc + 1; + + DEBUGP(DRTP, "%s RTP->L1: %s\n", gsm_lchan_name(lchan), + osmo_hexdump(data, *len)); + return 0; +} + +static int is_recv_only(uint8_t speech_mode) +{ + return (speech_mode & 0xF0) == (1 << 4); +} + +/*! \brief receive a traffic L1 primitive for a given lchan */ +int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg) +{ + GsmL1_Prim_t *l1p = msgb_l1prim(l1p_msg); + GsmL1_PhDataInd_t *data_ind = &l1p->u.phDataInd; + uint8_t *payload, payload_type, payload_len, sid_first[9] = { 0 }; + struct msgb *rmsg = NULL; + struct gsm_lchan *lchan = &trx->ts[L1SAP_CHAN2TS(chan_nr)].lchan[l1sap_chan2ss(chan_nr)]; + + if (is_recv_only(lchan->abis_ip.speech_mode)) + return -EAGAIN; + + if (data_ind->msgUnitParam.u8Size < 1) { + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "chan_nr %d Rx Payload size 0\n", chan_nr); + /* Push empty payload to upper layers */ + rmsg = msgb_alloc_headroom(256, 128, "L1P-to-RTP"); + return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn, + data_ind->measParam.fBer * 10000, + data_ind->measParam.fLinkQuality * 10, 0, 0, 0); + } + + payload_type = data_ind->msgUnitParam.u8Buffer[0]; + payload = data_ind->msgUnitParam.u8Buffer + 1; + 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; + case GsmL1_TchPlType_Amr_Onset: + if (lchan->type != GSM_LCHAN_TCH_H && + lchan->type != GSM_LCHAN_TCH_F) + goto err_payload_match; + /* according to 3GPP TS 26.093 ONSET frames precede the first + speech frame of a speech burst - set the marker for next RTP + frame */ + lchan->rtp_tx_marker = true; + break; + case GsmL1_TchPlType_Amr_SidFirstP1: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P1 from L1 " + "(%d bytes)\n", payload_len); + break; + case GsmL1_TchPlType_Amr_SidFirstP2: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_P2 from L1 " + "(%d bytes)\n", payload_len); + break; + case GsmL1_TchPlType_Amr_SidFirstInH: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + lchan->rtp_tx_marker = true; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_FIRST_INH from L1 " + "(%d bytes)\n", payload_len); + break; + case GsmL1_TchPlType_Amr_SidUpdateInH: + if (lchan->type != GSM_LCHAN_TCH_H) + goto err_payload_match; + lchan->rtp_tx_marker = true; + LOGPFN(DL1P, LOGL_DEBUG, data_ind->u32Fn, "DTX: received SID_UPDATE_INH from L1 " + "(%d bytes)\n", payload_len); + break; + default: + LOGPFN(DL1P, LOGL_NOTICE, data_ind->u32Fn, "%s Rx Payload Type %s is unsupported\n", + gsm_lchan_name(lchan), + get_value_string(lc15bts_tch_pl_names, payload_type)); + break; + } + + + switch (payload_type) { + case GsmL1_TchPlType_Fr: + rmsg = l1_to_rtppayload_fr(payload, payload_len, lchan); + break; + case GsmL1_TchPlType_Hr: + rmsg = l1_to_rtppayload_hr(payload, payload_len, lchan); + break; + case GsmL1_TchPlType_Efr: + rmsg = l1_to_rtppayload_efr(payload, payload_len, lchan); + break; + case GsmL1_TchPlType_Amr: + rmsg = l1_to_rtppayload_amr(payload, payload_len, lchan); + break; + case GsmL1_TchPlType_Amr_SidFirstP1: + memcpy(sid_first, payload, payload_len); + int len = osmo_amr_rtp_enc(sid_first, 0, AMR_SID, AMR_GOOD); + if (len < 0) + return 0; + rmsg = l1_to_rtppayload_amr(sid_first, len, lchan); + break; + } + + if (rmsg) + return add_l1sap_header(trx, rmsg, lchan, chan_nr, data_ind->u32Fn, + data_ind->measParam.fBer * 10000, + data_ind->measParam.fLinkQuality * 10, 0, 0, 0); + + return 0; + +err_payload_match: + LOGPFN(DL1P, LOGL_ERROR, data_ind->u32Fn, "%s Rx Payload Type %s incompatible with lchan\n", + gsm_lchan_name(lchan), get_value_string(lc15bts_tch_pl_names, payload_type)); + return -EINVAL; +} + +struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan, uint32_t fn) +{ + struct msgb *msg; + GsmL1_Prim_t *l1p; + GsmL1_PhDataReq_t *data_req; + GsmL1_MsgUnitParam_t *msu_param; + uint8_t *payload_type; + uint8_t *l1_payload; + int rc; + + msg = l1p_msgb_alloc(); + if (!msg) + return NULL; + + l1p = msgb_l1prim(msg); + data_req = &l1p->u.phDataReq; + msu_param = &data_req->msgUnitParam; + payload_type = &msu_param->u8Buffer[0]; + l1_payload = &msu_param->u8Buffer[1]; + + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_AMR: + if (lchan->type == GSM_LCHAN_TCH_H && + dtx_dl_amr_enabled(lchan)) { + /* we have to explicitly handle sending SID FIRST P2 for + AMR HR in here */ + *payload_type = GsmL1_TchPlType_Amr_SidFirstP2; + rc = dtx_dl_amr_fsm_step(lchan, NULL, 0, fn, l1_payload, + false, &(msu_param->u8Size), + NULL); + if (rc == 0) + return msg; + } + *payload_type = GsmL1_TchPlType_Amr; + break; + case GSM48_CMODE_SPEECH_V1: + if (lchan->type == GSM_LCHAN_TCH_F) + *payload_type = GsmL1_TchPlType_Fr; + else + *payload_type = GsmL1_TchPlType_Hr; + break; + case GSM48_CMODE_SPEECH_EFR: + *payload_type = GsmL1_TchPlType_Efr; + break; + default: + msgb_free(msg); + return NULL; + } + + rc = repeat_last_sid(lchan, l1_payload, fn); + if (!rc) { + msgb_free(msg); + return NULL; + } + msu_param->u8Size = rc; + + return msg; +} diff --git a/src/osmo-bts-lc15/utils.c b/src/osmo-bts-lc15/utils.c new file mode 100644 index 00000000..8d980ba8 --- /dev/null +++ b/src/osmo-bts-lc15/utils.c @@ -0,0 +1,118 @@ +/* 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; + int rc; + + if (!bts->auto_band) + return band_osmo2lc15(trx, bts->band); + + /* + * We need to check what will happen now. + */ + rc = gsm_arfcn2band_rc(arfcn, &band); + if (rc) /* wrong ARFCN, give up */ + return -1; + + /* 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; +} diff --git a/src/osmo-bts-lc15/utils.h b/src/osmo-bts-lc15/utils.h new file mode 100644 index 00000000..a2a22348 --- /dev/null +++ b/src/osmo-bts-lc15/utils.h @@ -0,0 +1,13 @@ +#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); + +#endif |