aboutsummaryrefslogtreecommitdiffstats
path: root/src/osmo-bts-oc2g/misc
diff options
context:
space:
mode:
authorOmar Ramadan <oramadan@fb.com>2018-10-23 15:42:46 -0700
committerHarald Welte <laforge@gnumonks.org>2018-10-27 11:35:58 +0000
commit9c75c387c04175298d7fae3dd432d84bd622fccd (patch)
tree5275a426f7aa23564b82d4bb7f434aa46219734d /src/osmo-bts-oc2g/misc
parentee9e8e9eb22257000765f4c6a5138fc0200e864f (diff)
Add OC-2G BTS sources
Diffstat (limited to 'src/osmo-bts-oc2g/misc')
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_bid.c175
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_bid.h47
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_bts.c129
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_bts.h21
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_clock.c263
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_clock.h16
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_led.c332
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_led.h22
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_mgr.c353
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_mgr.h398
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c750
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c208
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_mgr_temp.c980
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_mgr_vty.c984
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_misc.c381
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_misc.h17
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_nl.c123
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_nl.h27
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_par.c249
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_par.h43
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_power.c177
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_power.h36
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_swd.c178
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_swd.h7
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_temp.c71
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_temp.h26
-rw-r--r--src/osmo-bts-oc2g/misc/oc2gbts_util.c158
27 files changed, 6171 insertions, 0 deletions
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_bid.c b/src/osmo-bts-oc2g/misc/oc2gbts_bid.c
new file mode 100644
index 00000000..6eaa9c69
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_bid.c
@@ -0,0 +1,175 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include "oc2gbts_bid.h"
+
+#define BOARD_REV_MAJ_SYSFS "/var/oc2g/platform/rev/major"
+#define BOARD_REV_MIN_SYSFS "/var/oc2g/platform/rev/minor"
+#define BOARD_OPT_SYSFS "/var/oc2g/platform/option"
+
+static const int option_type_mask[_NUM_OPTION_TYPES] = {
+ [OC2GBTS_OPTION_OCXO] = 0x07,
+ [OC2GBTS_OPTION_FPGA] = 0x03,
+ [OC2GBTS_OPTION_PA] = 0x01,
+ [OC2GBTS_OPTION_BAND] = 0x03,
+ [OC2GBTS_OPTION_TX_ATT] = 0x01,
+ [OC2GBTS_OPTION_TX_FIL] = 0x01,
+ [OC2GBTS_OPTION_RX_ATT] = 0x01,
+ [OC2GBTS_OPTION_RMS_FWD] = 0x01,
+ [OC2GBTS_OPTION_RMS_REFL] = 0x01,
+ [OC2GBTS_OPTION_DDR_32B] = 0x01,
+ [OC2GBTS_OPTION_DDR_ECC] = 0x01,
+ [OC2GBTS_OPTION_PA_TEMP] = 0x01,
+};
+
+static const int option_type_shift[_NUM_OPTION_TYPES] = {
+ [OC2GBTS_OPTION_OCXO] = 0,
+ [OC2GBTS_OPTION_FPGA] = 3,
+ [OC2GBTS_OPTION_PA] = 5,
+ [OC2GBTS_OPTION_BAND] = 6,
+ [OC2GBTS_OPTION_TX_ATT] = 8,
+ [OC2GBTS_OPTION_TX_FIL] = 9,
+ [OC2GBTS_OPTION_RX_ATT] = 10,
+ [OC2GBTS_OPTION_RMS_FWD] = 11,
+ [OC2GBTS_OPTION_RMS_REFL] = 12,
+ [OC2GBTS_OPTION_DDR_32B] = 13,
+ [OC2GBTS_OPTION_DDR_ECC] = 14,
+ [OC2GBTS_OPTION_PA_TEMP] = 15,
+};
+
+
+static char board_rev_maj = -1;
+static char board_rev_min = -1;
+static int board_option = -1;
+
+void oc2gbts_rev_get(char *rev_maj, char *rev_min)
+{
+ FILE *fp;
+ char rev;
+
+ *rev_maj = 0;
+ *rev_min = 0;
+
+ if (board_rev_maj != -1 && board_rev_min != -1) {
+ *rev_maj = board_rev_maj;
+ *rev_min = board_rev_min;
+ }
+
+ fp = fopen(BOARD_REV_MAJ_SYSFS, "r");
+ if (fp == NULL) return;
+
+ if (fscanf(fp, "%c", &rev) != 1) {
+ fclose(fp);
+ return;
+ }
+
+ fclose(fp);
+
+ *rev_maj = board_rev_maj = rev;
+
+ fp = fopen(BOARD_REV_MIN_SYSFS, "r");
+ if (fp == NULL) return;
+
+ if (fscanf(fp, "%c", &rev) != 1) {
+ fclose(fp);
+ return;
+ }
+
+ fclose(fp);
+
+ *rev_min = board_rev_min = rev;
+}
+
+const char* get_hwversion_desc()
+{
+ int rev;
+ int model;
+ size_t len;
+ static char model_name[64] = {0, };
+ len = snprintf(model_name, sizeof(model_name), "NuRAN OC-2G BTS");
+
+ char rev_maj = 0, rev_min = 0;
+
+ int rc = 0;
+ oc2gbts_rev_get(&rev_maj, &rev_min);
+ if (rc < 0)
+ return rc;
+ if (rev >= 0) {
+ len += snprintf(model_name + len, sizeof(model_name) - len,
+ " Rev %d.%d", (uint8_t)rev_maj, (uint8_t)rev_min);
+ }
+
+ model = oc2gbts_model_get();
+ if (model >= 0) {
+ snprintf(model_name + len, sizeof(model_name) - len,
+ "%s (%05X)", model_name, model);
+ }
+ return model_name;
+}
+
+int oc2gbts_model_get(void)
+{
+ FILE *fp;
+ int opt;
+
+
+ if (board_option == -1) {
+ fp = fopen(BOARD_OPT_SYSFS, "r");
+ if (fp == NULL) {
+ return -1;
+ }
+
+ if (fscanf(fp, "%X", &opt) != 1) {
+ fclose( fp );
+ return -1;
+ }
+ fclose(fp);
+
+ board_option = opt;
+ }
+ return board_option;
+}
+
+int oc2gbts_option_get(enum oc2gbts_option_type type)
+{
+ int rc;
+ int option;
+
+ if (type >= _NUM_OPTION_TYPES) {
+ return -EINVAL;
+ }
+
+ if (board_option == -1) {
+ rc = oc2gbts_model_get();
+ if (rc < 0) return rc;
+ }
+
+ option = (board_option >> option_type_shift[type])
+ & option_type_mask[type];
+
+ return option;
+}
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_bid.h b/src/osmo-bts-oc2g/misc/oc2gbts_bid.h
new file mode 100644
index 00000000..00cf3899
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_bid.h
@@ -0,0 +1,47 @@
+#ifndef _OC2GBTS_BOARD_H
+#define _OC2GBTS_BOARD_H
+
+#include <stdint.h>
+
+enum oc2gbts_option_type {
+ OC2GBTS_OPTION_OCXO,
+ OC2GBTS_OPTION_FPGA,
+ OC2GBTS_OPTION_PA,
+ OC2GBTS_OPTION_BAND,
+ OC2GBTS_OPTION_TX_ATT,
+ OC2GBTS_OPTION_TX_FIL,
+ OC2GBTS_OPTION_RX_ATT,
+ OC2GBTS_OPTION_RMS_FWD,
+ OC2GBTS_OPTION_RMS_REFL,
+ OC2GBTS_OPTION_DDR_32B,
+ OC2GBTS_OPTION_DDR_ECC,
+ OC2GBTS_OPTION_PA_TEMP,
+ _NUM_OPTION_TYPES
+};
+
+enum oc2gbts_ocxo_type {
+ OC2GBTS_OCXO_BILAY_NVG45AV2072,
+ OC2GBTS_OCXO_TAITIEN_NJ26M003,
+ _NUM_OCXO_TYPES
+};
+
+enum oc2gbts_fpga_type {
+ OC2GBTS_FPGA_35T,
+ OC2GBTS_FPGA_50T,
+ OC2GBTS_FPGA_75T,
+ OC2GBTS_FPGA_100T,
+ _NUM_FPGA_TYPES
+};
+
+enum oc2gbts_gsm_band {
+ OC2GBTS_BAND_850,
+ OC2GBTS_BAND_900,
+ OC2GBTS_BAND_1800,
+ OC2GBTS_BAND_1900,
+};
+
+void oc2gbts_rev_get(char *rev_maj, char *rev_min);
+int oc2gbts_model_get(void);
+int oc2gbts_option_get(enum oc2gbts_option_type type);
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_bts.c b/src/osmo-bts-oc2g/misc/oc2gbts_bts.c
new file mode 100644
index 00000000..b3dae76e
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_bts.c
@@ -0,0 +1,129 @@
+/* Copyright (C) 2016 by NuRAN Wireless <support@nuranwireless.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <ifaddrs.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include "oc2gbts_mgr.h"
+#include "oc2gbts_bts.h"
+
+static int check_eth_status(char *dev_name)
+{
+ int fd, rc;
+ struct ifreq ifr;
+
+ fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
+ if (fd < 0)
+ return fd;
+
+ memset(&ifr, 0, sizeof(ifr));
+ memcpy(&ifr.ifr_name, dev_name, sizeof(ifr.ifr_name));
+ rc = ioctl(fd, SIOCGIFFLAGS, &ifr);
+ close(fd);
+
+ if (rc < 0)
+ return rc;
+
+ if ((ifr.ifr_flags & IFF_UP) && (ifr.ifr_flags & IFF_RUNNING))
+ return 0;
+
+ return 1;
+}
+
+void check_bts_led_pattern(uint8_t *led)
+{
+ FILE *fp;
+ char str[64] = "\0";
+ int rc;
+
+ /* check for existing of BTS state file */
+ if ((fp = fopen("/var/run/osmo-bts/state", "r")) == NULL) {
+ led[BLINK_PATTERN_INIT] = 1;
+ return;
+ }
+
+ /* check Ethernet interface status */
+ rc = check_eth_status("eth0");
+ if (rc > 0) {
+ LOGP(DTEMP, LOGL_DEBUG,"External link is DOWN\n");
+ led[BLINK_PATTERN_EXT_LINK_MALFUNC] = 1;
+ fclose(fp);
+ return;
+ }
+
+ /* check for BTS is still alive */
+ if (system("pidof osmo-bts-oc2g > /dev/null")) {
+ LOGP(DTEMP, LOGL_DEBUG,"BTS process has stopped\n");
+ led[BLINK_PATTERN_INT_PROC_MALFUNC] = 1;
+ fclose(fp);
+ return;
+ }
+
+ /* check for BTS state */
+ while (fgets(str, 64, fp) != NULL) {
+ LOGP(DTEMP, LOGL_DEBUG,"BTS state is %s\n", (strstr(str, "ABIS DOWN") != NULL) ? "DOWN" : "UP");
+ if (strstr(str, "ABIS DOWN") != NULL)
+ led[BLINK_PATTERN_INT_PROC_MALFUNC] = 1;
+ }
+ fclose(fp);
+
+ return;
+}
+
+int check_sensor_led_pattern( struct oc2gbts_mgr_instance *mgr, uint8_t *led)
+{
+ if(mgr->alarms.temp_high == 1)
+ led[BLINK_PATTERN_TEMP_HIGH] = 1;
+
+ if(mgr->alarms.temp_max == 1)
+ led[BLINK_PATTERN_TEMP_MAX] = 1;
+
+ if(mgr->alarms.supply_low == 1)
+ led[BLINK_PATTERN_SUPPLY_VOLT_LOW] = 1;
+
+ if(mgr->alarms.supply_min == 1)
+ led[BLINK_PATTERN_SUPPLY_VOLT_MIN] = 1;
+
+ if(mgr->alarms.vswr_high == 1)
+ led[BLINK_PATTERN_VSWR_HIGH] = 1;
+
+ if(mgr->alarms.vswr_max == 1)
+ led[BLINK_PATTERN_VSWR_MAX] = 1;
+
+ if(mgr->alarms.supply_pwr_high == 1)
+ led[BLINK_PATTERN_SUPPLY_PWR_HIGH] = 1;
+
+ if(mgr->alarms.supply_pwr_max == 1)
+ led[BLINK_PATTERN_SUPPLY_PWR_MAX] = 1;
+
+ if(mgr->alarms.pa_pwr_high == 1)
+ led[BLINK_PATTERN_PA_PWR_HIGH] = 1;
+
+ if(mgr->alarms.pa_pwr_max == 1)
+ led[BLINK_PATTERN_PA_PWR_MAX] = 1;
+
+ if(mgr->alarms.gps_fix_lost == 1)
+ led[BLINK_PATTERN_GPS_FIX_LOST] = 1;
+
+ return 0;
+}
+
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_bts.h b/src/osmo-bts-oc2g/misc/oc2gbts_bts.h
new file mode 100644
index 00000000..b003bdcd
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_bts.h
@@ -0,0 +1,21 @@
+#ifndef _OC2GBTS_BTS_H_
+#define _OC2GBTS_BTS_H_
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <string.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <osmo-bts/logging.h>
+
+/* public function prototypes */
+void check_bts_led_pattern(uint8_t *led);
+int check_sensor_led_pattern( struct oc2gbts_mgr_instance *mgr, uint8_t *led);
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_clock.c b/src/osmo-bts-oc2g/misc/oc2gbts_clock.c
new file mode 100644
index 00000000..acbbbc5c
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_clock.c
@@ -0,0 +1,263 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include "oc2gbts_clock.h"
+
+#define CLKERR_ERR_SYSFS "/var/oc2g/clkerr/clkerr1_average"
+#define CLKERR_ACC_SYSFS "/var/oc2g/clkerr/clkerr1_average_accuracy"
+#define CLKERR_INT_SYSFS "/var/oc2g/clkerr/clkerr1_average_interval"
+#define CLKERR_FLT_SYSFS "/var/oc2g/clkerr/clkerr1_fault"
+#define CLKERR_RFS_SYSFS "/var/oc2g/clkerr/refresh"
+#define CLKERR_RST_SYSFS "/var/oc2g/clkerr/reset"
+
+#define OCXODAC_VAL_SYSFS "/var/oc2g/ocxo/voltage"
+#define OCXODAC_ROM_SYSFS "/var/oc2g/ocxo/eeprom"
+
+/* clock error */
+static int clkerr_fd_err = -1;
+static int clkerr_fd_accuracy = -1;
+static int clkerr_fd_interval = -1;
+static int clkerr_fd_fault = -1;
+static int clkerr_fd_refresh = -1;
+static int clkerr_fd_reset = -1;
+
+/* ocxo dac */
+static int ocxodac_fd_value = -1;
+static int ocxodac_fd_save = -1;
+
+
+static int sysfs_read_val(int fd, int *val)
+{
+ int rc;
+ char szVal[32] = {0};
+
+ lseek( fd, 0, SEEK_SET );
+
+ rc = read(fd, szVal, sizeof(szVal) - 1);
+ if (rc < 0) {
+ return -errno;
+ }
+
+ rc = sscanf(szVal, "%d", val);
+ if (rc != 1) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int sysfs_write_val(int fd, int val)
+{
+ int n, rc;
+ char szVal[32] = {0};
+
+ n = sprintf(szVal, "%d", val);
+
+ lseek(fd, 0, SEEK_SET);
+ rc = write(fd, szVal, n+1);
+ if (rc < 0) {
+ return -errno;
+ }
+ return 0;
+}
+
+static int sysfs_write_str(int fd, const char *str)
+{
+ int rc;
+
+ lseek( fd, 0, SEEK_SET );
+ rc = write(fd, str, strlen(str)+1);
+ if (rc < 0) {
+ return -errno;
+ }
+ return 0;
+}
+
+
+int oc2gbts_clock_err_open(void)
+{
+ int rc;
+ int fault;
+
+ if (clkerr_fd_err < 0) {
+ clkerr_fd_err = open(CLKERR_ERR_SYSFS, O_RDONLY);
+ if (clkerr_fd_err < 0) {
+ oc2gbts_clock_err_close();
+ return clkerr_fd_err;
+ }
+ }
+
+ if (clkerr_fd_accuracy < 0) {
+ clkerr_fd_accuracy = open(CLKERR_ACC_SYSFS, O_RDONLY);
+ if (clkerr_fd_accuracy < 0) {
+ oc2gbts_clock_err_close();
+ return clkerr_fd_accuracy;
+ }
+ }
+
+ if (clkerr_fd_interval < 0) {
+ clkerr_fd_interval = open(CLKERR_INT_SYSFS, O_RDONLY);
+ if (clkerr_fd_interval < 0) {
+ oc2gbts_clock_err_close();
+ return clkerr_fd_interval;
+ }
+ }
+
+ if (clkerr_fd_fault < 0) {
+ clkerr_fd_fault = open(CLKERR_FLT_SYSFS, O_RDONLY);
+ if (clkerr_fd_fault < 0) {
+ oc2gbts_clock_err_close();
+ return clkerr_fd_fault;
+ }
+ }
+
+ if (clkerr_fd_refresh < 0) {
+ clkerr_fd_refresh = open(CLKERR_RFS_SYSFS, O_WRONLY);
+ if (clkerr_fd_refresh < 0) {
+ oc2gbts_clock_err_close();
+ return clkerr_fd_refresh;
+ }
+ }
+
+ if (clkerr_fd_reset < 0) {
+ clkerr_fd_reset = open(CLKERR_RST_SYSFS, O_WRONLY);
+ if (clkerr_fd_reset < 0) {
+ oc2gbts_clock_err_close();
+ return clkerr_fd_reset;
+ }
+ }
+ return 0;
+}
+
+void oc2gbts_clock_err_close(void)
+{
+ if (clkerr_fd_err >= 0) {
+ close(clkerr_fd_err);
+ clkerr_fd_err = -1;
+ }
+
+ if (clkerr_fd_accuracy >= 0) {
+ close(clkerr_fd_accuracy);
+ clkerr_fd_accuracy = -1;
+ }
+
+ if (clkerr_fd_interval >= 0) {
+ close(clkerr_fd_interval);
+ clkerr_fd_interval = -1;
+ }
+
+ if (clkerr_fd_fault >= 0) {
+ close(clkerr_fd_fault);
+ clkerr_fd_fault = -1;
+ }
+
+ if (clkerr_fd_refresh >= 0) {
+ close(clkerr_fd_refresh);
+ clkerr_fd_refresh = -1;
+ }
+
+ if (clkerr_fd_reset >= 0) {
+ close(clkerr_fd_reset);
+ clkerr_fd_reset = -1;
+ }
+}
+
+int oc2gbts_clock_err_reset(void)
+{
+ return sysfs_write_val(clkerr_fd_reset, 1);
+}
+
+int oc2gbts_clock_err_get(int *fault, int *error_ppt,
+ int *accuracy_ppq, int *interval_sec)
+{
+ int rc;
+
+ rc = sysfs_write_str(clkerr_fd_refresh, "once");
+ if (rc < 0) {
+ return -1;
+ }
+
+ rc = sysfs_read_val(clkerr_fd_fault, fault);
+ rc |= sysfs_read_val(clkerr_fd_err, error_ppt);
+ rc |= sysfs_read_val(clkerr_fd_accuracy, accuracy_ppq);
+ rc |= sysfs_read_val(clkerr_fd_interval, interval_sec);
+ if (rc) {
+ return -1;
+ }
+ return 0;
+}
+
+
+int oc2gbts_clock_dac_open(void)
+{
+ if (ocxodac_fd_value < 0) {
+ ocxodac_fd_value = open(OCXODAC_VAL_SYSFS, O_RDWR);
+ if (ocxodac_fd_value < 0) {
+ oc2gbts_clock_dac_close();
+ return ocxodac_fd_value;
+ }
+ }
+
+ if (ocxodac_fd_save < 0) {
+ ocxodac_fd_save = open(OCXODAC_ROM_SYSFS, O_WRONLY);
+ if (ocxodac_fd_save < 0) {
+ oc2gbts_clock_dac_close();
+ return ocxodac_fd_save;
+ }
+ }
+ return 0;
+}
+
+void oc2gbts_clock_dac_close(void)
+{
+ if (ocxodac_fd_value >= 0) {
+ close(ocxodac_fd_value);
+ ocxodac_fd_value = -1;
+ }
+
+ if (ocxodac_fd_save >= 0) {
+ close(ocxodac_fd_save);
+ ocxodac_fd_save = -1;
+ }
+}
+
+int oc2gbts_clock_dac_get(int *dac_value)
+{
+ return sysfs_read_val(ocxodac_fd_value, dac_value);
+}
+
+int oc2gbts_clock_dac_set(int dac_value)
+{
+ return sysfs_write_val(ocxodac_fd_value, dac_value);
+}
+
+int oc2gbts_clock_dac_save(void)
+{
+ return sysfs_write_val(ocxodac_fd_save, 1);
+}
+
+
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_clock.h b/src/osmo-bts-oc2g/misc/oc2gbts_clock.h
new file mode 100644
index 00000000..1444b5cf
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_clock.h
@@ -0,0 +1,16 @@
+#ifndef _OC2GBTS_CLOCK_H
+#define _OC2GBTS_CLOCK_H
+
+int oc2gbts_clock_err_open(void);
+void oc2gbts_clock_err_close(void);
+int oc2gbts_clock_err_reset(void);
+int oc2gbts_clock_err_get(int *fault, int *error_ppt,
+ int *accuracy_ppq, int *interval_sec);
+
+int oc2gbts_clock_dac_open(void);
+void oc2gbts_clock_dac_close(void);
+int oc2gbts_clock_dac_get(int *dac_value);
+int oc2gbts_clock_dac_set(int dac_value);
+int oc2gbts_clock_dac_save(void);
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_led.c b/src/osmo-bts-oc2g/misc/oc2gbts_led.c
new file mode 100644
index 00000000..b8758b8e
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_led.c
@@ -0,0 +1,332 @@
+/* Copyright (C) 2016 by NuRAN Wireless <support@nuranwireless.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include "oc2gbts_led.h"
+#include "oc2gbts_bts.h"
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/linuxlist.h>
+
+static struct oc2gbts_led led_entries[] = {
+ {
+ .name = "led0",
+ .fullname = "led red",
+ .path = "/var/oc2g/leds/led0/brightness"
+ },
+ {
+ .name = "led1",
+ .fullname = "led green",
+ .path = "/var/oc2g/leds/led1/brightness"
+ }
+};
+
+static const struct value_string oc2gbts_led_strs[] = {
+ { OC2GBTS_LED_RED, "LED red" },
+ { OC2GBTS_LED_GREEN, "LED green" },
+ { OC2GBTS_LED_ORANGE, "LED orange" },
+ { OC2GBTS_LED_OFF, "LED off" },
+ { 0, NULL }
+};
+
+static uint8_t led_priority[] = {
+ BLINK_PATTERN_INIT,
+ BLINK_PATTERN_INT_PROC_MALFUNC,
+ BLINK_PATTERN_SUPPLY_PWR_MAX,
+ BLINK_PATTERN_PA_PWR_MAX,
+ BLINK_PATTERN_VSWR_MAX,
+ BLINK_PATTERN_SUPPLY_VOLT_MIN,
+ BLINK_PATTERN_TEMP_MAX,
+ BLINK_PATTERN_EXT_LINK_MALFUNC,
+ BLINK_PATTERN_SUPPLY_VOLT_LOW,
+ BLINK_PATTERN_TEMP_HIGH,
+ BLINK_PATTERN_VSWR_HIGH,
+ BLINK_PATTERN_SUPPLY_PWR_HIGH,
+ BLINK_PATTERN_PA_PWR_HIGH,
+ BLINK_PATTERN_GPS_FIX_LOST,
+ BLINK_PATTERN_NORMAL
+};
+
+
+char *blink_pattern_command[] = BLINK_PATTERN_COMMAND;
+
+static int oc2gbts_led_write(char *path, char *str)
+{
+ int fd;
+
+ if ((fd = open(path, O_WRONLY)) == -1)
+ {
+ return 0;
+ }
+
+ write(fd, str, strlen(str)+1);
+ close(fd);
+ return 1;
+}
+
+static void led_set_red()
+{
+ oc2gbts_led_write(led_entries[0].path, "1");
+ oc2gbts_led_write(led_entries[1].path, "0");
+}
+
+static void led_set_green()
+{
+ oc2gbts_led_write(led_entries[0].path, "0");
+ oc2gbts_led_write(led_entries[1].path, "1");
+}
+
+static void led_set_orange()
+{
+ oc2gbts_led_write(led_entries[0].path, "1");
+ oc2gbts_led_write(led_entries[1].path, "1");
+}
+
+static void led_set_off()
+{
+ oc2gbts_led_write(led_entries[0].path, "0");
+ oc2gbts_led_write(led_entries[1].path, "0");
+}
+
+static void led_sleep( struct oc2gbts_mgr_instance *mgr, struct oc2gbts_led_timer *led_timer, void (*led_timer_cb)(void *_data)) {
+ /* Cancel any pending timer */
+ osmo_timer_del(&led_timer->timer);
+ /* Start LED timer */
+ led_timer->timer.cb = led_timer_cb;
+ led_timer->timer.data = mgr;
+ mgr->oc2gbts_leds.active_timer = led_timer->idx;
+ osmo_timer_schedule(&led_timer->timer, led_timer->param.sleep_sec, led_timer->param.sleep_usec);
+ LOGP(DTEMP, LOGL_DEBUG,"%s timer scheduled for %d sec + %d usec\n",
+ get_value_string(oc2gbts_led_strs, led_timer->idx),
+ led_timer->param.sleep_sec,
+ led_timer->param.sleep_usec);
+
+ switch (led_timer->idx) {
+ case OC2GBTS_LED_RED:
+ led_set_red();
+ break;
+ case OC2GBTS_LED_GREEN:
+ led_set_green();
+ break;
+ case OC2GBTS_LED_ORANGE:
+ led_set_orange();
+ break;
+ case OC2GBTS_LED_OFF:
+ led_set_off();
+ break;
+ default:
+ led_set_off();
+ }
+}
+
+static void led_sleep_cb(void *_data) {
+ struct oc2gbts_mgr_instance *mgr = _data;
+ struct oc2gbts_led_timer_list *led_list;
+
+ /* make sure the timer list is not empty */
+ if (llist_empty(&mgr->oc2gbts_leds.list))
+ return;
+
+ llist_for_each_entry(led_list, &mgr->oc2gbts_leds.list, list) {
+ if (led_list->led_timer.idx == mgr->oc2gbts_leds.active_timer) {
+ LOGP(DTEMP, LOGL_DEBUG,"Delete expired %s timer %d sec + %d usec\n",
+ get_value_string(oc2gbts_led_strs, led_list->led_timer.idx),
+ led_list->led_timer.param.sleep_sec,
+ led_list->led_timer.param.sleep_usec);
+
+ /* Delete current timer */
+ osmo_timer_del(&led_list->led_timer.timer);
+ /* Rotate the timer list */
+ llist_move_tail(led_list, &mgr->oc2gbts_leds.list);
+ break;
+ }
+ }
+
+ /* Execute next timer */
+ led_list = llist_first_entry(&mgr->oc2gbts_leds.list, struct oc2gbts_led_timer_list, list);
+ if (led_list) {
+ LOGP(DTEMP, LOGL_DEBUG,"Execute %s timer %d sec + %d usec, total entries=%d\n",
+ get_value_string(oc2gbts_led_strs, led_list->led_timer.idx),
+ led_list->led_timer.param.sleep_sec,
+ led_list->led_timer.param.sleep_usec,
+ llist_count(&mgr->oc2gbts_leds.list));
+
+ led_sleep(mgr, &led_list->led_timer, led_sleep_cb);
+ }
+
+}
+
+static void delete_led_timer_entries(struct oc2gbts_mgr_instance *mgr)
+{
+ struct oc2gbts_led_timer_list *led_list, *led_list2;
+
+ if (llist_empty(&mgr->oc2gbts_leds.list))
+ return;
+
+ llist_for_each_entry_safe(led_list, led_list2, &mgr->oc2gbts_leds.list, list) {
+ /* Delete the timer in list */
+ if (led_list) {
+ LOGP(DTEMP, LOGL_DEBUG,"Delete %s timer entry from list, %d sec + %d usec\n",
+ get_value_string(oc2gbts_led_strs, led_list->led_timer.idx),
+ led_list->led_timer.param.sleep_sec,
+ led_list->led_timer.param.sleep_usec);
+
+ /* Delete current timer */
+ osmo_timer_del(&led_list->led_timer.timer);
+ llist_del(&led_list->list);
+ talloc_free(led_list);
+ }
+ }
+ return;
+}
+
+static int add_led_timer_entry(struct oc2gbts_mgr_instance *mgr, char *cmdstr)
+{
+ double sec, int_sec, frac_sec;
+ struct oc2gbts_sleep_time led_param;
+
+ led_param.sleep_sec = 0;
+ led_param.sleep_usec = 0;
+
+ if (strstr(cmdstr, "set red") != NULL)
+ mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_RED;
+ else if (strstr(cmdstr, "set green") != NULL)
+ mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_GREEN;
+ else if (strstr(cmdstr, "set orange") != NULL)
+ mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_ORANGE;
+ else if (strstr(cmdstr, "set off") != NULL)
+ mgr->oc2gbts_leds.led_idx = OC2GBTS_LED_OFF;
+ else if (strstr(cmdstr, "sleep") != NULL) {
+ sec = atof(cmdstr + 6);
+ /* split time into integer and fractional of seconds */
+ frac_sec = modf(sec, &int_sec) * 1000000.0;
+ led_param.sleep_sec = (int)int_sec;
+ led_param.sleep_usec = (int)frac_sec;
+
+ if ((mgr->oc2gbts_leds.led_idx >= OC2GBTS_LED_RED) && (mgr->oc2gbts_leds.led_idx < _OC2GBTS_LED_MAX)) {
+ struct oc2gbts_led_timer_list *led_list;
+
+ /* allocate timer entry */
+ led_list = talloc_zero(tall_mgr_ctx, struct oc2gbts_led_timer_list);
+ if (led_list) {
+ led_list->led_timer.idx = mgr->oc2gbts_leds.led_idx;
+ led_list->led_timer.param.sleep_sec = led_param.sleep_sec;
+ led_list->led_timer.param.sleep_usec = led_param.sleep_usec;
+ llist_add_tail(&led_list->list, &mgr->oc2gbts_leds.list);
+
+ LOGP(DTEMP, LOGL_DEBUG,"Add %s timer to list, %d sec + %d usec, total entries=%d\n",
+ get_value_string(oc2gbts_led_strs, mgr->oc2gbts_leds.led_idx),
+ led_list->led_timer.param.sleep_sec,
+ led_list->led_timer.param.sleep_usec,
+ llist_count(&mgr->oc2gbts_leds.list));
+ }
+ }
+ } else
+ return -1;
+
+ return 0;
+}
+
+static int parse_led_pattern(char *pattern, struct oc2gbts_mgr_instance *mgr)
+{
+ char str[1024];
+ char *pstr;
+ char *sep;
+ int rc = 0;
+
+ strcpy(str, pattern);
+ pstr = str;
+ while ((sep = strsep(&pstr, ";")) != NULL) {
+ rc = add_led_timer_entry(mgr, sep);
+ if (rc < 0) {
+ break;
+ }
+
+ }
+ return rc;
+}
+
+/*** led interface ***/
+
+void led_set(struct oc2gbts_mgr_instance *mgr, int pattern_id)
+{
+ int rc;
+ struct oc2gbts_led_timer_list *led_list;
+
+ if (pattern_id > BLINK_PATTERN_MAX_ITEM - 1) {
+ LOGP(DTEMP, LOGL_ERROR, "Invalid LED pattern : %d. LED pattern must be between %d..%d\n",
+ pattern_id,
+ BLINK_PATTERN_POWER_ON,
+ BLINK_PATTERN_MAX_ITEM - 1);
+ return;
+ }
+ if (pattern_id == mgr->oc2gbts_leds.last_pattern_id)
+ return;
+
+ mgr->oc2gbts_leds.last_pattern_id = pattern_id;
+
+ LOGP(DTEMP, LOGL_INFO, "blink pattern command : %d\n", pattern_id);
+ LOGP(DTEMP, LOGL_INFO, "%s\n", blink_pattern_command[pattern_id]);
+
+ /* Empty existing LED timer in the list */
+ delete_led_timer_entries(mgr);
+
+ /* parse LED pattern */
+ rc = parse_led_pattern(blink_pattern_command[pattern_id], mgr);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR,"LED pattern not found or invalid LED pattern\n");
+ return;
+ }
+
+ /* make sure the timer list is not empty */
+ if (llist_empty(&mgr->oc2gbts_leds.list))
+ return;
+
+ /* Start the first LED timer in the list */
+ led_list = llist_first_entry(&mgr->oc2gbts_leds.list, struct oc2gbts_led_timer_list, list);
+ if (led_list) {
+ LOGP(DTEMP, LOGL_DEBUG,"Execute timer %s for %d sec + %d usec\n",
+ get_value_string(oc2gbts_led_strs, led_list->led_timer.idx),
+ led_list->led_timer.param.sleep_sec,
+ led_list->led_timer.param.sleep_usec);
+
+ led_sleep(mgr, &led_list->led_timer, led_sleep_cb);
+ }
+
+}
+
+void select_led_pattern(struct oc2gbts_mgr_instance *mgr)
+{
+ int i;
+ uint8_t led[BLINK_PATTERN_MAX_ITEM] = {0};
+
+ /* set normal LED pattern at first */
+ led[BLINK_PATTERN_NORMAL] = 1;
+
+ /* check on-board sensors for new LED pattern */
+ check_sensor_led_pattern(mgr, led);
+
+ /* check BTS status for new LED pattern */
+ check_bts_led_pattern(led);
+
+ /* check by priority */
+ for (i = 0; i < sizeof(led_priority)/sizeof(uint8_t); i++) {
+ if(led[led_priority[i]] == 1) {
+ led_set(mgr, led_priority[i]);
+ break;
+ }
+ }
+}
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_led.h b/src/osmo-bts-oc2g/misc/oc2gbts_led.h
new file mode 100644
index 00000000..cb627e3c
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_led.h
@@ -0,0 +1,22 @@
+#ifndef _OC2GBTS_LED_H
+#define _OC2GBTS_LED_H
+
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <math.h>
+#include <sys/stat.h>
+
+#include <osmo-bts/logging.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/utils.h>
+
+#include "oc2gbts_mgr.h"
+
+/* public function prototypes */
+void led_set(struct oc2gbts_mgr_instance *mgr, int pattern_id);
+
+void select_led_pattern(struct oc2gbts_mgr_instance *mgr);
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr.c
new file mode 100644
index 00000000..45ecc65d
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr.c
@@ -0,0 +1,353 @@
+/* Main program for NuRAN Wireless OC-2G BTS management daemon */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_mgr.c
+ * (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <sys/signal.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/ports.h>
+
+#include "misc/oc2gbts_misc.h"
+#include "misc/oc2gbts_mgr.h"
+#include "misc/oc2gbts_par.h"
+#include "misc/oc2gbts_bid.h"
+#include "misc/oc2gbts_power.h"
+#include "misc/oc2gbts_swd.h"
+
+#include "oc2gbts_led.h"
+
+static int no_rom_write = 0;
+static int daemonize = 0;
+void *tall_mgr_ctx;
+
+/* every 6 hours means 365*4 = 1460 rom writes per year (max) */
+#define SENSOR_TIMER_SECS (6 * 3600)
+
+/* every 1 hours means 365*24 = 8760 rom writes per year (max) */
+#define HOURS_TIMER_SECS (1 * 3600)
+
+/* the initial state */
+static struct oc2gbts_mgr_instance manager = {
+ .config_file = "oc2gbts-mgr.cfg",
+ .temp = {
+ .supply_temp_limit = {
+ .thresh_warn_max = 80,
+ .thresh_crit_max = 85,
+ .thresh_warn_min = -40,
+ },
+ .soc_temp_limit = {
+ .thresh_warn_max = 95,
+ .thresh_crit_max = 100,
+ .thresh_warn_min = -40,
+ },
+ .fpga_temp_limit = {
+ .thresh_warn_max = 95,
+ .thresh_crit_max = 100,
+ .thresh_warn_min = -40,
+ },
+ .rmsdet_temp_limit = {
+ .thresh_warn_max = 80,
+ .thresh_crit_max = 85,
+ .thresh_warn_min = -40,
+ },
+ .ocxo_temp_limit = {
+ .thresh_warn_max = 80,
+ .thresh_crit_max = 85,
+ .thresh_warn_min = -40,
+ },
+ .tx_temp_limit = {
+ .thresh_warn_max = 80,
+ .thresh_crit_max = 85,
+ .thresh_warn_min = -20,
+ },
+ .pa_temp_limit = {
+ .thresh_warn_max = 80,
+ .thresh_crit_max = 85,
+ .thresh_warn_min = -40,
+ }
+ },
+ .volt = {
+ .supply_volt_limit = {
+ .thresh_warn_max = 30000,
+ .thresh_crit_max = 30500,
+ .thresh_warn_min = 19000,
+ .thresh_crit_min = 17500,
+ }
+ },
+ .pwr = {
+ .supply_pwr_limit = {
+ .thresh_warn_max = 30,
+ .thresh_crit_max = 40,
+ },
+ .pa_pwr_limit = {
+ .thresh_warn_max = 20,
+ .thresh_crit_max = 30,
+ }
+ },
+ .vswr = {
+ .vswr_limit = {
+ .thresh_warn_max = 3000,
+ .thresh_crit_max = 5000,
+ }
+ },
+ .gps = {
+ .gps_fix_limit = {
+ .thresh_warn_max = 7,
+ }
+ },
+ .state = {
+ .action_norm = SENSOR_ACT_NORM_PA_ON,
+ .action_warn = 0,
+ .action_crit = 0,
+ .action_comb = 0,
+ .state = STATE_NORMAL,
+ }
+};
+
+static struct osmo_timer_list sensor_timer;
+static void check_sensor_timer_cb(void *unused)
+{
+ oc2gbts_check_temp(no_rom_write);
+ oc2gbts_check_power(no_rom_write);
+ oc2gbts_check_vswr(no_rom_write);
+ osmo_timer_schedule(&sensor_timer, SENSOR_TIMER_SECS, 0);
+ /* TODO checks if oc2gbts_check_temp/oc2gbts_check_power/oc2gbts_check_vswr went ok */
+ oc2gbts_swd_event(&manager, SWD_CHECK_SENSOR);
+}
+
+static struct osmo_timer_list hours_timer;
+static void hours_timer_cb(void *unused)
+{
+ oc2gbts_update_hours(no_rom_write);
+
+ osmo_timer_schedule(&hours_timer, HOURS_TIMER_SECS, 0);
+ /* TODO: validates if oc2gbts_update_hours went correctly */
+ oc2gbts_swd_event(&manager, SWD_UPDATE_HOURS);
+}
+
+static void print_help(void)
+{
+ printf("oc2gbts-mgr [-nsD] [-d cat]\n");
+ printf(" -n Do not write to ROM\n");
+ printf(" -s Disable color\n");
+ printf(" -d CAT enable debugging\n");
+ printf(" -D daemonize\n");
+ printf(" -c Specify the filename of the config file\n");
+}
+
+static int parse_options(int argc, char **argv)
+{
+ int opt;
+
+ while ((opt = getopt(argc, argv, "nhsd:c:")) != -1) {
+ switch (opt) {
+ case 'n':
+ no_rom_write = 1;
+ break;
+ case 'h':
+ print_help();
+ return -1;
+ case 's':
+ log_set_use_color(osmo_stderr_target, 0);
+ break;
+ case 'd':
+ log_parse_category_mask(osmo_stderr_target, optarg);
+ break;
+ case 'D':
+ daemonize = 1;
+ break;
+ case 'c':
+ manager.config_file = optarg;
+ break;
+ default:
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void signal_handler(int signal)
+{
+ fprintf(stderr, "signal %u received\n", signal);
+
+ switch (signal) {
+ case SIGINT:
+ oc2gbts_check_temp(no_rom_write);
+ oc2gbts_check_power(no_rom_write);
+ oc2gbts_check_vswr(no_rom_write);
+ oc2gbts_update_hours(no_rom_write);
+ exit(0);
+ break;
+ case SIGABRT:
+ case SIGUSR1:
+ case SIGUSR2:
+ talloc_report_full(tall_mgr_ctx, stderr);
+ break;
+ default:
+ break;
+ }
+}
+
+static struct log_info_cat mgr_log_info_cat[] = {
+ [DTEMP] = {
+ .name = "DTEMP",
+ .description = "Temperature monitoring",
+ .color = "\033[1;35m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DFW] = {
+ .name = "DFW",
+ .description = "Firmware management",
+ .color = "\033[1;36m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DFIND] = {
+ .name = "DFIND",
+ .description = "ipaccess-find handling",
+ .color = "\033[1;37m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DCALIB] = {
+ .name = "DCALIB",
+ .description = "Calibration handling",
+ .color = "\033[1;37m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DSWD] = {
+ .name = "DSWD",
+ .description = "Software Watchdog",
+ .color = "\033[1;37m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+};
+
+static const struct log_info mgr_log_info = {
+ .cat = mgr_log_info_cat,
+ .num_cat = ARRAY_SIZE(mgr_log_info_cat),
+};
+
+static int mgr_log_init(void)
+{
+ osmo_init_logging(&mgr_log_info);
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ void *tall_msgb_ctx;
+ int rc;
+ pthread_t tid;
+
+ tall_mgr_ctx = talloc_named_const(NULL, 1, "bts manager");
+ tall_msgb_ctx = talloc_named_const(tall_mgr_ctx, 1, "msgb");
+ msgb_set_talloc_ctx(tall_msgb_ctx);
+
+ mgr_log_init();
+
+ osmo_init_ignore_signals();
+ signal(SIGINT, &signal_handler);
+ signal(SIGUSR1, &signal_handler);
+ signal(SIGUSR2, &signal_handler);
+
+ rc = parse_options(argc, argv);
+ if (rc < 0)
+ exit(2);
+
+ oc2gbts_mgr_vty_init();
+ logging_vty_add_cmds(&mgr_log_info);
+ rc = oc2gbts_mgr_parse_config(&manager);
+ if (rc < 0) {
+ LOGP(DFIND, LOGL_FATAL, "Cannot parse config file\n");
+ exit(1);
+ }
+
+ rc = telnet_init(tall_mgr_ctx, NULL, OSMO_VTY_PORT_BTSMGR);
+ if (rc < 0) {
+ fprintf(stderr, "Error initializing telnet\n");
+ exit(1);
+ }
+
+ INIT_LLIST_HEAD(&manager.oc2gbts_leds.list);
+ INIT_LLIST_HEAD(&manager.alarms.list);
+
+ /* Initialize the service watchdog notification for SWD_LAST event(s) */
+ if (oc2gbts_swd_init(&manager, (int)(SWD_LAST)) != 0)
+ exit(3);
+
+ /* start temperature check timer */
+ sensor_timer.cb = check_sensor_timer_cb;
+ check_sensor_timer_cb(NULL);
+
+ /* start operational hours timer */
+ hours_timer.cb = hours_timer_cb;
+ hours_timer_cb(NULL);
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ /* Enable the PAs */
+ rc = oc2gbts_power_set(OC2GBTS_POWER_PA, 1);
+ if (rc < 0) {
+ exit(3);
+ }
+ }
+ /* handle broadcast messages for ipaccess-find */
+ if (oc2gbts_mgr_nl_init() != 0)
+ exit(3);
+
+ /* Initialize the sensor control */
+ oc2gbts_mgr_sensor_init(&manager);
+
+ if (oc2gbts_mgr_calib_init(&manager) != 0)
+ exit(3);
+
+ if (oc2gbts_mgr_control_init(&manager))
+ exit(3);
+
+ if (daemonize) {
+ rc = osmo_daemonize();
+ if (rc < 0) {
+ perror("Error during daemonize");
+ exit(1);
+ }
+ }
+
+ while (1) {
+ log_reset_context();
+ osmo_select_main(0);
+ oc2gbts_swd_event(&manager, SWD_MAINLOOP);
+ }
+}
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr.h b/src/osmo-bts-oc2g/misc/oc2gbts_mgr.h
new file mode 100644
index 00000000..1f50fb6b
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr.h
@@ -0,0 +1,398 @@
+#ifndef _OC2GBTS_MGR_H
+#define _OC2GBTS_MGR_H
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/timer.h>
+
+#include <stdint.h>
+#include <gps.h>
+
+#define OC2GBTS_SENSOR_TIMER_DURATION 60
+#define OC2GBTS_PREVENT_TIMER_DURATION 15 * 60
+#define OC2GBTS_PREVENT_TIMER_SHORT_DURATION 5 * 60
+#define OC2GBTS_PREVENT_TIMER_NONE 0
+#define OC2GBTS_PREVENT_RETRY INT_MAX - 1
+
+enum BLINK_PATTERN {
+ BLINK_PATTERN_POWER_ON = 0, //hardware set
+ BLINK_PATTERN_INIT,
+ BLINK_PATTERN_NORMAL,
+ BLINK_PATTERN_EXT_LINK_MALFUNC,
+ BLINK_PATTERN_INT_PROC_MALFUNC,
+ BLINK_PATTERN_SUPPLY_VOLT_LOW,
+ BLINK_PATTERN_SUPPLY_VOLT_MIN,
+ BLINK_PATTERN_VSWR_HIGH,
+ BLINK_PATTERN_VSWR_MAX,
+ BLINK_PATTERN_TEMP_HIGH,
+ BLINK_PATTERN_TEMP_MAX,
+ BLINK_PATTERN_SUPPLY_PWR_HIGH,
+ BLINK_PATTERN_SUPPLY_PWR_MAX,
+ BLINK_PATTERN_PA_PWR_HIGH,
+ BLINK_PATTERN_PA_PWR_MAX,
+ BLINK_PATTERN_GPS_FIX_LOST,
+ BLINK_PATTERN_MAX_ITEM
+};
+
+#define BLINK_PATTERN_COMMAND {\
+ "set red; sleep 5.0",\
+ "set orange; sleep 5.0",\
+ "set green; sleep 2.5; set off; sleep 2.5",\
+ "set red; sleep 0.5; set off; sleep 0.5",\
+ "set red; sleep 2.5; set off; sleep 2.5",\
+ "set green; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5",\
+ "set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5 ",\
+ "set green; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\
+ "set red; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\
+ "set orange; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5 ",\
+ "set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\
+ "set green; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\
+ "set red; sleep 2.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5",\
+ "set green; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\
+ "set red; sleep 2.5; set off; sleep 0.5; set red; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\
+ "set green; sleep 2.5; set off; sleep 0.5; set green; sleep 0.5; set off; sleep 0.5; set orange; sleep 0.5; set off; sleep 0.5",\
+}
+
+enum {
+ DTEMP,
+ DFW,
+ DFIND,
+ DCALIB,
+ DSWD,
+};
+
+// TODO NTQD: Define new actions like reducing output power, limit ARM core speed, shutdown second TRX/PA, ...
+enum {
+#if 0
+ SENSOR_ACT_PWR_CONTRL = 0x1,
+#endif
+ SENSOR_ACT_PA_OFF = 0x2,
+ SENSOR_ACT_BTS_SRV_OFF = 0x10,
+};
+
+/* actions only for normal state */
+enum {
+#if 0
+ SENSOR_ACT_NORM_PW_CONTRL = 0x1,
+#endif
+ SENSOR_ACT_NORM_PA_ON = 0x2,
+ SENSOR_ACT_NORM_BTS_SRV_ON= 0x10,
+};
+
+enum oc2gbts_sensor_state {
+ STATE_NORMAL, /* Everything is fine */
+ STATE_WARNING_HYST, /* Go back to normal next? */
+ STATE_WARNING, /* We are above the warning threshold */
+ STATE_CRITICAL, /* We have an issue. Wait for below warning */
+};
+
+enum oc2gbts_leds_name {
+ OC2GBTS_LED_RED = 0,
+ OC2GBTS_LED_GREEN,
+ OC2GBTS_LED_ORANGE,
+ OC2GBTS_LED_OFF,
+ _OC2GBTS_LED_MAX
+};
+
+struct oc2gbts_led{
+ char *name;
+ char *fullname;
+ char *path;
+};
+
+/**
+ * Temperature Limits. We separate from a threshold
+ * that will generate a warning and one that is so
+ * severe that an action will be taken.
+ */
+struct oc2gbts_temp_limit {
+ int thresh_warn_max;
+ int thresh_crit_max;
+ int thresh_warn_min;
+};
+
+struct oc2gbts_volt_limit {
+ int thresh_warn_max;
+ int thresh_crit_max;
+ int thresh_warn_min;
+ int thresh_crit_min;
+};
+
+struct oc2gbts_pwr_limit {
+ int thresh_warn_max;
+ int thresh_crit_max;
+};
+
+struct oc2gbts_vswr_limit {
+ int thresh_warn_max;
+ int thresh_crit_max;
+};
+
+struct oc2gbts_gps_fix_limit {
+ int thresh_warn_max;
+};
+
+struct oc2gbts_sleep_time {
+ int sleep_sec;
+ int sleep_usec;
+};
+
+struct oc2gbts_led_timer {
+ uint8_t idx;
+ struct osmo_timer_list timer;
+ struct oc2gbts_sleep_time param;
+};
+
+struct oc2gbts_led_timer_list {
+ struct llist_head list;
+ struct oc2gbts_led_timer led_timer;
+};
+
+struct oc2gbts_preventive_list {
+ struct llist_head list;
+ struct oc2gbts_sleep_time param;
+ int action_flag;
+};
+
+enum mgr_vty_node {
+ MGR_NODE = _LAST_OSMOVTY_NODE + 1,
+
+ ACT_NORM_NODE,
+ ACT_WARN_NODE,
+ ACT_CRIT_NODE,
+ LIMIT_SUPPLY_TEMP_NODE,
+ LIMIT_SOC_NODE,
+ LIMIT_FPGA_NODE,
+ LIMIT_RMSDET_NODE,
+ LIMIT_OCXO_NODE,
+ LIMIT_TX_TEMP_NODE,
+ LIMIT_PA_TEMP_NODE,
+ LIMIT_SUPPLY_VOLT_NODE,
+ LIMIT_VSWR_NODE,
+ LIMIT_SUPPLY_PWR_NODE,
+ LIMIT_PA_PWR_NODE,
+ LIMIT_GPS_FIX_NODE,
+};
+
+enum mgr_vty_limit_type {
+ MGR_LIMIT_TYPE_TEMP = 0,
+ MGR_LIMIT_TYPE_VOLT,
+ MGR_LIMIT_TYPE_VSWR,
+ MGR_LIMIT_TYPE_PWR,
+ _MGR_LIMIT_TYPE_MAX,
+};
+
+struct oc2gbts_mgr_instance {
+ const char *config_file;
+
+ struct {
+ struct oc2gbts_temp_limit supply_temp_limit;
+ struct oc2gbts_temp_limit soc_temp_limit;
+ struct oc2gbts_temp_limit fpga_temp_limit;
+ struct oc2gbts_temp_limit rmsdet_temp_limit;
+ struct oc2gbts_temp_limit ocxo_temp_limit;
+ struct oc2gbts_temp_limit tx_temp_limit;
+ struct oc2gbts_temp_limit pa_temp_limit;
+ } temp;
+
+ struct {
+ struct oc2gbts_volt_limit supply_volt_limit;
+ } volt;
+
+ struct {
+ struct oc2gbts_pwr_limit supply_pwr_limit;
+ struct oc2gbts_pwr_limit pa_pwr_limit;
+ } pwr;
+
+ struct {
+ struct oc2gbts_vswr_limit vswr_limit;
+ int last_vswr;
+ } vswr;
+
+ struct {
+ struct oc2gbts_gps_fix_limit gps_fix_limit;
+ int last_update;
+ time_t last_gps_fix;
+ time_t gps_fix_now;
+ int gps_open;
+ struct osmo_fd gpsfd;
+ struct gps_data_t gpsdata;
+ struct osmo_timer_list fix_timeout;
+ } gps;
+
+ struct {
+ int action_norm;
+ int action_warn;
+ int action_crit;
+ int action_comb;
+
+ enum oc2gbts_sensor_state state;
+ } state;
+
+ struct {
+ int state;
+ int calib_from_loop;
+ struct osmo_timer_list calib_timeout;
+ } calib;
+
+ struct {
+ int state;
+ int swd_from_loop;
+ unsigned long long int swd_events;
+ unsigned long long int swd_events_cache;
+ unsigned long long int swd_eventmasks;
+ int num_events;
+ struct osmo_timer_list swd_timeout;
+ } swd;
+
+ struct {
+ uint8_t led_idx;
+ uint8_t last_pattern_id;
+ uint8_t active_timer;
+ struct llist_head list;
+ } oc2gbts_leds;
+
+ struct {
+ int is_up;
+ uint32_t last_seqno;
+ struct osmo_timer_list recon_timer;
+ struct ipa_client_conn *bts_conn;
+ uint32_t crit_flags;
+ uint32_t warn_flags;
+ } oc2gbts_ctrl;
+
+ struct oc2gbts_alarms {
+ int temp_high;
+ int temp_max;
+ int supply_low;
+ int supply_min;
+ int vswr_high;
+ int vswr_max;
+ int supply_pwr_high;
+ int supply_pwr_max;
+ int pa_pwr_high;
+ int pa_pwr_max;
+ int gps_fix_lost;
+ struct llist_head list;
+ struct osmo_timer_list preventive_timer;
+ int preventive_duration;
+ int preventive_retry;
+ } alarms;
+
+};
+
+enum oc2gbts_mgr_fail_evt_rep_crit_sig {
+ /* Critical alarms */
+ S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM = (1 << 0),
+ S_MGR_TEMP_SOC_CRIT_MAX_ALARM = (1 << 1),
+ S_MGR_TEMP_FPGA_CRIT_MAX_ALARM = (1 << 2),
+ S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM = (1 << 3),
+ S_MGR_TEMP_OCXO_CRIT_MAX_ALARM = (1 << 4),
+ S_MGR_TEMP_TRX_CRIT_MAX_ALARM = (1 << 5),
+ S_MGR_TEMP_PA_CRIT_MAX_ALARM = (1 << 6),
+ S_MGR_SUPPLY_CRIT_MAX_ALARM = (1 << 7),
+ S_MGR_SUPPLY_CRIT_MIN_ALARM = (1 << 8),
+ S_MGR_VSWR_CRIT_MAX_ALARM = (1 << 9),
+ S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM = (1 << 10),
+ S_MGR_PWR_PA_CRIT_MAX_ALARM = (1 << 11),
+ _S_MGR_CRIT_ALARM_MAX,
+};
+
+enum oc2gbts_mgr_fail_evt_rep_warn_sig {
+ /* Warning alarms */
+ S_MGR_TEMP_SUPPLY_WARN_MIN_ALARM = (1 << 0),
+ S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM = (1 << 1),
+ S_MGR_TEMP_SOC_WARN_MIN_ALARM = (1 << 2),
+ S_MGR_TEMP_SOC_WARN_MAX_ALARM = (1 << 3),
+ S_MGR_TEMP_FPGA_WARN_MIN_ALARM = (1 << 4),
+ S_MGR_TEMP_FPGA_WARN_MAX_ALARM = (1 << 5),
+ S_MGR_TEMP_RMS_DET_WARN_MIN_ALARM = (1 << 6),
+ S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM = (1 << 7),
+ S_MGR_TEMP_OCXO_WARN_MIN_ALARM = (1 << 8),
+ S_MGR_TEMP_OCXO_WARN_MAX_ALARM = (1 << 9),
+ S_MGR_TEMP_TRX_WARN_MIN_ALARM = (1 << 10),
+ S_MGR_TEMP_TRX_WARN_MAX_ALARM = (1 << 11),
+ S_MGR_TEMP_PA_WARN_MIN_ALARM = (1 << 12),
+ S_MGR_TEMP_PA_WARN_MAX_ALARM = (1 << 13),
+ S_MGR_SUPPLY_WARN_MIN_ALARM = (1 << 14),
+ S_MGR_SUPPLY_WARN_MAX_ALARM = (1 << 15),
+ S_MGR_VSWR_WARN_MAX_ALARM = (1 << 16),
+ S_MGR_PWR_SUPPLY_WARN_MAX_ALARM = (1 << 17),
+ S_MGR_PWR_PA_WARN_MAX_ALARM = (1 << 18),
+ S_MGR_GPS_FIX_WARN_ALARM = (1 << 19),
+ _S_MGR_WARN_ALARM_MAX,
+};
+
+enum oc2gbts_mgr_failure_event_causes {
+ /* Critical causes */
+ NM_EVT_CAUSE_CRIT_TEMP_SUPPLY_MAX_FAIL = 0x4100,
+ NM_EVT_CAUSE_CRIT_TEMP_FPGA_MAX_FAIL = 0x4101,
+ NM_EVT_CAUSE_CRIT_TEMP_SOC_MAX_FAIL = 0x4102,
+ NM_EVT_CAUSE_CRIT_TEMP_RMS_DET_MAX_FAIL = 0x4103,
+ NM_EVT_CAUSE_CRIT_TEMP_OCXO_MAX_FAIL = 0x4104,
+ NM_EVT_CAUSE_CRIT_TEMP_TRX_MAX_FAIL = 0x4105,
+ NM_EVT_CAUSE_CRIT_TEMP_PA_MAX_FAIL = 0x4106,
+ NM_EVT_CAUSE_CRIT_SUPPLY_MAX_FAIL = 0x4107,
+ NM_EVT_CAUSE_CRIT_SUPPLY_MIN_FAIL = 0x4108,
+ NM_EVT_CAUSE_CRIT_VSWR_MAX_FAIL = 0x4109,
+ NM_EVT_CAUSE_CRIT_PWR_SUPPLY_MAX_FAIL = 0x410A,
+ NM_EVT_CAUSE_CRIT_PWR_PA_MAX_FAIL = 0x410B,
+ /* Warning causes */
+ NM_EVT_CAUSE_WARN_TEMP_SUPPLY_LOW_FAIL = 0x4400,
+ NM_EVT_CAUSE_WARN_TEMP_SUPPLY_HIGH_FAIL = 0x4401,
+ NM_EVT_CAUSE_WARN_TEMP_FPGA_LOW_FAIL = 0x4402,
+ NM_EVT_CAUSE_WARN_TEMP_FPGA_HIGH_FAIL = 0x4403,
+ NM_EVT_CAUSE_WARN_TEMP_SOC_LOW_FAIL = 0x4404,
+ NM_EVT_CAUSE_WARN_TEMP_SOC_HIGH_FAIL = 0x4405,
+ NM_EVT_CAUSE_WARN_TEMP_RMS_DET_LOW_FAIL = 0x4406,
+ NM_EVT_CAUSE_WARN_TEMP_RMS_DET_HIGH_FAIL= 0x4407,
+ NM_EVT_CAUSE_WARN_TEMP_OCXO_LOW_FAIL = 0x4408,
+ NM_EVT_CAUSE_WARN_TEMP_OCXO_HIGH_FAIL = 0x4409,
+ NM_EVT_CAUSE_WARN_TEMP_TRX_LOW_FAIL = 0x440A,
+ NM_EVT_CAUSE_WARN_TEMP_TRX_HIGH_FAIL = 0x440B,
+ NM_EVT_CAUSE_WARN_TEMP_PA_LOW_FAIL = 0x440C,
+ NM_EVT_CAUSE_WARN_TEMP_PA_HIGH_FAIL = 0x440D,
+ NM_EVT_CAUSE_WARN_SUPPLY_LOW_FAIL = 0x440E,
+ NM_EVT_CAUSE_WARN_SUPPLY_HIGH_FAIL = 0x440F,
+ NM_EVT_CAUSE_WARN_VSWR_HIGH_FAIL = 0x4410,
+ NM_EVT_CAUSE_WARN_PWR_SUPPLY_HIGH_FAIL = 0x4411,
+ NM_EVT_CAUSE_WARN_PWR_PA_HIGH_FAIL = 0x4412,
+ NM_EVT_CAUSE_WARN_GPS_FIX_FAIL = 0x4413,
+};
+
+/* This defines the list of notification events for systemd service watchdog.
+ all these events must be notified in a certain service defined timeslot
+ or the service (this app) would be restarted (only if related systemd service
+ unit file has WatchdogSec!=0).
+ WARNING: swd events must begin with event 0. Last events must be
+ SWD_LAST (max 64 events in this list).
+*/
+enum mgr_swd_events {
+ SWD_MAINLOOP = 0,
+ SWD_CHECK_SENSOR,
+ SWD_UPDATE_HOURS,
+ SWD_CHECK_TEMP_SENSOR,
+ SWD_CHECK_LED_CTRL,
+ SWD_CHECK_CALIB,
+ SWD_CHECK_BTS_CONNECTION,
+ SWD_LAST
+};
+
+int oc2gbts_mgr_vty_init(void);
+int oc2gbts_mgr_parse_config(struct oc2gbts_mgr_instance *mgr);
+int oc2gbts_mgr_nl_init(void);
+int oc2gbts_mgr_sensor_init(struct oc2gbts_mgr_instance *mgr);
+const char *oc2gbts_mgr_sensor_get_state(enum oc2gbts_sensor_state state);
+
+int oc2gbts_mgr_calib_init(struct oc2gbts_mgr_instance *mgr);
+int oc2gbts_mgr_control_init(struct oc2gbts_mgr_instance *mgr);
+int oc2gbts_mgr_calib_run(struct oc2gbts_mgr_instance *mgr);
+void oc2gbts_mgr_dispatch_alarm(struct oc2gbts_mgr_instance *mgr, const int cause, const char *key, const char *text);
+void handle_alert_actions(struct oc2gbts_mgr_instance *mgr);
+void handle_ceased_actions(struct oc2gbts_mgr_instance *mgr);
+void handle_warn_actions(struct oc2gbts_mgr_instance *mgr);
+extern void *tall_mgr_ctx;
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c
new file mode 100644
index 00000000..96162621
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_calib.c
@@ -0,0 +1,750 @@
+/* OCXO calibration control for OC-2G BTS management daemon */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_mgr_calib.c
+ * (C) 2014,2015 by Holger Hans Peter Freyther
+ * (C) 2014 by Harald Welte for the IPA code from the oml router
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "misc/oc2gbts_mgr.h"
+#include "misc/oc2gbts_misc.h"
+#include "misc/oc2gbts_clock.h"
+#include "misc/oc2gbts_swd.h"
+#include "misc/oc2gbts_par.h"
+#include "misc/oc2gbts_led.h"
+#include "osmo-bts/msg_utils.h"
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/select.h>
+
+#include <osmocom/ctrl/control_cmd.h>
+#include <osmocom/ctrl/ports.h>
+
+#include <osmocom/gsm/ipa.h>
+#include <osmocom/gsm/protocol/ipaccess.h>
+
+#include <osmocom/abis/abis.h>
+#include <osmocom/abis/e1_input.h>
+#include <osmocom/abis/ipa.h>
+
+#include <time.h>
+#include <sys/sysinfo.h>
+#include <errno.h>
+
+static void calib_adjust(struct oc2gbts_mgr_instance *mgr);
+static void calib_state_reset(struct oc2gbts_mgr_instance *mgr, int reason);
+static void calib_loop_run(void *_data);
+
+static int ocxodac_saved_value = -1;
+
+enum calib_state {
+ CALIB_INITIAL,
+ CALIB_IN_PROGRESS,
+ CALIB_GPS_WAIT_FOR_FIX,
+};
+
+enum calib_result {
+ CALIB_FAIL_START,
+ CALIB_FAIL_GPSFIX,
+ CALIB_FAIL_CLKERR,
+ CALIB_FAIL_OCXODAC,
+ CALIB_SUCCESS,
+};
+
+static int oc2gbts_par_get_uptime(void *ctx, int *ret)
+{
+ char *fpath;
+ FILE *fp;
+ int rc;
+
+ fpath = talloc_asprintf(ctx, "%s", UPTIME_TMP_PATH);
+ if (!fpath)
+ return NULL;
+
+ fp = fopen(fpath, "r");
+ if (!fp)
+ fprintf(stderr, "Failed to open %s due to '%s' error\n", fpath, strerror(errno));
+
+ talloc_free(fpath);
+
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fscanf(fp, "%d", ret);
+ if (rc != 1) {
+ fclose(fp);
+ return -EIO;
+ }
+ fclose(fp);
+
+ return 0;
+}
+
+static int oc2gbts_par_set_uptime(void *ctx, int val)
+{
+ char *fpath;
+ FILE *fp;
+ int rc;
+
+ fpath = talloc_asprintf(ctx, "%s", UPTIME_TMP_PATH);
+ if (!fpath)
+ return NULL;
+
+ fp = fopen(fpath, "w");
+ if (!fp)
+ fprintf(stderr, "Failed to open %s due to '%s' error\n", fpath, strerror(errno));
+
+ talloc_free(fpath);
+
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fprintf(fp, "%d", val);
+ if (rc < 0) {
+ fclose(fp);
+ return -EIO;
+ }
+ fsync(fp);
+ fclose(fp);
+
+ return 0;
+}
+
+static void mgr_gps_close(struct oc2gbts_mgr_instance *mgr)
+{
+ if (!mgr->gps.gps_open)
+ return;
+
+ osmo_timer_del(&mgr->gps.fix_timeout);
+
+ osmo_fd_unregister(&mgr->gps.gpsfd);
+ gps_close(&mgr->gps.gpsdata);
+ memset(&mgr->gps.gpsdata, 0, sizeof(mgr->gps.gpsdata));
+ mgr->gps.gps_open = 0;
+}
+
+static void mgr_gps_checkfix(struct oc2gbts_mgr_instance *mgr)
+{
+ struct gps_data_t *data = &mgr->gps.gpsdata;
+
+ /* No 3D fix yet */
+ if (data->fix.mode < MODE_3D) {
+ LOGP(DCALIB, LOGL_DEBUG, "Fix mode not enough: %d\n",
+ data->fix.mode);
+ return;
+ }
+
+ /* Satellite used checking */
+ if (data->satellites_used < 1) {
+ LOGP(DCALIB, LOGL_DEBUG, "Not enough satellites used: %d\n",
+ data->satellites_used);
+ return;
+ }
+
+ mgr->gps.gps_fix_now = (time_t) data->fix.time;
+ LOGP(DCALIB, LOGL_INFO, "Got a GPS fix, satellites used: %d, timestamp: %ld\n",
+ data->satellites_used, mgr->gps.gps_fix_now);
+ osmo_timer_del(&mgr->gps.fix_timeout);
+ mgr_gps_close(mgr);
+}
+
+static int mgr_gps_read(struct osmo_fd *fd, unsigned int what)
+{
+ int rc;
+ struct oc2gbts_mgr_instance *mgr = fd->data;
+
+ rc = gps_read(&mgr->gps.gpsdata);
+ if (rc == -1) {
+ LOGP(DCALIB, LOGL_ERROR, "gpsd vanished during read.\n");
+ calib_state_reset(mgr, CALIB_FAIL_GPSFIX);
+ return -1;
+ }
+
+ if (rc > 0)
+ mgr_gps_checkfix(mgr);
+ return 0;
+}
+
+static void mgr_gps_fix_timeout(void *_data)
+{
+ struct oc2gbts_mgr_instance *mgr = _data;
+
+ LOGP(DCALIB, LOGL_ERROR, "Failed to acquire GPS fix.\n");
+ mgr_gps_close(mgr);
+}
+
+static void mgr_gps_open(struct oc2gbts_mgr_instance *mgr)
+{
+ int rc;
+
+ if (mgr->gps.gps_open)
+ return;
+
+ rc = gps_open("localhost", DEFAULT_GPSD_PORT, &mgr->gps.gpsdata);
+ if (rc != 0) {
+ LOGP(DCALIB, LOGL_ERROR, "Failed to connect to GPS %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_GPSFIX);
+ return;
+ }
+
+ mgr->gps.gps_open = 1;
+ gps_stream(&mgr->gps.gpsdata, WATCH_ENABLE, NULL);
+
+ mgr->gps.gpsfd.data = mgr;
+ mgr->gps.gpsfd.cb = mgr_gps_read;
+ mgr->gps.gpsfd.when = BSC_FD_READ | BSC_FD_EXCEPT;
+ mgr->gps.gpsfd.fd = mgr->gps.gpsdata.gps_fd;
+ if (osmo_fd_register(&mgr->gps.gpsfd) < 0) {
+ LOGP(DCALIB, LOGL_ERROR, "Failed to register GPSD fd\n");
+ calib_state_reset(mgr, CALIB_FAIL_GPSFIX);
+ }
+
+ mgr->calib.state = CALIB_GPS_WAIT_FOR_FIX;
+ mgr->gps.fix_timeout.data = mgr;
+ mgr->gps.fix_timeout.cb = mgr_gps_fix_timeout;
+ osmo_timer_schedule(&mgr->gps.fix_timeout, 60, 0);
+ LOGP(DCALIB, LOGL_INFO, "Opened the GPSD connection waiting for fix: %d\n",
+ mgr->gps.gpsfd.fd);
+}
+
+/* OC2G CTRL interface related functions */
+static void send_ctrl_cmd(struct oc2gbts_mgr_instance *mgr, struct msgb *msg)
+{
+ ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL);
+ ipa_prepend_header(msg, IPAC_PROTO_OSMO);
+ ipa_client_conn_send(mgr->oc2gbts_ctrl.bts_conn, msg);
+}
+
+static void send_set_ctrl_cmd_int(struct oc2gbts_mgr_instance *mgr, const char *key, const int val)
+{
+ struct msgb *msg;
+ int ret;
+
+ msg = msgb_alloc_headroom(1024, 128, "CTRL SET");
+ ret = snprintf((char *) msg->data, 4096, "SET %u %s %d",
+ mgr->oc2gbts_ctrl.last_seqno++, key, val);
+ msg->l2h = msgb_put(msg, ret);
+ return send_ctrl_cmd(mgr, msg);
+}
+
+static void send_set_ctrl_cmd(struct oc2gbts_mgr_instance *mgr, const char *key, const int val, const char *text)
+{
+ struct msgb *msg;
+ int ret;
+
+ msg = msgb_alloc_headroom(1024, 128, "CTRL SET");
+ ret = snprintf((char *) msg->data, 4096, "SET %u %s %d, %s",
+ mgr->oc2gbts_ctrl.last_seqno++, key, val, text);
+ msg->l2h = msgb_put(msg, ret);
+ return send_ctrl_cmd(mgr, msg);
+}
+
+static void calib_start(struct oc2gbts_mgr_instance *mgr)
+{
+ int rc;
+
+ rc = oc2gbts_clock_err_open();
+ if (rc != 0) {
+ LOGP(DCALIB, LOGL_ERROR, "Failed to open clock error module %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_CLKERR);
+ return;
+ }
+
+ rc = oc2gbts_clock_dac_open();
+ if (rc != 0) {
+ LOGP(DCALIB, LOGL_ERROR, "Failed to open OCXO dac module %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_OCXODAC);
+ return;
+ }
+
+ calib_adjust(mgr);
+}
+static int get_uptime(int *uptime)
+{
+ struct sysinfo s_info;
+ int rc;
+ rc = sysinfo(&s_info);
+ if(!rc)
+ *uptime = s_info.uptime /(60 * 60);
+
+ return rc;
+}
+
+static void calib_adjust(struct oc2gbts_mgr_instance *mgr)
+{
+ int rc;
+ int fault;
+ int error_ppt;
+ int accuracy_ppq;
+ int interval_sec;
+ int dac_value;
+ int new_dac_value;
+ int dac_correction;
+ int now = 0;
+
+ /* Get GPS time via GPSD */
+ mgr_gps_open(mgr);
+
+ rc = oc2gbts_clock_err_get(&fault, &error_ppt,
+ &accuracy_ppq, &interval_sec);
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to get clock error measurement %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_CLKERR);
+ return;
+ }
+
+ /* get current up time */
+ rc = get_uptime(&now);
+ if (rc < 0)
+ LOGP(DTEMP, LOGL_ERROR, "Unable to read up time hours: %d (%s)\n", rc, strerror(errno));
+
+ /* read last up time */
+ rc = oc2gbts_par_get_uptime(tall_mgr_ctx, &mgr->gps.last_update);
+ if (rc < 0)
+ LOGP(DCALIB, LOGL_NOTICE, "Last GPS 3D fix can not read (%d). Last GPS 3D fix sets to zero\n", rc);
+
+ if (fault) {
+ LOGP(DCALIB, LOGL_NOTICE, "GPS has no fix, warn_flags=0x%08x, last=%d, now=%d\n",
+ mgr->oc2gbts_ctrl.warn_flags, mgr->gps.last_update, now);
+ if (now >= mgr->gps.last_update + mgr->gps.gps_fix_limit.thresh_warn_max * 24) {
+ if (!(mgr->oc2gbts_ctrl.warn_flags & S_MGR_GPS_FIX_WARN_ALARM)) {
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_GPS_FIX_WARN_ALARM;
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_GPS_FIX_FAIL, "oc2g-oml-alert", "GPS 3D fix has been lost");
+
+ LOGP(DCALIB, LOGL_NOTICE, "GPS has no fix since the last verification, warn_flags=0x%08x, last=%d, now=%d\n",
+ mgr->oc2gbts_ctrl.warn_flags, mgr->gps.last_update, now);
+
+ /* schedule LED pattern for GPS fix lost */
+ mgr->alarms.gps_fix_lost = 1;
+ /* update LED pattern */
+ select_led_pattern(mgr);
+ }
+ } else {
+ /* read from last GPS 3D fix timestamp */
+ rc = oc2gbts_par_get_gps_fix(tall_mgr_ctx, &mgr->gps.last_gps_fix);
+ if (rc < 0)
+ LOGP(DCALIB, LOGL_NOTICE, "Last GPS 3D fix timestamp can not read (%d)\n", rc);
+
+ if (difftime(mgr->gps.gps_fix_now, mgr->gps.last_gps_fix) > mgr->gps.gps_fix_limit.thresh_warn_max * 24 * 60 * 60) {
+ if (!(mgr->oc2gbts_ctrl.warn_flags & S_MGR_GPS_FIX_WARN_ALARM)) {
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_GPS_FIX_WARN_ALARM;
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_GPS_FIX_FAIL, "oc2g-oml-alert", "GPS 3D fix has been lost");
+
+ LOGP(DCALIB, LOGL_NOTICE, "GPS has no fix since the last known GPS fix, warn_flags=0x%08x, gps_last=%ld, gps_now=%ld\n",
+ mgr->oc2gbts_ctrl.warn_flags, mgr->gps.last_gps_fix, mgr->gps.gps_fix_now);
+
+ /* schedule LED pattern for GPS fix lost */
+ mgr->alarms.gps_fix_lost = 1;
+ /* update LED pattern */
+ select_led_pattern(mgr);
+ }
+ }
+ }
+
+ rc = oc2gbts_clock_err_reset();
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to reset clock error module %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_CLKERR);
+ return;
+ }
+
+ calib_state_reset(mgr, CALIB_FAIL_GPSFIX);
+ return;
+ }
+
+ if (!interval_sec) {
+ LOGP(DCALIB, LOGL_INFO, "Skipping this iteration, no integration time\n");
+ calib_state_reset(mgr, CALIB_SUCCESS);
+ return;
+ }
+
+ /* We got GPS 3D fix */
+ LOGP(DCALIB, LOGL_DEBUG, "Got GPS 3D fix warn_flags=0x%08x, uptime_last=%d, uptime_now=%d, gps_last=%ld, gps_now=%ld\n",
+ mgr->oc2gbts_ctrl.warn_flags, mgr->gps.last_update, now, mgr->gps.last_gps_fix, mgr->gps.gps_fix_now);
+
+ if (mgr->oc2gbts_ctrl.warn_flags & S_MGR_GPS_FIX_WARN_ALARM) {
+ /* Store GPS fix as soon as we send ceased alarm */
+ LOGP(DCALIB, LOGL_NOTICE, "Store GPS fix as soon as we send ceased alarm last=%ld, now=%ld\n",
+ mgr->gps.last_gps_fix , mgr->gps.gps_fix_now);
+ rc = oc2gbts_par_set_gps_fix(tall_mgr_ctx, mgr->gps.gps_fix_now);
+ if (rc < 0)
+ LOGP(DCALIB, LOGL_ERROR, "Failed to store GPS 3D fix to storage %d\n", rc);
+
+ /* Store last up time */
+ rc = oc2gbts_par_set_uptime(tall_mgr_ctx, now);
+ if (rc < 0)
+ LOGP(DCALIB, LOGL_ERROR, "Failed to store uptime to storage %d\n", rc);
+
+ mgr->gps.last_update = now;
+
+ /* schedule LED pattern for GPS fix resume */
+ mgr->alarms.gps_fix_lost = 0;
+ /* update LED pattern */
+ select_led_pattern(mgr);
+ /* send ceased alarm if possible */
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_GPS_FIX_FAIL, "oc2g-oml-ceased", "GPS 3D fix has been lost");
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_GPS_FIX_WARN_ALARM;
+
+ }
+ /* Store GPS fix at every hour */
+ if (now > mgr->gps.last_update) {
+ /* Store GPS fix every 60 minutes */
+ LOGP(DCALIB, LOGL_INFO, "Store GPS fix every hour last=%ld, now=%ld\n",
+ mgr->gps.last_gps_fix , mgr->gps.gps_fix_now);
+ rc = oc2gbts_par_set_gps_fix(tall_mgr_ctx, mgr->gps.gps_fix_now);
+ if (rc < 0)
+ LOGP(DCALIB, LOGL_ERROR, "Failed to store GPS 3D fix to storage %d\n", rc);
+
+ /* Store last up time every 60 minutes */
+ rc = oc2gbts_par_set_uptime(tall_mgr_ctx, now);
+ if (rc < 0)
+ LOGP(DCALIB, LOGL_ERROR, "Failed to store uptime to storage %d\n", rc);
+
+ /* update last uptime */
+ mgr->gps.last_update = now;
+ }
+
+ rc = oc2gbts_clock_dac_get(&dac_value);
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to get OCXO dac value %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_OCXODAC);
+ return;
+ }
+
+ /* Set OCXO initial dac value */
+ if (ocxodac_saved_value < 0)
+ ocxodac_saved_value = dac_value;
+
+ LOGP(DCALIB, LOGL_INFO,
+ "Calibration ERR(%f PPB) ACC(%f PPB) INT(%d) DAC(%d)\n",
+ error_ppt / 1000., accuracy_ppq / 1000000., interval_sec, dac_value);
+
+ /* 1 unit of correction equal about 0.5 - 1 PPB correction */
+ dac_correction = (int)(-error_ppt * 0.0015);
+ new_dac_value = dac_value + dac_correction;
+
+ if (new_dac_value > 4095)
+ new_dac_value = 4095;
+ else if (new_dac_value < 0)
+ new_dac_value = 0;
+
+ /* We have a fix, make sure the measured error is
+ meaningful (10 times the accuracy) */
+ if ((new_dac_value != dac_value) && ((100l * abs(error_ppt)) > accuracy_ppq)) {
+
+ LOGP(DCALIB, LOGL_INFO,
+ "Going to apply %d as new clock setting.\n",
+ new_dac_value);
+
+ rc = oc2gbts_clock_dac_set(new_dac_value);
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to set OCXO dac value %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_OCXODAC);
+ return;
+ }
+ rc = oc2gbts_clock_err_reset();
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to reset clock error module %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_CLKERR);
+ return;
+ }
+ }
+ /* New conditions to store DAC value:
+ * - Resolution accuracy less or equal than 0.01PPB (or 10000 PPQ)
+ * - Error less or equal than 2PPB (or 2000PPT)
+ * - Solution different than the last one */
+ else if (accuracy_ppq <= 10000) {
+ if((dac_value != ocxodac_saved_value) && (abs(error_ppt) < 2000)) {
+ LOGP(DCALIB, LOGL_INFO, "Saving OCXO DAC value to memory... val = %d\n", dac_value);
+ rc = oc2gbts_clock_dac_save();
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to save OCXO dac value %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_OCXODAC);
+ } else {
+ ocxodac_saved_value = dac_value;
+ }
+ }
+
+ rc = oc2gbts_clock_err_reset();
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Failed to reset clock error module %d\n", rc);
+ calib_state_reset(mgr, CALIB_FAIL_CLKERR);
+ }
+ }
+
+ calib_state_reset(mgr, CALIB_SUCCESS);
+ return;
+}
+
+static void calib_close(struct oc2gbts_mgr_instance *mgr)
+{
+ oc2gbts_clock_err_close();
+ oc2gbts_clock_dac_close();
+}
+
+static void calib_state_reset(struct oc2gbts_mgr_instance *mgr, int outcome)
+{
+ if (mgr->calib.calib_from_loop) {
+ /*
+ * In case of success calibrate in two hours again
+ * and in case of a failure in some minutes.
+ *
+ * TODO NTQ: Select timeout based on last error and accuracy
+ */
+ int timeout = 60;
+ //int timeout = 2 * 60 * 60;
+ //if (outcome != CALIB_SUCESS) }
+ // timeout = 5 * 60;
+ //}
+
+ mgr->calib.calib_timeout.data = mgr;
+ mgr->calib.calib_timeout.cb = calib_loop_run;
+ osmo_timer_schedule(&mgr->calib.calib_timeout, timeout, 0);
+ /* TODO: do we want to notify if we got a calibration error, like no gps fix? */
+ oc2gbts_swd_event(mgr, SWD_CHECK_CALIB);
+ }
+
+ mgr->calib.state = CALIB_INITIAL;
+ calib_close(mgr);
+}
+
+static int calib_run(struct oc2gbts_mgr_instance *mgr, int from_loop)
+{
+ if (mgr->calib.state != CALIB_INITIAL) {
+ LOGP(DCALIB, LOGL_ERROR, "Calib is already in progress.\n");
+ return -1;
+ }
+
+ /* Validates if we have a bts connection */
+ if (mgr->oc2gbts_ctrl.is_up) {
+ LOGP(DCALIB, LOGL_DEBUG, "Bts connection is up.\n");
+ oc2gbts_swd_event(mgr, SWD_CHECK_BTS_CONNECTION);
+ }
+
+ mgr->calib.calib_from_loop = from_loop;
+
+ /* From now on everything will be handled from the failure */
+ mgr->calib.state = CALIB_IN_PROGRESS;
+ calib_start(mgr);
+ return 0;
+}
+
+static void calib_loop_run(void *_data)
+{
+ int rc;
+ struct oc2gbts_mgr_instance *mgr = _data;
+
+ LOGP(DCALIB, LOGL_INFO, "Going to calibrate the system.\n");
+ rc = calib_run(mgr, 1);
+ if (rc != 0) {
+ calib_state_reset(mgr, CALIB_FAIL_START);
+ }
+}
+
+int oc2gbts_mgr_calib_run(struct oc2gbts_mgr_instance *mgr)
+{
+ return calib_run(mgr, 0);
+}
+
+static void schedule_bts_connect(struct oc2gbts_mgr_instance *mgr)
+{
+ DEBUGP(DLCTRL, "Scheduling BTS connect\n");
+ osmo_timer_schedule(&mgr->oc2gbts_ctrl.recon_timer, 1, 0);
+}
+
+/* link to BSC has gone up or down */
+static void bts_updown_cb(struct ipa_client_conn *link, int up)
+{
+ struct oc2gbts_mgr_instance *mgr = link->data;
+
+ LOGP(DLCTRL, LOGL_INFO, "BTS connection %s\n", up ? "up" : "down");
+
+ if (up) {
+ mgr->oc2gbts_ctrl.is_up = 1;
+ mgr->oc2gbts_ctrl.last_seqno = 0;
+ /* handle any pending alarm */
+ handle_alert_actions(mgr);
+ handle_warn_actions(mgr);
+ } else {
+ mgr->oc2gbts_ctrl.is_up = 0;
+ schedule_bts_connect(mgr);
+ }
+
+}
+
+/* BTS re-connect timer call-back */
+static void bts_recon_timer_cb(void *data)
+{
+ int rc;
+ struct oc2gbts_mgr_instance *mgr = data;
+
+ /* update LED pattern */
+ select_led_pattern(mgr);
+
+ /* The connection failures are to be expected during boot */
+ mgr->oc2gbts_ctrl.bts_conn->ofd->when |= BSC_FD_WRITE;
+ rc = ipa_client_conn_open(mgr->oc2gbts_ctrl.bts_conn);
+ if (rc < 0) {
+ LOGP(DLCTRL, LOGL_NOTICE, "Failed to connect to BTS.\n");
+ schedule_bts_connect(mgr);
+ }
+}
+
+static void oc2gbts_handle_ctrl(struct oc2gbts_mgr_instance *mgr, struct msgb *msg)
+{
+ struct ctrl_cmd *cmd = ctrl_cmd_parse(tall_mgr_ctx, msg);
+ int cause = atoi(cmd->reply);
+
+ if (!cmd) {
+ LOGP(DCALIB, LOGL_ERROR, "Failed to parse command/response\n");
+ return;
+ }
+
+ switch (cmd->type) {
+ case CTRL_TYPE_GET_REPLY:
+ LOGP(DCALIB, LOGL_INFO, "Got GET_REPLY from BTS cause=0x%x\n", cause);
+ break;
+ case CTRL_TYPE_SET_REPLY:
+ LOGP(DCALIB, LOGL_INFO, "Got SET_REPLY from BTS cause=0x%x\n", cause);
+ break;
+ default:
+ LOGP(DCALIB, LOGL_ERROR,
+ "Unhandled CTRL response: %d. Resetting state\n",
+ cmd->type);
+ break;
+ }
+
+ talloc_free(cmd);
+ return;
+}
+
+static int bts_read_cb(struct ipa_client_conn *link, struct msgb *msg)
+{
+ int rc;
+ struct ipaccess_head *hh = (struct ipaccess_head *) msgb_l1(msg);
+ struct ipaccess_head_ext *hh_ext;
+
+ LOGP(DLCTRL, LOGL_DEBUG, "Received data from BTS: %s\n",
+ osmo_hexdump(msgb_data(msg), msgb_length(msg)));
+
+ /* regular message handling */
+ rc = msg_verify_ipa_structure(msg);
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR,
+ "Invalid IPA message from BTS (rc=%d)\n", rc);
+ goto err;
+ }
+
+ switch (hh->proto) {
+ case IPAC_PROTO_OSMO:
+ hh_ext = (struct ipaccess_head_ext *) hh->data;
+ switch (hh_ext->proto) {
+ case IPAC_PROTO_EXT_CTRL:
+ oc2gbts_handle_ctrl(link->data, msg);
+ break;
+ default:
+ LOGP(DCALIB, LOGL_NOTICE,
+ "Unhandled osmo ID %u from BTS\n", hh_ext->proto);
+ };
+ msgb_free(msg);
+ break;
+ default:
+ LOGP(DCALIB, LOGL_NOTICE,
+ "Unhandled stream ID %u from BTS\n", hh->proto);
+ msgb_free(msg);
+ break;
+ }
+ return 0;
+err:
+ msgb_free(msg);
+ return -1;
+}
+
+int oc2gbts_mgr_calib_init(struct oc2gbts_mgr_instance *mgr)
+{
+ int rc;
+
+ /* initialize last uptime */
+ mgr->gps.last_update = 0;
+ rc = oc2gbts_par_set_uptime(tall_mgr_ctx, mgr->gps.last_update);
+ if (rc < 0)
+ LOGP(DCALIB, LOGL_ERROR, "Failed to store uptime to storage %d\n", rc);
+
+ /* get last GPS 3D fix timestamp */
+ mgr->gps.last_gps_fix = 0;
+ rc = oc2gbts_par_get_gps_fix(tall_mgr_ctx, &mgr->gps.last_gps_fix);
+ if (rc < 0) {
+ LOGP(DCALIB, LOGL_ERROR, "Failed to get last GPS 3D fix timestamp from storage. Create it anyway %d\n", rc);
+ rc = oc2gbts_par_set_gps_fix(tall_mgr_ctx, mgr->gps.last_gps_fix);
+ if (rc < 0)
+ LOGP(DCALIB, LOGL_ERROR, "Failed to store initial GPS fix to storage %d\n", rc);
+ }
+
+ mgr->calib.state = CALIB_INITIAL;
+ mgr->calib.calib_timeout.data = mgr;
+ mgr->calib.calib_timeout.cb = calib_loop_run;
+ osmo_timer_schedule(&mgr->calib.calib_timeout, 0, 0);
+
+ return rc;
+}
+
+int oc2gbts_mgr_control_init(struct oc2gbts_mgr_instance *mgr)
+{
+ mgr->oc2gbts_ctrl.bts_conn = ipa_client_conn_create(tall_mgr_ctx, NULL, 0,
+ "127.0.0.1", OSMO_CTRL_PORT_BTS,
+ bts_updown_cb, bts_read_cb,
+ NULL, mgr);
+ if (!mgr->oc2gbts_ctrl.bts_conn) {
+ LOGP(DLCTRL, LOGL_ERROR, "Failed to create IPA connection to BTS\n");
+ return -1;
+ }
+
+ mgr->oc2gbts_ctrl.recon_timer.cb = bts_recon_timer_cb;
+ mgr->oc2gbts_ctrl.recon_timer.data = mgr;
+ schedule_bts_connect(mgr);
+
+ return 0;
+}
+
+void oc2gbts_mgr_dispatch_alarm(struct oc2gbts_mgr_instance *mgr, const int cause, const char *key, const char *text)
+{
+ /* Make sure the control link is ready before sending alarm */
+ if (mgr->oc2gbts_ctrl.bts_conn->state != IPA_CLIENT_LINK_STATE_CONNECTED) {
+ LOGP(DLCTRL, LOGL_NOTICE, "MGR losts connection to BTS.\n");
+ LOGP(DLCTRL, LOGL_NOTICE, "MGR drops an alert cause=0x%x, text=%s to BTS\n", cause, text);
+ return;
+ }
+
+ LOGP(DLCTRL, LOGL_DEBUG, "MGR sends an alert cause=0x%x, text=%s to BTS\n", cause, text);
+ send_set_ctrl_cmd(mgr, key, cause, text);
+ return;
+}
+
+
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c
new file mode 100644
index 00000000..db67caf2
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_nl.c
@@ -0,0 +1,208 @@
+/* NetworkListen for NuRAN OC-2G BTS management daemon */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_mgr_nl.c
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "misc/oc2gbts_mgr.h"
+#include "misc/oc2gbts_misc.h"
+#include "misc/oc2gbts_nl.h"
+#include "misc/oc2gbts_par.h"
+#include "misc/oc2gbts_bid.h"
+
+#include <osmo-bts/logging.h>
+
+#include <osmocom/gsm/protocol/ipaccess.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/select.h>
+
+#include <arpa/inet.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#define ETH0_ADDR_SYSFS "/var/oc2g/net/eth0/address"
+
+static struct osmo_fd nl_fd;
+
+/*
+ * The TLV structure in IPA messages in UDP packages is a bit
+ * weird. First the header appears to have an extra NULL byte
+ * and second the L16 of the L16TV needs to include +1 for the
+ * tag. The default msgb/tlv and libosmo-abis routines do not
+ * provide this.
+ */
+
+static void ipaccess_prepend_header_quirk(struct msgb *msg, int proto)
+{
+ struct ipaccess_head *hh;
+
+ /* prepend the ip.access header */
+ hh = (struct ipaccess_head *) msgb_push(msg, sizeof(*hh) + 1);
+ hh->len = htons(msg->len - sizeof(*hh) - 1);
+ hh->proto = proto;
+}
+
+static void quirk_l16tv_put(struct msgb *msg, uint16_t len, uint8_t tag,
+ const uint8_t *val)
+{
+ uint8_t *buf = msgb_put(msg, len + 2 + 1);
+
+ *buf++ = (len + 1) >> 8;
+ *buf++ = (len + 1) & 0xff;
+ *buf++ = tag;
+ memcpy(buf, val, len);
+}
+
+/*
+ * We don't look at the content of the request yet and lie
+ * about most of the responses.
+ */
+static void respond_to(struct sockaddr_in *src, struct osmo_fd *fd,
+ uint8_t *data, size_t len)
+{
+ static int fetched_info = 0;
+ static char mac_str[20] = {0, };
+ static char model_name[64] = {0, };
+ static char ser_str[20] = {0, };
+
+ struct sockaddr_in loc_addr;
+ int rc;
+ char loc_ip[INET_ADDRSTRLEN];
+ struct msgb *msg = msgb_alloc_headroom(512, 128, "ipa get response");
+ if (!msg) {
+ LOGP(DFIND, LOGL_ERROR, "Failed to allocate msgb\n");
+ return;
+ }
+
+ if (!fetched_info) {
+ int fd_eth;
+ int serno;
+ int model;
+ char rev_maj, rev_min;
+
+ /* fetch the MAC */
+ fd_eth = open(ETH0_ADDR_SYSFS, O_RDONLY);
+ if (fd_eth >= 0) {
+ read(fd_eth, mac_str, sizeof(mac_str)-1);
+ mac_str[sizeof(mac_str)-1] = '\0';
+ close(fd_eth);
+ }
+
+ /* fetch the serial number */
+ oc2gbts_par_get_int(OC2GBTS_PAR_SERNR, &serno);
+ snprintf(ser_str, sizeof(ser_str), "%d", serno);
+
+ /* fetch the model and trx number */
+ snprintf(model_name, sizeof(model_name), "OC-2G BTS");
+
+ oc2gbts_rev_get(&rev_maj, &rev_min);
+ snprintf(model_name, sizeof(model_name), "%s Rev %c.%c",
+ model_name, rev_maj, rev_min);
+
+ model = oc2gbts_model_get();
+ if (model >= 0) {
+ snprintf(model_name, sizeof(model_name), "%s (%05X)",
+ model_name, model);
+ }
+ fetched_info = 1;
+ }
+
+ if (source_for_dest(&src->sin_addr, &loc_addr.sin_addr) != 0) {
+ LOGP(DFIND, LOGL_ERROR, "Failed to determine local source\n");
+ return;
+ }
+
+ msgb_put_u8(msg, IPAC_MSGT_ID_RESP);
+
+ /* append MAC addr */
+ quirk_l16tv_put(msg, strlen(mac_str) + 1, IPAC_IDTAG_MACADDR, (uint8_t *) mac_str);
+
+ /* append ip address */
+ inet_ntop(AF_INET, &loc_addr.sin_addr, loc_ip, sizeof(loc_ip));
+ quirk_l16tv_put(msg, strlen(loc_ip) + 1, IPAC_IDTAG_IPADDR, (uint8_t *) loc_ip);
+
+ /* append the serial number */
+ quirk_l16tv_put(msg, strlen(ser_str) + 1, IPAC_IDTAG_SERNR, (uint8_t *) ser_str);
+
+ /* abuse some flags */
+ quirk_l16tv_put(msg, strlen(model_name) + 1, IPAC_IDTAG_UNIT, (uint8_t *) model_name);
+
+ /* ip.access nanoBTS would reply to port==3006 */
+ ipaccess_prepend_header_quirk(msg, IPAC_PROTO_IPACCESS);
+ rc = sendto(fd->fd, msg->data, msg->len, 0, (struct sockaddr *)src, sizeof(*src));
+ if (rc != msg->len)
+ LOGP(DFIND, LOGL_ERROR,
+ "Failed to send with rc(%d) errno(%d)\n", rc, errno);
+}
+
+static int ipaccess_bcast(struct osmo_fd *fd, unsigned int what)
+{
+ uint8_t data[2048];
+ char src[INET_ADDRSTRLEN];
+ struct sockaddr_in addr = {};
+ socklen_t len = sizeof(addr);
+ int rc;
+
+ rc = recvfrom(fd->fd, data, sizeof(data), 0,
+ (struct sockaddr *) &addr, &len);
+ if (rc <= 0) {
+ LOGP(DFIND, LOGL_ERROR,
+ "Failed to read from socket errno(%d)\n", errno);
+ return -1;
+ }
+
+ LOGP(DFIND, LOGL_DEBUG,
+ "Received request from: %s size %d\n",
+ inet_ntop(AF_INET, &addr.sin_addr, src, sizeof(src)), rc);
+
+ if (rc < 6)
+ return 0;
+
+ if (data[2] != IPAC_PROTO_IPACCESS || data[4] != IPAC_MSGT_ID_GET)
+ return 0;
+
+ respond_to(&addr, fd, data + 6, rc - 6);
+ return 0;
+}
+
+int oc2gbts_mgr_nl_init(void)
+{
+ int rc;
+
+ nl_fd.cb = ipaccess_bcast;
+ rc = osmo_sock_init_ofd(&nl_fd, AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ "0.0.0.0", 3006, OSMO_SOCK_F_BIND);
+ if (rc < 0) {
+ perror("Socket creation");
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_temp.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_temp.c
new file mode 100644
index 00000000..f9efd9cd
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_temp.c
@@ -0,0 +1,980 @@
+/* Temperature control for NuRAN OC-2G BTS management daemon */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_mgr_temp.c
+ * (C) 2014 by Holger Hans Peter Freyther
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <inttypes.h>
+#include "misc/oc2gbts_mgr.h"
+#include "misc/oc2gbts_misc.h"
+#include "misc/oc2gbts_temp.h"
+#include "misc/oc2gbts_power.h"
+#include "misc/oc2gbts_led.h"
+#include "misc/oc2gbts_swd.h"
+#include "misc/oc2gbts_bid.h"
+#include "limits.h"
+
+#include <osmo-bts/logging.h>
+
+#include <osmocom/core/timer.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/linuxlist.h>
+
+struct oc2gbts_mgr_instance *s_mgr;
+static struct osmo_timer_list sensor_ctrl_timer;
+
+static const struct value_string state_names[] = {
+ { STATE_NORMAL, "NORMAL" },
+ { STATE_WARNING_HYST, "WARNING (HYST)" },
+ { STATE_WARNING, "WARNING" },
+ { STATE_CRITICAL, "CRITICAL" },
+ { 0, NULL }
+};
+
+/* private function prototype */
+static void sensor_ctrl_check(struct oc2gbts_mgr_instance *mgr);
+
+const char *oc2gbts_mgr_sensor_get_state(enum oc2gbts_sensor_state state)
+{
+ return get_value_string(state_names, state);
+}
+
+static int next_state(enum oc2gbts_sensor_state current_state, int critical, int warning)
+{
+ int next_state = -1;
+ switch (current_state) {
+ case STATE_NORMAL:
+ if (critical)
+ next_state = STATE_CRITICAL;
+ else if (warning)
+ next_state = STATE_WARNING;
+ break;
+ case STATE_WARNING_HYST:
+ if (critical)
+ next_state = STATE_CRITICAL;
+ else if (warning)
+ next_state = STATE_WARNING;
+ else
+ next_state = STATE_NORMAL;
+ break;
+ case STATE_WARNING:
+ if (critical)
+ next_state = STATE_CRITICAL;
+ else if (!warning)
+ next_state = STATE_WARNING_HYST;
+ break;
+ case STATE_CRITICAL:
+ if (!critical && !warning)
+ next_state = STATE_WARNING;
+ break;
+ };
+
+ return next_state;
+}
+
+static void handle_normal_actions(int actions)
+{
+ /* switch on the PA */
+ if (actions & SENSOR_ACT_NORM_PA_ON) {
+ if (oc2gbts_power_set(OC2GBTS_POWER_PA, 1) != 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to switch on the PA\n");
+ } else {
+ LOGP(DTEMP, LOGL_INFO,
+ "Switched on the PA as normal action.\n");
+ }
+ }
+
+ if (actions & SENSOR_ACT_NORM_BTS_SRV_ON) {
+ LOGP(DTEMP, LOGL_INFO,
+ "Going to switch on the BTS service\n");
+ /*
+ * TODO: use/create something like nspawn that serializes
+ * and used SIGCHLD/waitpid to pick up the dead processes
+ * without invoking shell.
+ */
+ system("/bin/systemctl start osmo-bts.service");
+ }
+}
+
+static void handle_actions(int actions)
+{
+ /* switch off the PA */
+ if (actions & SENSOR_ACT_PA_OFF) {
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ if (oc2gbts_power_set(OC2GBTS_POWER_PA, 0) != 0) {
+ LOGP(DTEMP, LOGL_ERROR,
+ "Failed to switch off the PA. Stop BTS?\n");
+ } else {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Switched off the PA due temperature.\n");
+ }
+ }
+ }
+
+ if (actions & SENSOR_ACT_BTS_SRV_OFF) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Going to switch off the BTS service\n");
+ /*
+ * TODO: use/create something like nspawn that serializes
+ * and used SIGCHLD/waitpid to pick up the dead processes
+ * without invoking shell.
+ */
+ system("/bin/systemctl stop osmo-bts.service");
+ }
+}
+
+void handle_ceased_actions(struct oc2gbts_mgr_instance *mgr)
+{ int i;
+ uint32_t cause;
+
+ if (!mgr->oc2gbts_ctrl.is_up)
+ return;
+
+ LOGP(DTEMP, LOGL_DEBUG, "handle_ceased_actions in state %s, warn_flags=0x%x, crit_flags=0x%x\n",
+ oc2gbts_mgr_sensor_get_state(mgr->state.state),
+ mgr->oc2gbts_ctrl.warn_flags,
+ mgr->oc2gbts_ctrl.crit_flags);
+
+ for (i = 0; i < 32; i++) {
+ cause = 1 << i;
+ /* clear warning flag without sending ceased alarm */
+ if (mgr->oc2gbts_ctrl.warn_flags & cause)
+ mgr->oc2gbts_ctrl.warn_flags &= ~cause;
+
+ /* clear warning flag with sending ceased alarm */
+ if (mgr->oc2gbts_ctrl.crit_flags & cause) {
+ /* clear associated flag */
+ mgr->oc2gbts_ctrl.crit_flags &= ~cause;
+ /* dispatch ceased alarm */
+ switch (cause) {
+ case S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_SUPPLY_MAX_FAIL, "oc2g-oml-ceased", "Main power supply temperature is too high");
+ break;
+ case S_MGR_TEMP_SOC_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_SOC_MAX_FAIL, "oc2g-oml-ceased", "SoC temperature is too high");
+ break;
+ case S_MGR_TEMP_FPGA_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_FPGA_MAX_FAIL, "oc2g-oml-ceased", "FPGA temperature is too high");
+ break;
+ case S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_RMS_DET_MAX_FAIL, "oc2g-oml-ceased", "RMS detector temperature is too high");
+ break;
+ case S_MGR_TEMP_OCXO_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_OCXO_MAX_FAIL, "oc2g-oml-ceased", "OCXO temperature is too high");
+ break;
+ case S_MGR_TEMP_TRX_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_TRX_MAX_FAIL, "oc2g-oml-ceased", "TRX temperature is too high");
+ break;
+ case S_MGR_TEMP_PA_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_PA_MAX_FAIL, "oc2g-oml-ceased", "PA temperature is too high");
+ break;
+ case S_MGR_SUPPLY_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_SUPPLY_MAX_FAIL, "oc2g-oml-ceased", "Power supply voltage is too high");
+ break;
+ case S_MGR_SUPPLY_CRIT_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_SUPPLY_MIN_FAIL, "oc2g-oml-ceased", "Power supply voltage is too low");
+ break;
+ case S_MGR_VSWR_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_VSWR_MAX_FAIL, "oc2g-oml-ceased", "VSWR is too high");
+ break;
+ case S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_PWR_SUPPLY_MAX_FAIL, "oc2g-oml-ceased", "Power supply consumption is too high");
+ break;
+ case S_MGR_PWR_PA_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_PWR_PA_MAX_FAIL, "oc2g-oml-ceased", "PA power consumption is too high");
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ return;
+}
+
+void handle_alert_actions(struct oc2gbts_mgr_instance *mgr)
+{ int i;
+ uint32_t cause;
+
+ if (!mgr->oc2gbts_ctrl.is_up)
+ return;
+
+ LOGP(DTEMP, LOGL_DEBUG, "handle_alert_actions in state %s, crit_flags=0x%x\n",
+ oc2gbts_mgr_sensor_get_state(mgr->state.state),
+ mgr->oc2gbts_ctrl.crit_flags);
+
+ for (i = 0; i < 32; i++) {
+ cause = 1 << i;
+ if (mgr->oc2gbts_ctrl.crit_flags & cause) {
+ switch(cause) {
+ case S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_SUPPLY_MAX_FAIL, "oc2g-oml-alert", "Main power supply temperature is too high");
+ break;
+ case S_MGR_TEMP_SOC_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_SOC_MAX_FAIL, "oc2g-oml-alert", "SoC temperature is too high");
+ break;
+ case S_MGR_TEMP_FPGA_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_FPGA_MAX_FAIL, "oc2g-oml-alert", "FPGA temperature is too high");
+ break;
+ case S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_RMS_DET_MAX_FAIL, "oc2g-oml-alert", "RMS detector temperature is too high");
+ break;
+ case S_MGR_TEMP_OCXO_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_OCXO_MAX_FAIL, "oc2g-oml-alert", "OCXO temperature is too high");
+ break;
+ case S_MGR_TEMP_TRX_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_TRX_MAX_FAIL, "oc2g-oml-alert", "TRX temperature is too high");
+ break;
+ case S_MGR_TEMP_PA_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_TEMP_PA_MAX_FAIL, "oc2g-oml-alert", "PA temperature is too high");
+ break;
+ case S_MGR_SUPPLY_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_SUPPLY_MAX_FAIL, "oc2g-oml-alert", "Power supply voltage is too high");
+ break;
+ case S_MGR_SUPPLY_CRIT_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_SUPPLY_MIN_FAIL, "oc2g-oml-alert", "Power supply voltage is too low");
+ break;
+ case S_MGR_VSWR_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_VSWR_MAX_FAIL, "oc2g-oml-alert", "VSWR is too high");
+ break;
+ case S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_PWR_SUPPLY_MAX_FAIL, "oc2g-oml-alert", "Power supply consumption is too high");
+ break;
+ case S_MGR_PWR_PA_CRIT_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_CRIT_PWR_PA_MAX_FAIL, "oc2g-oml-alert", "PA power consumption is too high");
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ return;
+}
+
+void handle_warn_actions(struct oc2gbts_mgr_instance *mgr)
+{ int i;
+ uint32_t cause;
+
+ if (!mgr->oc2gbts_ctrl.is_up)
+ return;
+
+ LOGP(DTEMP, LOGL_DEBUG, "handle_warn_actions in state %s, warn_flags=0x%x\n",
+ oc2gbts_mgr_sensor_get_state(mgr->state.state),
+ mgr->oc2gbts_ctrl.warn_flags);
+
+ for (i = 0; i < 32; i++) {
+ cause = 1 << i;
+ if (mgr->oc2gbts_ctrl.warn_flags & cause) {
+ switch(cause) {
+ case S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_SUPPLY_HIGH_FAIL, "oc2g-oml-alert", "Main power supply temperature is high");
+ break;
+ case S_MGR_TEMP_SUPPLY_WARN_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_SUPPLY_LOW_FAIL, "oc2g-oml-alert", "Main power supply temperature is low");
+ break;
+ case S_MGR_TEMP_SOC_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_SOC_HIGH_FAIL, "oc2g-oml-alert", "SoC temperature is high");
+ break;
+ case S_MGR_TEMP_SOC_WARN_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_SOC_LOW_FAIL, "oc2g-oml-alert", "SoC temperature is low");
+ break;
+ case S_MGR_TEMP_FPGA_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_FPGA_HIGH_FAIL, "oc2g-oml-alert", "FPGA temperature is high");
+ break;
+ case S_MGR_TEMP_FPGA_WARN_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_FPGA_LOW_FAIL, "oc2g-oml-alert", "FPGA temperature is low");
+ break;
+ case S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_RMS_DET_HIGH_FAIL, "oc2g-oml-alert", "RMS detector temperature is high");
+ break;
+ case S_MGR_TEMP_RMS_DET_WARN_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_RMS_DET_LOW_FAIL, "oc2g-oml-alert", "RMS detector temperature is low");
+ break;
+ case S_MGR_TEMP_OCXO_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_OCXO_HIGH_FAIL, "oc2g-oml-alert", "OCXO temperature is high");
+ break;
+ case S_MGR_TEMP_OCXO_WARN_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_OCXO_LOW_FAIL, "oc2g-oml-alert", "OCXO temperature is low");
+ break;
+ case S_MGR_TEMP_TRX_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_TRX_HIGH_FAIL, "oc2g-oml-alert", "TRX temperature is high");
+ break;
+ case S_MGR_TEMP_TRX_WARN_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_TRX_LOW_FAIL, "oc2g-oml-alert", "TRX temperature is low");
+ break;
+ case S_MGR_TEMP_PA_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_PA_HIGH_FAIL, "oc2g-oml-alert", "PA temperature is high");
+ break;
+ case S_MGR_TEMP_PA_WARN_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_TEMP_PA_LOW_FAIL, "oc2g-oml-alert", "PA temperature is low");
+ break;
+ case S_MGR_SUPPLY_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_SUPPLY_HIGH_FAIL, "oc2g-oml-alert", "Power supply voltage is high");
+ break;
+ case S_MGR_SUPPLY_WARN_MIN_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_SUPPLY_LOW_FAIL, "oc2g-oml-alert", "Power supply voltage is low");
+ break;
+ case S_MGR_VSWR_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_VSWR_HIGH_FAIL, "oc2g-oml-alert", "VSWR is high");
+ break;
+ case S_MGR_PWR_SUPPLY_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_PWR_SUPPLY_HIGH_FAIL, "oc2g-oml-alert", "Power supply consumption is high");
+ break;
+ case S_MGR_PWR_PA_WARN_MAX_ALARM:
+ oc2gbts_mgr_dispatch_alarm(mgr, NM_EVT_CAUSE_WARN_PWR_PA_HIGH_FAIL, "oc2g-oml-alert", "PA power consumption is high");
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ return;
+}
+
+/**
+ * Go back to normal! Depending on the configuration execute the normal
+ * actions that could (start to) undo everything we did in the other
+ * states. What is still missing is the power increase/decrease depending
+ * on the state. E.g. starting from WARNING_HYST we might want to slowly
+ * ramp up the output power again.
+ */
+static void execute_normal_act(struct oc2gbts_mgr_instance *manager)
+{
+ LOGP(DTEMP, LOGL_NOTICE, "System is back to normal state.\n");
+ handle_ceased_actions(manager);
+ handle_normal_actions(manager->state.action_norm);
+}
+
+static void execute_warning_act(struct oc2gbts_mgr_instance *manager)
+{
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning state.\n");
+ handle_warn_actions(manager);
+ handle_actions(manager->state.action_warn);
+}
+
+/* Preventive timer call-back */
+static void preventive_timer_cb(void *_data)
+{
+ struct oc2gbts_mgr_instance *mgr = _data;
+
+ /* Delete current preventive timer if possible */
+ osmo_timer_del(&mgr->alarms.preventive_timer);
+
+ LOGP(DTEMP, LOGL_DEBUG, "Preventive timer expired in %d sec, retry=%d\n",
+ mgr->alarms.preventive_duration,
+ mgr->alarms.preventive_retry);
+
+ /* Turn on PA and clear action flag */
+ if (mgr->state.action_comb & SENSOR_ACT_PA_OFF) {
+ mgr->state.action_comb &= ~SENSOR_ACT_PA_OFF;
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ if (oc2gbts_power_set(OC2GBTS_POWER_PA, 1))
+ LOGP(DTEMP, LOGL_ERROR, "Failed to switch on the PA\n");
+ else
+ LOGP(DTEMP, LOGL_DEBUG, "Re-enable PA after preventive timer expired in %d sec\n",
+ mgr->alarms.preventive_duration);
+ }
+ }
+
+ /* restart check sensor timer */
+ osmo_timer_del(&sensor_ctrl_timer);
+ osmo_timer_schedule(&sensor_ctrl_timer, OC2GBTS_SENSOR_TIMER_DURATION, 0);
+
+ return;
+
+}
+
+static void execute_preventive_act(struct oc2gbts_mgr_instance *manager)
+{
+ struct oc2gbts_preventive_list *prevent_list, *prevent_list2;
+
+ /* update LED pattern */
+ select_led_pattern(manager);
+
+ /* do nothing if the preventive action list is empty */
+ if (llist_empty(&manager->alarms.list))
+ return;
+
+ llist_for_each_entry_safe(prevent_list, prevent_list2, &manager->alarms.list, list) {
+ /* Delete the timer in list and perform action*/
+ if (prevent_list) {
+ /* Delete current preventive timer if possible */
+ osmo_timer_del(&manager->alarms.preventive_timer);
+
+ /* Start/restart preventive timer */
+ if (prevent_list->param.sleep_sec) {
+ manager->alarms.preventive_timer.cb = preventive_timer_cb;
+ manager->alarms.preventive_timer.data = manager;
+ osmo_timer_schedule(&manager->alarms.preventive_timer, prevent_list->param.sleep_sec, 0);
+
+ LOGP(DTEMP, LOGL_DEBUG,"Preventive timer scheduled for %d sec, preventive flags=0x%x\n",
+ prevent_list->param.sleep_sec,
+ prevent_list->action_flag);
+ }
+ /* Update active flags */
+ manager->state.action_comb |= prevent_list->action_flag;
+
+ /* Turn off PA */
+ if (manager->state.action_comb & SENSOR_ACT_PA_OFF) {
+ if (oc2gbts_power_set(OC2GBTS_POWER_PA, 0))
+ LOGP(DTEMP, LOGL_ERROR, "Failed to switch off the PA\n");
+ }
+
+ /* Delete this preventive entry */
+ llist_del(&prevent_list->list);
+ talloc_free(prevent_list);
+ LOGP(DTEMP, LOGL_DEBUG,"Deleted preventive entry from list, entries left=%d\n",
+ llist_count(&manager->alarms.list));
+
+ /* stay in last state is preventive active has exceed maximum number of retries */
+ if (manager->alarms.preventive_retry > OC2GBTS_PREVENT_RETRY)
+ LOGP(DTEMP, LOGL_NOTICE, "Maximum number of preventive active exceed\n");
+ else
+ /* increase retry counter */
+ manager->alarms.preventive_retry++;
+ }
+ }
+ return;
+}
+
+static void execute_critical_act(struct oc2gbts_mgr_instance *manager)
+{
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical warning.\n");
+ handle_alert_actions(manager);
+ handle_actions(manager->state.action_crit);
+
+}
+
+static void oc2gbts_mgr_sensor_handle(struct oc2gbts_mgr_instance *manager,
+ int critical, int warning)
+{
+ int new_state = next_state(manager->state.state, critical, warning);
+
+ /* run preventive action if it is possible */
+ execute_preventive_act(manager);
+
+ /* Nothing changed */
+ if (new_state < 0)
+ return;
+ LOGP(DTEMP, LOGL_INFO, "Moving from state %s to %s.\n",
+ get_value_string(state_names, manager->state.state),
+ get_value_string(state_names, new_state));
+ manager->state.state = new_state;
+ switch (manager->state.state) {
+ case STATE_NORMAL:
+ execute_normal_act(manager);
+ /* reset alarms */
+ manager->alarms.temp_high = 0;
+ manager->alarms.temp_max = 0;
+ manager->alarms.vswr_high = 0;
+ manager->alarms.vswr_max = 0;
+ manager->alarms.supply_low = 0;
+ manager->alarms.supply_min = 0;
+ manager->alarms.supply_pwr_high = 0;
+ manager->alarms.supply_pwr_max = 0;
+ manager->alarms.pa_pwr_max = 0;
+ manager->alarms.pa_pwr_high = 0;
+ manager->state.action_comb = 0;
+ manager->alarms.preventive_retry = 0;
+ /* update LED pattern */
+ select_led_pattern(manager);
+ break;
+ case STATE_WARNING_HYST:
+ /* do nothing? Maybe start to increase transmit power? */
+ break;
+ case STATE_WARNING:
+ execute_warning_act(manager);
+ /* update LED pattern */
+ select_led_pattern(manager);
+ break;
+ case STATE_CRITICAL:
+ execute_critical_act(manager);
+ /* update LED pattern */
+ select_led_pattern(manager);
+ break;
+ };
+}
+
+static void schedule_preventive_action(struct oc2gbts_mgr_instance *mgr, int action, int duration)
+{
+ struct oc2gbts_preventive_list *prevent_list;
+
+ /* add to pending list */
+ prevent_list = talloc_zero(tall_mgr_ctx, struct oc2gbts_preventive_list);
+ if (prevent_list) {
+ prevent_list->action_flag = action;
+ prevent_list->param.sleep_sec = duration;
+ prevent_list->param.sleep_usec = 0;
+ llist_add_tail(&prevent_list->list, &mgr->alarms.list);
+ LOGP(DTEMP, LOGL_DEBUG,"Added preventive action to list, duration=%d sec, total entries=%d\n",
+ prevent_list->param.sleep_sec,
+ llist_count(&mgr->alarms.list));
+ }
+ return;
+}
+
+static void sensor_ctrl_check(struct oc2gbts_mgr_instance *mgr)
+{
+ int rc;
+ int temp, volt, vswr, power = 0;
+ int warn_thresh_passed = 0;
+ int crit_thresh_passed = 0;
+ int action = 0;
+
+ LOGP(DTEMP, LOGL_INFO, "Going to check the temperature.\n");
+
+ /* Read the current supply temperature */
+ rc = oc2gbts_temp_get(OC2GBTS_TEMP_SUPPLY, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the supply temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ temp = temp / 1000;
+ if (temp > mgr->temp.supply_temp_limit.thresh_warn_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because supply temperature is over %d\n", mgr->temp.supply_temp_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.temp_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ if (temp < mgr->temp.supply_temp_limit.thresh_warn_min){
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because supply temperature is under %d\n", mgr->temp.supply_temp_limit.thresh_warn_min);
+ warn_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_SUPPLY_WARN_MIN_ALARM;
+ }
+ if (temp > mgr->temp.supply_temp_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because supply temperature is over %d\n", mgr->temp.supply_temp_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.temp_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_SUPPLY_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_SUPPLY_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "Supply temperature is: %d\n", temp);
+ }
+
+ /* Read the current SoC temperature */
+ rc = oc2gbts_temp_get(OC2GBTS_TEMP_SOC, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the SoC temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ temp = temp / 1000;
+ if (temp > mgr->temp.soc_temp_limit.thresh_warn_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because SoC temperature is over %d\n", mgr->temp.soc_temp_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.temp_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_SOC_WARN_MAX_ALARM;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ if (temp < mgr->temp.soc_temp_limit.thresh_warn_min){
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because SoC temperature is under %d\n", mgr->temp.soc_temp_limit.thresh_warn_min);
+ warn_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_SOC_WARN_MIN_ALARM;
+ }
+ if (temp > mgr->temp.soc_temp_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because SoC temperature is over %d\n", mgr->temp.soc_temp_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.temp_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_SOC_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_SOC_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "SoC temperature is: %d\n", temp);
+ }
+
+ /* Read the current fpga temperature */
+ rc = oc2gbts_temp_get(OC2GBTS_TEMP_FPGA, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the fpga temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ temp = temp / 1000;
+ if (temp > mgr->temp.fpga_temp_limit.thresh_warn_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because fpga temperature is over %d\n", mgr->temp.fpga_temp_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.temp_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_FPGA_WARN_MAX_ALARM;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ if (temp < mgr->temp.fpga_temp_limit.thresh_warn_min) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because fpga temperature is under %d\n", mgr->temp.fpga_temp_limit.thresh_warn_min);
+ warn_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_FPGA_WARN_MIN_ALARM;
+ }
+ if (temp > mgr->temp.fpga_temp_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because fpga temperature is over %d\n", mgr->temp.fpga_temp_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.temp_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_FPGA_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_FPGA_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "FPGA temperature is: %d\n", temp);
+ }
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) || oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ /* Read the current RMS detector temperature */
+ rc = oc2gbts_temp_get(OC2GBTS_TEMP_RMSDET, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the RMS detector temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ temp = temp / 1000;
+ if (temp > mgr->temp.rmsdet_temp_limit.thresh_warn_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because RMS detector temperature is over %d\n", mgr->temp.rmsdet_temp_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.temp_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ if (temp < mgr->temp.rmsdet_temp_limit.thresh_warn_min) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because RMS detector temperature is under %d\n", mgr->temp.rmsdet_temp_limit.thresh_warn_min);
+ warn_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_RMS_DET_WARN_MIN_ALARM;
+ }
+ if (temp > mgr->temp.rmsdet_temp_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because RMS detector temperature is over %d\n", mgr->temp.rmsdet_temp_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.temp_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_RMS_DET_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_RMS_DET_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "RMS detector temperature is: %d\n", temp);
+ }
+ }
+
+ /* Read the current OCXO temperature */
+ rc = oc2gbts_temp_get(OC2GBTS_TEMP_OCXO, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the OCXO temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ temp = temp / 1000;
+ if (temp > mgr->temp.ocxo_temp_limit.thresh_warn_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because OCXO temperature is over %d\n", mgr->temp.ocxo_temp_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.temp_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_OCXO_WARN_MAX_ALARM;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ if (temp < mgr->temp.ocxo_temp_limit.thresh_warn_min) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because OCXO temperature is under %d\n", mgr->temp.ocxo_temp_limit.thresh_warn_min);
+ warn_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_OCXO_WARN_MIN_ALARM;
+ }
+ if (temp > mgr->temp.ocxo_temp_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because OCXO temperature is over %d\n", mgr->temp.ocxo_temp_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.temp_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_OCXO_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_OCXO_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "OCXO temperature is: %d\n", temp);
+ }
+
+ /* Read the current TX temperature */
+ rc = oc2gbts_temp_get(OC2GBTS_TEMP_TX, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the TX temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ temp = temp / 1000;
+ if (temp > mgr->temp.tx_temp_limit.thresh_warn_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because TX temperature is over %d\n", mgr->temp.tx_temp_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.temp_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_TRX_WARN_MAX_ALARM;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ if (temp < mgr->temp.tx_temp_limit.thresh_warn_min) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because TX temperature is under %d\n", mgr->temp.tx_temp_limit.thresh_warn_min);
+ warn_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_TRX_WARN_MIN_ALARM;
+ }
+ if (temp > mgr->temp.tx_temp_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because TX temperature is over %d\n", mgr->temp.tx_temp_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.temp_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_TRX_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_TRX_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "TX temperature is: %d\n", temp);
+ }
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) {
+ /* Read the current PA temperature */
+ rc = oc2gbts_temp_get(OC2GBTS_TEMP_PA, &temp);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the PA temperature. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ temp = temp / 1000;
+ if (temp > mgr->temp.pa_temp_limit.thresh_warn_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because PA temperature because is over %d\n", mgr->temp.pa_temp_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.temp_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_PA_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ if (temp < mgr->temp.pa_temp_limit.thresh_warn_min) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because PA temperature because is under %d\n", mgr->temp.pa_temp_limit.thresh_warn_min);
+ warn_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_TEMP_PA_WARN_MIN_ALARM;
+ }
+ if (temp > mgr->temp.pa_temp_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because PA temperature because is over %d\n", mgr->temp.pa_temp_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.temp_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_TEMP_PA_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_TEMP_PA_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "PA temperature is: %d\n", temp);
+ }
+ }
+
+ /* Read the current main supply voltage */
+ if (oc2gbts_power_get(OC2GBTS_POWER_SUPPLY)) {
+ rc = oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY, OC2GBTS_POWER_VOLTAGE, &volt);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the main supply voltage. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ if (volt > mgr->volt.supply_volt_limit.thresh_warn_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because supply voltage is over %d\n", mgr->volt.supply_volt_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_SUPPLY_WARN_MAX_ALARM;
+ }
+ if (volt < mgr->volt.supply_volt_limit.thresh_warn_min) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because supply voltage is under %d\n", mgr->volt.supply_volt_limit.thresh_warn_min);
+ warn_thresh_passed = 1;
+ mgr->alarms.supply_low = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_SUPPLY_WARN_MIN_ALARM;
+ }
+ if (volt > mgr->volt.supply_volt_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because supply voltage is over %d\n", mgr->volt.supply_volt_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_SUPPLY_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_SUPPLY_WARN_MAX_ALARM;
+ }
+
+ if (volt < mgr->volt.supply_volt_limit.thresh_crit_min) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because supply voltage is under %d\n", mgr->volt.supply_volt_limit.thresh_crit_min);
+ crit_thresh_passed = 1;
+ mgr->alarms.supply_min = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_SUPPLY_CRIT_MIN_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_SUPPLY_WARN_MIN_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_NONE);
+ }
+ LOGP(DTEMP, LOGL_INFO, "Main supply voltage is: %d\n", volt);
+ }
+ }
+
+ /* Read the main supply power consumption */
+ if (oc2gbts_power_get(OC2GBTS_POWER_SUPPLY)) {
+ rc = oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY, OC2GBTS_POWER_POWER, &power);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the power supply current. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ power /= 1000000;
+ if (power > mgr->pwr.supply_pwr_limit.thresh_warn_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because main supply power consumption is over %d\n", mgr->pwr.supply_pwr_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.supply_pwr_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_PWR_SUPPLY_WARN_MAX_ALARM;
+ }
+ if (power > mgr->pwr.supply_pwr_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because main supply power consumption is over %d\n", mgr->pwr.supply_pwr_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_PWR_SUPPLY_WARN_MAX_ALARM;
+
+ if (oc2gbts_power_get(OC2GBTS_POWER_PA)) {
+ mgr->alarms.supply_pwr_max = 1;
+ /* schedule to turn off PA */
+ action = SENSOR_ACT_PA_OFF;
+ /* repeat same alarm to BSC */
+ handle_alert_actions(mgr);
+ }
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_SHORT_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "Main supply current power consumption is: %d\n", power);
+ }
+ } else {
+ /* keep last state */
+ if (mgr->oc2gbts_ctrl.crit_flags & S_MGR_PWR_SUPPLY_CRIT_MAX_ALARM) {
+ warn_thresh_passed = 1;
+ crit_thresh_passed = 1;
+ }
+ }
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ /* Read the current PA power consumption */
+ if (oc2gbts_power_get(OC2GBTS_POWER_PA)) {
+ rc = oc2gbts_power_sensor_get(OC2GBTS_POWER_PA, OC2GBTS_POWER_POWER, &power);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the PA power. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ power /= 1000000;
+ if (power > mgr->pwr.pa_pwr_limit.thresh_warn_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because PA power consumption is over %d\n", mgr->pwr.pa_pwr_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.pa_pwr_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_PWR_PA_WARN_MAX_ALARM;
+ }
+ if (power > mgr->pwr.pa_pwr_limit.thresh_crit_max) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because PA power consumption is over %d\n", mgr->pwr.pa_pwr_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.pa_pwr_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_PWR_PA_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_PWR_PA_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_SHORT_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "PA power consumption is: %d\n", power);
+ }
+ } else {
+ /* keep last state */
+ if (mgr->oc2gbts_ctrl.crit_flags & S_MGR_PWR_PA_CRIT_MAX_ALARM) {
+ warn_thresh_passed = 1;
+ crit_thresh_passed = 1;
+ }
+ }
+ }
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ /* Read the current VSWR of powered ON PA*/
+ if (oc2gbts_power_get(OC2GBTS_POWER_PA)) {
+ rc = oc2gbts_vswr_get(OC2GBTS_VSWR, &vswr);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_NOTICE,
+ "Failed to read the VSWR. rc=%d\n", rc);
+ warn_thresh_passed = crit_thresh_passed = 1;
+ } else {
+ if ((vswr > mgr->vswr.vswr_limit.thresh_warn_max) && (mgr->vswr.last_vswr > mgr->vswr.vswr_limit.thresh_warn_max)) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached warning because VSWR is over %d\n", mgr->vswr.vswr_limit.thresh_warn_max);
+ warn_thresh_passed = 1;
+ mgr->alarms.vswr_high = 1;
+ mgr->oc2gbts_ctrl.warn_flags |= S_MGR_VSWR_WARN_MAX_ALARM;
+ }
+ if ((vswr > mgr->vswr.vswr_limit.thresh_crit_max) && (mgr->vswr.last_vswr > mgr->vswr.vswr_limit.thresh_crit_max)) {
+ LOGP(DTEMP, LOGL_NOTICE, "System has reached critical because VSWR is over %d\n", mgr->vswr.vswr_limit.thresh_crit_max);
+ crit_thresh_passed = 1;
+ mgr->alarms.vswr_max = 1;
+ mgr->oc2gbts_ctrl.crit_flags |= S_MGR_VSWR_CRIT_MAX_ALARM;
+ mgr->oc2gbts_ctrl.warn_flags &= ~S_MGR_VSWR_WARN_MAX_ALARM;
+ action = SENSOR_ACT_PA_OFF;
+ /* add to pending list */
+ schedule_preventive_action(mgr, action, OC2GBTS_PREVENT_TIMER_DURATION);
+ }
+ LOGP(DTEMP, LOGL_INFO, "VSWR is: current = %d, last = %d\n", vswr, mgr->vswr.last_vswr);
+
+ /* update last VSWR */
+ mgr->vswr.last_vswr = vswr;
+ }
+ } else {
+ /* keep last state */
+ if (mgr->oc2gbts_ctrl.crit_flags & S_MGR_VSWR_CRIT_MAX_ALARM) {
+ warn_thresh_passed = 1;
+ crit_thresh_passed = 1;
+ }
+ }
+ }
+
+ select_led_pattern(mgr);
+ oc2gbts_mgr_sensor_handle(mgr, crit_thresh_passed, warn_thresh_passed);
+}
+
+static void sensor_ctrl_check_cb(void *_data)
+{
+ struct oc2gbts_mgr_instance *mgr = _data;
+ sensor_ctrl_check(mgr);
+ /* Check every minute? XXX make it configurable! */
+ osmo_timer_schedule(&sensor_ctrl_timer, OC2GBTS_SENSOR_TIMER_DURATION, 0);
+ LOGP(DTEMP, LOGL_DEBUG,"Check sensors timer expired\n");
+ /* TODO: do we want to notify if some sensors could not be read? */
+ oc2gbts_swd_event(mgr, SWD_CHECK_TEMP_SENSOR);
+}
+
+int oc2gbts_mgr_sensor_init(struct oc2gbts_mgr_instance *mgr)
+{
+ int rc = 0;
+
+ /* always enable PA GPIO for OC-2G */
+ if (!oc2gbts_power_get(OC2GBTS_POWER_PA)) {
+ rc = oc2gbts_power_set(OC2GBTS_POWER_PA, 1);
+ if (!rc)
+ LOGP(DTEMP, LOGL_ERROR, "Failed to set GPIO for internal PA\n");
+ }
+
+ s_mgr = mgr;
+ sensor_ctrl_timer.cb = sensor_ctrl_check_cb;
+ sensor_ctrl_timer.data = s_mgr;
+ sensor_ctrl_check_cb(s_mgr);
+ return rc;
+}
+
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_mgr_vty.c b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_vty.c
new file mode 100644
index 00000000..ef527394
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_mgr_vty.c
@@ -0,0 +1,984 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_mgr_vty.c
+ * (C) 2014 by oc2gcom - s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * Author: Alvaro Neira Ayuso <anayuso@oc2gcom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <inttypes.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/misc.h>
+
+#include <osmo-bts/logging.h>
+
+#include "oc2gbts_misc.h"
+#include "oc2gbts_mgr.h"
+#include "oc2gbts_temp.h"
+#include "oc2gbts_power.h"
+#include "oc2gbts_bid.h"
+#include "oc2gbts_led.h"
+#include "btsconfig.h"
+
+static struct oc2gbts_mgr_instance *s_mgr;
+
+static const char copyright[] =
+ "(C) 2012 by Harald Welte <laforge@gnumonks.org>\r\n"
+ "(C) 2014 by Holger Hans Peter Freyther\r\n"
+ "(C) 2015 by Yves Godin <support@nuranwireless.com>\r\n"
+ "License AGPLv3+: GNU AGPL version 2 or later <http://gnu.org/licenses/agpl-3.0.html>\r\n"
+ "This is free software: you are free to change and redistribute it.\r\n"
+ "There is NO WARRANTY, to the extent permitted by law.\r\n";
+
+static int go_to_parent(struct vty *vty)
+{
+ switch (vty->node) {
+ case MGR_NODE:
+ vty->node = CONFIG_NODE;
+ break;
+ case ACT_NORM_NODE:
+ case ACT_WARN_NODE:
+ case ACT_CRIT_NODE:
+ case LIMIT_SUPPLY_TEMP_NODE:
+ case LIMIT_SOC_NODE:
+ case LIMIT_FPGA_NODE:
+ case LIMIT_RMSDET_NODE:
+ case LIMIT_OCXO_NODE:
+ case LIMIT_TX_TEMP_NODE:
+ case LIMIT_PA_TEMP_NODE:
+ case LIMIT_SUPPLY_VOLT_NODE:
+ case LIMIT_VSWR_NODE:
+ case LIMIT_SUPPLY_PWR_NODE:
+ case LIMIT_PA_PWR_NODE:
+ vty->node = MGR_NODE;
+ break;
+ default:
+ vty->node = CONFIG_NODE;
+ }
+ return vty->node;
+}
+
+static int is_config_node(struct vty *vty, int node)
+{
+ switch (node) {
+ case MGR_NODE:
+ case ACT_NORM_NODE:
+ case ACT_WARN_NODE:
+ case ACT_CRIT_NODE:
+ case LIMIT_SUPPLY_TEMP_NODE:
+ case LIMIT_SOC_NODE:
+ case LIMIT_FPGA_NODE:
+ case LIMIT_RMSDET_NODE:
+ case LIMIT_OCXO_NODE:
+ case LIMIT_TX_TEMP_NODE:
+ case LIMIT_PA_TEMP_NODE:
+ case LIMIT_SUPPLY_VOLT_NODE:
+ case LIMIT_VSWR_NODE:
+ case LIMIT_SUPPLY_PWR_NODE:
+ case LIMIT_PA_PWR_NODE:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static struct vty_app_info vty_info = {
+ .name = "oc2gbts-mgr",
+ .version = PACKAGE_VERSION,
+ .go_parent_cb = go_to_parent,
+ .is_config_node = is_config_node,
+ .copyright = copyright,
+};
+
+
+#define MGR_STR "Configure oc2gbts-mgr\n"
+
+static struct cmd_node mgr_node = {
+ MGR_NODE,
+ "%s(oc2gbts-mgr)# ",
+ 1,
+};
+
+static struct cmd_node act_norm_node = {
+ ACT_NORM_NODE,
+ "%s(actions-normal)# ",
+ 1,
+};
+
+static struct cmd_node act_warn_node = {
+ ACT_WARN_NODE,
+ "%s(actions-warn)# ",
+ 1,
+};
+
+static struct cmd_node act_crit_node = {
+ ACT_CRIT_NODE,
+ "%s(actions-critical)# ",
+ 1,
+};
+
+static struct cmd_node limit_supply_temp_node = {
+ LIMIT_SUPPLY_TEMP_NODE,
+ "%s(limit-supply-temp)# ",
+ 1,
+};
+
+static struct cmd_node limit_soc_node = {
+ LIMIT_SOC_NODE,
+ "%s(limit-soc)# ",
+ 1,
+};
+
+static struct cmd_node limit_fpga_node = {
+ LIMIT_FPGA_NODE,
+ "%s(limit-fpga)# ",
+ 1,
+};
+
+static struct cmd_node limit_rmsdet_node = {
+ LIMIT_RMSDET_NODE,
+ "%s(limit-rmsdet)# ",
+ 1,
+};
+
+static struct cmd_node limit_ocxo_node = {
+ LIMIT_OCXO_NODE,
+ "%s(limit-ocxo)# ",
+ 1,
+};
+
+static struct cmd_node limit_tx_temp_node = {
+ LIMIT_TX_TEMP_NODE,
+ "%s(limit-tx-temp)# ",
+ 1,
+};
+static struct cmd_node limit_pa_temp_node = {
+ LIMIT_PA_TEMP_NODE,
+ "%s(limit-pa-temp)# ",
+ 1,
+};
+static struct cmd_node limit_supply_volt_node = {
+ LIMIT_SUPPLY_VOLT_NODE,
+ "%s(limit-supply-volt)# ",
+ 1,
+};
+static struct cmd_node limit_vswr_node = {
+ LIMIT_VSWR_NODE,
+ "%s(limit-vswr)# ",
+ 1,
+};
+static struct cmd_node limit_supply_pwr_node = {
+ LIMIT_SUPPLY_PWR_NODE,
+ "%s(limit-supply-pwr)# ",
+ 1,
+};
+static struct cmd_node limit_pa_pwr_node = {
+ LIMIT_PA_PWR_NODE,
+ "%s(limit-pa-pwr)# ",
+ 1,
+};
+
+static struct cmd_node limit_gps_fix_node = {
+ LIMIT_GPS_FIX_NODE,
+ "%s(limit-gps-fix)# ",
+ 1,
+};
+
+DEFUN(cfg_mgr, cfg_mgr_cmd,
+ "oc2gbts-mgr",
+ MGR_STR)
+{
+ vty->node = MGR_NODE;
+ return CMD_SUCCESS;
+}
+
+static void write_volt_limit(struct vty *vty, const char *name,
+ struct oc2gbts_volt_limit *limit)
+{
+ vty_out(vty, " %s%s", name, VTY_NEWLINE);
+ vty_out(vty, " threshold warning min %d%s",
+ limit->thresh_warn_min, VTY_NEWLINE);
+ vty_out(vty, " threshold critical min %d%s",
+ limit->thresh_crit_min, VTY_NEWLINE);
+}
+
+static void write_vswr_limit(struct vty *vty, const char *name,
+ struct oc2gbts_vswr_limit *limit)
+{
+ vty_out(vty, " %s%s", name, VTY_NEWLINE);
+ vty_out(vty, " threshold warning max %d%s",
+ limit->thresh_warn_max, VTY_NEWLINE);
+}
+
+static void write_pwr_limit(struct vty *vty, const char *name,
+ struct oc2gbts_pwr_limit *limit)
+{
+ vty_out(vty, " %s%s", name, VTY_NEWLINE);
+ vty_out(vty, " threshold warning max %d%s",
+ limit->thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " threshold critical max %d%s",
+ limit->thresh_crit_max, VTY_NEWLINE);
+}
+
+static void write_norm_action(struct vty *vty, const char *name, int actions)
+{
+ vty_out(vty, " %s%s", name, VTY_NEWLINE);
+ vty_out(vty, " %spa-on%s",
+ (actions & SENSOR_ACT_NORM_PA_ON) ? "" : "no ", VTY_NEWLINE);
+ vty_out(vty, " %sbts-service-on%s",
+ (actions & SENSOR_ACT_NORM_BTS_SRV_ON) ? "" : "no ", VTY_NEWLINE);
+}
+
+static void write_action(struct vty *vty, const char *name, int actions)
+{
+ vty_out(vty, " %s%s", name, VTY_NEWLINE);
+ vty_out(vty, " %spa-off%s",
+ (actions & SENSOR_ACT_PA_OFF) ? "" : "no ", VTY_NEWLINE);
+ vty_out(vty, " %sbts-service-off%s",
+ (actions & SENSOR_ACT_BTS_SRV_OFF) ? "" : "no ", VTY_NEWLINE);
+}
+
+static int config_write_mgr(struct vty *vty)
+{
+ vty_out(vty, "oc2gbts-mgr%s", VTY_NEWLINE);
+
+ write_volt_limit(vty, "limits supply_volt", &s_mgr->volt.supply_volt_limit);
+ write_pwr_limit(vty, "limits supply_pwr", &s_mgr->pwr.supply_pwr_limit);
+ write_vswr_limit(vty, "limits vswr", &s_mgr->vswr.vswr_limit);
+
+ write_norm_action(vty, "actions normal", s_mgr->state.action_norm);
+ write_action(vty, "actions warn", s_mgr->state.action_warn);
+ write_action(vty, "actions critical", s_mgr->state.action_crit);
+
+ return CMD_SUCCESS;
+}
+
+static int config_write_dummy(struct vty *vty)
+{
+ return CMD_SUCCESS;
+}
+
+#define CFG_LIMIT_TEMP(name, expl, switch_to, variable) \
+DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \
+ "limits " #name, \
+ "Configure Limits\n" expl) \
+{ \
+ vty->node = switch_to; \
+ vty->index = &s_mgr->temp.variable; \
+ return CMD_SUCCESS; \
+}
+
+CFG_LIMIT_TEMP(supply_temp, "SUPPLY TEMP\n", LIMIT_SUPPLY_TEMP_NODE, supply_temp_limit)
+CFG_LIMIT_TEMP(soc_temp, "SOC TEMP\n", LIMIT_SOC_NODE, soc_temp_limit)
+CFG_LIMIT_TEMP(fpga_temp, "FPGA TEMP\n", LIMIT_FPGA_NODE, fpga_temp_limit)
+CFG_LIMIT_TEMP(rmsdet_temp, "RMSDET TEMP\n", LIMIT_RMSDET_NODE, rmsdet_temp_limit)
+CFG_LIMIT_TEMP(ocxo_temp, "OCXO TEMP\n", LIMIT_OCXO_NODE, ocxo_temp_limit)
+CFG_LIMIT_TEMP(tx_temp, "TX TEMP\n", LIMIT_TX_TEMP_NODE, tx_temp_limit)
+CFG_LIMIT_TEMP(pa_temp, "PA TEMP\n", LIMIT_PA_TEMP_NODE, pa_temp_limit)
+#undef CFG_LIMIT_TEMP
+
+#define CFG_LIMIT_VOLT(name, expl, switch_to, variable) \
+DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \
+ "limits " #name, \
+ "Configure Limits\n" expl) \
+{ \
+ vty->node = switch_to; \
+ vty->index = &s_mgr->volt.variable; \
+ return CMD_SUCCESS; \
+}
+
+CFG_LIMIT_VOLT(supply_volt, "SUPPLY VOLT\n", LIMIT_SUPPLY_VOLT_NODE, supply_volt_limit)
+#undef CFG_LIMIT_VOLT
+
+#define CFG_LIMIT_VSWR(name, expl, switch_to, variable) \
+DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \
+ "limits " #name, \
+ "Configure Limits\n" expl) \
+{ \
+ vty->node = switch_to; \
+ vty->index = &s_mgr->vswr.variable; \
+ return CMD_SUCCESS; \
+}
+
+CFG_LIMIT_VSWR(vswr, "VSWR\n", LIMIT_VSWR_NODE, vswr_limit)
+#undef CFG_LIMIT_VSWR
+
+#define CFG_LIMIT_PWR(name, expl, switch_to, variable) \
+DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \
+ "limits " #name, \
+ "Configure Limits\n" expl) \
+{ \
+ vty->node = switch_to; \
+ vty->index = &s_mgr->pwr.variable; \
+ return CMD_SUCCESS; \
+}
+
+CFG_LIMIT_PWR(supply_pwr, "SUPPLY PWR\n", LIMIT_SUPPLY_PWR_NODE, supply_pwr_limit)
+CFG_LIMIT_PWR(pa_pwr, "PA PWR\n", LIMIT_PA_PWR_NODE, pa_pwr_limit)
+#undef CFG_LIMIT_PWR
+
+#define CFG_LIMIT_GPS_FIX(name, expl, switch_to, variable) \
+DEFUN(cfg_limit_##name, cfg_limit_##name##_cmd, \
+ "limits " #name, \
+ "Configure Limits\n" expl) \
+{ \
+ vty->node = switch_to; \
+ vty->index = &s_mgr->gps.variable; \
+ return CMD_SUCCESS; \
+}
+
+CFG_LIMIT_GPS_FIX(gps_fix, "GPS FIX\n", LIMIT_GPS_FIX_NODE, gps_fix_limit)
+#undef CFG_LIMIT_GPS_FIX
+
+DEFUN(cfg_limit_volt_warn_min, cfg_thresh_volt_warn_min_cmd,
+ "threshold warning min <0-48000>",
+ "Threshold to reach\n" "Warning level\n" "Range\n")
+{
+ struct oc2gbts_volt_limit *limit = vty->index;
+ limit->thresh_warn_min = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_limit_volt_crit_min, cfg_thresh_volt_crit_min_cmd,
+ "threshold critical min <0-48000>",
+ "Threshold to reach\n" "Critical level\n" "Range\n")
+{
+ struct oc2gbts_volt_limit *limit = vty->index;
+ limit->thresh_crit_min = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_limit_vswr_warn_max, cfg_thresh_vswr_warn_max_cmd,
+ "threshold warning max <1000-200000>",
+ "Threshold to reach\n" "Warning level\n" "Range\n")
+{
+ struct oc2gbts_vswr_limit *limit = vty->index;
+ limit->thresh_warn_max = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_limit_vswr_crit_max, cfg_thresh_vswr_crit_max_cmd,
+ "threshold critical max <1000-200000>",
+ "Threshold to reach\n" "Warning level\n" "Range\n")
+{
+ struct oc2gbts_vswr_limit *limit = vty->index;
+ limit->thresh_crit_max = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_limit_pwr_warn_max, cfg_thresh_pwr_warn_max_cmd,
+ "threshold warning max <0-200>",
+ "Threshold to reach\n" "Warning level\n" "Range\n")
+{
+ struct oc2gbts_pwr_limit *limit = vty->index;
+ limit->thresh_warn_max = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_limit_pwr_crit_max, cfg_thresh_pwr_crit_max_cmd,
+ "threshold critical max <0-200>",
+ "Threshold to reach\n" "Warning level\n" "Range\n")
+{
+ struct oc2gbts_pwr_limit *limit = vty->index;
+ limit->thresh_crit_max = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+#define CFG_ACTION(name, expl, switch_to, variable) \
+DEFUN(cfg_action_##name, cfg_action_##name##_cmd, \
+ "actions " #name, \
+ "Configure Actions\n" expl) \
+{ \
+ vty->node = switch_to; \
+ vty->index = &s_mgr->state.variable; \
+ return CMD_SUCCESS; \
+}
+CFG_ACTION(normal, "Normal Actions\n", ACT_NORM_NODE, action_norm)
+CFG_ACTION(warn, "Warning Actions\n", ACT_WARN_NODE, action_warn)
+CFG_ACTION(critical, "Critical Actions\n", ACT_CRIT_NODE, action_crit)
+#undef CFG_ACTION
+
+DEFUN(cfg_action_pa_on, cfg_action_pa_on_cmd,
+ "pa-on",
+ "Switch the Power Amplifier on\n")
+{
+ int *action = vty->index;
+ *action |= SENSOR_ACT_NORM_PA_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_pa_on, cfg_no_action_pa_on_cmd,
+ "no pa-on",
+ NO_STR "Switch the Power Amplifier on\n")
+{
+ int *action = vty->index;
+ *action &= ~SENSOR_ACT_NORM_PA_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_bts_srv_on, cfg_action_bts_srv_on_cmd,
+ "bts-service-on",
+ "Start the systemd oc2gbts.service\n")
+{
+ int *action = vty->index;
+ *action |= SENSOR_ACT_NORM_BTS_SRV_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_bts_srv_on, cfg_no_action_bts_srv_on_cmd,
+ "no bts-service-on",
+ NO_STR "Start the systemd oc2gbts.service\n")
+{
+ int *action = vty->index;
+ *action &= ~SENSOR_ACT_NORM_BTS_SRV_ON;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_pa_off, cfg_action_pa_off_cmd,
+ "pa-off",
+ "Switch the Power Amplifier off\n")
+{
+ int *action = vty->index;
+ *action |= SENSOR_ACT_PA_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_pa_off, cfg_no_action_pa_off_cmd,
+ "no pa-off",
+ NO_STR "Do not switch off the Power Amplifier\n")
+{
+ int *action = vty->index;
+ *action &= ~SENSOR_ACT_PA_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_action_bts_srv_off, cfg_action_bts_srv_off_cmd,
+ "bts-service-off",
+ "Stop the systemd oc2gbts.service\n")
+{
+ int *action = vty->index;
+ *action |= SENSOR_ACT_BTS_SRV_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_action_bts_srv_off, cfg_no_action_bts_srv_off_cmd,
+ "no bts-service-off",
+ NO_STR "Stop the systemd oc2gbts.service\n")
+{
+ int *action = vty->index;
+ *action &= ~SENSOR_ACT_BTS_SRV_OFF;
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_mgr, show_mgr_cmd, "show manager",
+ SHOW_STR "Display information about the manager")
+{
+ int temp, volt, current, power, vswr;
+ vty_out(vty, "Warning alarm flags: 0x%08x%s",
+ s_mgr->oc2gbts_ctrl.warn_flags, VTY_NEWLINE);
+ vty_out(vty, "Critical alarm flags: 0x%08x%s",
+ s_mgr->oc2gbts_ctrl.crit_flags, VTY_NEWLINE);
+ vty_out(vty, "Preventive action retried: %d%s",
+ s_mgr->alarms.preventive_retry, VTY_NEWLINE);
+ vty_out(vty, "Temperature control state: %s%s",
+ oc2gbts_mgr_sensor_get_state(s_mgr->state.state), VTY_NEWLINE);
+ vty_out(vty, "Current Temperatures%s", VTY_NEWLINE);
+ oc2gbts_temp_get(OC2GBTS_TEMP_SUPPLY, &temp);
+ vty_out(vty, " Main Supply : %4.2f Celcius%s",
+ temp/ 1000.0f,
+ VTY_NEWLINE);
+ oc2gbts_temp_get(OC2GBTS_TEMP_SOC, &temp);
+ vty_out(vty, " SoC : %4.2f Celcius%s",
+ temp / 1000.0f,
+ VTY_NEWLINE);
+ oc2gbts_temp_get(OC2GBTS_TEMP_FPGA, &temp);
+ vty_out(vty, " FPGA : %4.2f Celcius%s",
+ temp / 1000.0f,
+ VTY_NEWLINE);
+ if (oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) ||
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ oc2gbts_temp_get(OC2GBTS_TEMP_RMSDET, &temp);
+ vty_out(vty, " RMSDet : %4.2f Celcius%s",
+ temp / 1000.0f,
+ VTY_NEWLINE);
+ }
+ oc2gbts_temp_get(OC2GBTS_TEMP_OCXO, &temp);
+ vty_out(vty, " OCXO : %4.2f Celcius%s",
+ temp / 1000.0f,
+ VTY_NEWLINE);
+ oc2gbts_temp_get(OC2GBTS_TEMP_TX, &temp);
+ vty_out(vty, " TX : %4.2f Celcius%s",
+ temp / 1000.0f,
+ VTY_NEWLINE);
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) {
+ oc2gbts_temp_get(OC2GBTS_TEMP_PA, &temp);
+ vty_out(vty, " Power Amp : %4.2f Celcius%s",
+ temp / 1000.0f,
+ VTY_NEWLINE);
+ }
+ vty_out(vty, "Power Status%s", VTY_NEWLINE);
+ oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY,
+ OC2GBTS_POWER_VOLTAGE, &volt);
+ oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY,
+ OC2GBTS_POWER_CURRENT, &current);
+ oc2gbts_power_sensor_get(OC2GBTS_POWER_SUPPLY,
+ OC2GBTS_POWER_POWER, &power);
+ vty_out(vty, " Main Supply : ON [%6.2f Vdc, %4.2f A, %6.2f W]%s",
+ volt /1000.0f,
+ current /1000.0f,
+ power /1000000.0f,
+ VTY_NEWLINE);
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ oc2gbts_power_sensor_get(OC2GBTS_POWER_PA,
+ OC2GBTS_POWER_VOLTAGE, &volt);
+ oc2gbts_power_sensor_get(OC2GBTS_POWER_PA,
+ OC2GBTS_POWER_CURRENT, &current);
+ oc2gbts_power_sensor_get(OC2GBTS_POWER_PA,
+ OC2GBTS_POWER_POWER, &power);
+ vty_out(vty, " Power Amp : %s [%6.2f Vdc, %4.2f A, %6.2f W]%s",
+ oc2gbts_power_get(OC2GBTS_POWER_PA) ? "ON " : "OFF",
+ volt /1000.0f,
+ current /1000.0f,
+ power /1000000.0f,
+ VTY_NEWLINE);
+ }
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ vty_out(vty, "VSWR Status%s", VTY_NEWLINE);
+ oc2gbts_vswr_get(OC2GBTS_VSWR, &vswr);
+ vty_out(vty, " VSWR : %f %s",
+ vswr / 1000.0f,
+ VTY_NEWLINE);
+ }
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_thresh, show_thresh_cmd, "show thresholds",
+ SHOW_STR "Display information about the thresholds")
+{
+ vty_out(vty, "Temperature limits (Celsius)%s", VTY_NEWLINE);
+ vty_out(vty, " Main supply%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->temp.supply_temp_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->temp.supply_temp_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->temp.supply_temp_limit.thresh_warn_min, VTY_NEWLINE);
+ vty_out(vty, " SoC%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->temp.soc_temp_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->temp.soc_temp_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->temp.soc_temp_limit.thresh_warn_min, VTY_NEWLINE);
+ vty_out(vty, " FPGA%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->temp.fpga_temp_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->temp.fpga_temp_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->temp.fpga_temp_limit.thresh_warn_min, VTY_NEWLINE);
+ if (oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) ||
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ vty_out(vty, " RMSDet%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->temp.rmsdet_temp_limit.thresh_warn_min, VTY_NEWLINE);
+ }
+ vty_out(vty, " OCXO%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->temp.ocxo_temp_limit.thresh_warn_min, VTY_NEWLINE);
+ vty_out(vty, " TX%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->temp.tx_temp_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->temp.tx_temp_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->temp.tx_temp_limit.thresh_warn_min, VTY_NEWLINE);
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) {
+ vty_out(vty, " PA%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->temp.pa_temp_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->temp.pa_temp_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->temp.pa_temp_limit.thresh_warn_min, VTY_NEWLINE);
+ }
+ vty_out(vty, "Power limits%s", VTY_NEWLINE);
+ vty_out(vty, " Main supply (mV)%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->volt.supply_volt_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->volt.supply_volt_limit.thresh_warn_max, VTY_NEWLINE);
+ vty_out(vty, " Warning min : %d%s",s_mgr->volt.supply_volt_limit.thresh_warn_min, VTY_NEWLINE);
+ vty_out(vty, " Critical min : %d%s",s_mgr->volt.supply_volt_limit.thresh_crit_min, VTY_NEWLINE);
+ vty_out(vty, " Main supply power (W)%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->pwr.supply_pwr_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->pwr.supply_pwr_limit.thresh_warn_max, VTY_NEWLINE);
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ vty_out(vty, " PA power (W)%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->pwr.pa_pwr_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->pwr.pa_pwr_limit.thresh_warn_max, VTY_NEWLINE);
+ }
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ vty_out(vty, "VSWR limits%s", VTY_NEWLINE);
+ vty_out(vty, " TX%s", VTY_NEWLINE);
+ vty_out(vty, " Critical max : %d%s",s_mgr->vswr.vswr_limit.thresh_crit_max, VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->vswr.vswr_limit.thresh_warn_max, VTY_NEWLINE);
+ }
+ vty_out(vty, "Days since last GPS 3D fix%s", VTY_NEWLINE);
+ vty_out(vty, " Warning max : %d%s",s_mgr->gps.gps_fix_limit.thresh_warn_max, VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(calibrate_clock, calibrate_clock_cmd,
+ "calibrate clock",
+ "Calibration commands\n"
+ "Calibrate clock against GPS PPS\n")
+{
+ if (oc2gbts_mgr_calib_run(s_mgr) < 0) {
+ vty_out(vty, "%%Failed to start calibration.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ return CMD_SUCCESS;
+}
+
+DEFUN(set_led_pattern, set_led_pattern_cmd,
+ "set led pattern <0-255>",
+ "Set LED pattern\n"
+ "Set LED pattern for debugging purpose only. This pattern will be overridden after 60 seconds by LED pattern of actual system state\n")
+{
+ int pattern_id = atoi(argv[0]);
+
+ if ((pattern_id < 0) || (pattern_id > BLINK_PATTERN_MAX_ITEM)) {
+ vty_out(vty, "%%Invalid LED pattern ID. It must be in range of %d..%d %s", 0, BLINK_PATTERN_MAX_ITEM - 1, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ led_set(s_mgr, pattern_id);
+ return CMD_SUCCESS;
+}
+
+DEFUN(force_mgr_state, force_mgr_state_cmd,
+ "force manager state <0-255>",
+ "Force BTS manager state\n"
+ "Force BTS manager state for debugging purpose only\n")
+{
+ int state = atoi(argv[0]);
+
+ if ((state < 0) || (state > STATE_CRITICAL)) {
+ vty_out(vty, "%%Invalid BTS manager state. It must be in range of %d..%d %s", 0, STATE_CRITICAL, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ s_mgr->state.state = state;
+ return CMD_SUCCESS;
+}
+
+#define LIMIT_TEMP(name, limit, expl, variable, criticity, min_max) \
+DEFUN(limit_temp_##name##_##variable, limit_temp_##name##_##variable##_cmd, \
+ "limit temp " #name " " #criticity " " #min_max " <-200-200>", \
+ "Limit to reach\n" expl) \
+{ \
+ s_mgr->temp.limit.variable = atoi(argv[0]); \
+ return CMD_SUCCESS; \
+}
+
+LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_warn_max, warning, max)
+LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_crit_max, critical, max)
+LIMIT_TEMP(supply, supply_temp_limit, "SUPPLY TEMP\n", thresh_warn_min, warning, min)
+LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_warn_max, warning, max)
+LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_crit_max, critical, max)
+LIMIT_TEMP(soc, supply_temp_limit, "SOC TEMP\n", thresh_warn_min, warning, min)
+LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_warn_max, warning, max)
+LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_crit_max, critical, max)
+LIMIT_TEMP(fpga, fpga_temp_limit, "FPGA TEMP\n", thresh_warn_min, warning, min)
+LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_warn_max, warning, max)
+LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_crit_max, critical, max)
+LIMIT_TEMP(rmsdet, rmsdet_temp_limit, "RMSDET TEMP\n", thresh_warn_min, warning, min)
+LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_warn_max, warning, max)
+LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_crit_max, critical, max)
+LIMIT_TEMP(ocxo, ocxo_temp_limit, "OCXO TEMP\n", thresh_warn_min, warning, min)
+LIMIT_TEMP(tx, tx_temp_limit, "TX TEMP\n", thresh_warn_max, warning, max)
+LIMIT_TEMP(tx, tx_temp_limit, "TX TEMP\n", thresh_crit_max, critical, max)
+LIMIT_TEMP(tx, tx_temp_limit, "TX TEMP\n", thresh_warn_min, warning, min)
+LIMIT_TEMP(pa, pa_temp_limit, "PA TEMP\n", thresh_warn_max, warning, max)
+LIMIT_TEMP(pa, pa_temp_limit, "PA TEMP\n", thresh_crit_max, critical, max)
+LIMIT_TEMP(pa, pa_temp_limit, "PA TEMP\n", thresh_warn_min, warning, min)
+#undef LIMIT_TEMP
+
+#define LIMIT_VOLT(name, limit, expl, variable, criticity, min_max) \
+DEFUN(limit_volt_##name##_##variable, limit_volt_##name##_##variable##_cmd, \
+ "limit " #name " " #criticity " " #min_max " <0-48000>", \
+ "Limit to reach\n" expl) \
+{ \
+ s_mgr->volt.limit.variable = atoi(argv[0]); \
+ return CMD_SUCCESS; \
+}
+
+LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_warn_max, warning, max)
+LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_crit_max, critical, max)
+LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_warn_min, warning, min)
+LIMIT_VOLT(supply, supply_volt_limit, "SUPPLY VOLT\n", thresh_crit_min, critical, min)
+#undef LIMIT_VOLT
+
+#define LIMIT_PWR(name, limit, expl, variable, criticity, min_max) \
+DEFUN(limit_pwr_##name##_##variable, limit_pwr_##name##_##variable##_cmd, \
+ "limit power " #name " " #criticity " " #min_max " <0-200>", \
+ "Limit to reach\n" expl) \
+{ \
+ s_mgr->pwr.limit.variable = atoi(argv[0]); \
+ return CMD_SUCCESS; \
+}
+
+LIMIT_PWR(supply, supply_pwr_limit, "SUPPLY PWR\n", thresh_warn_max, warning, max)
+LIMIT_PWR(supply, supply_pwr_limit, "SUPPLY PWR\n", thresh_crit_max, critical, max)
+LIMIT_PWR(pa, pa_pwr_limit, "PA PWR\n", thresh_warn_max, warning, max)
+LIMIT_PWR(pa, pa_pwr_limit, "PA PWR\n", thresh_crit_max, critical, max)
+#undef LIMIT_PWR
+
+#define LIMIT_VSWR(limit, expl, variable, criticity, min_max) \
+DEFUN(limit_vswr_##variable, limit_vswr_##variable##_cmd, \
+ "limit vswr " #criticity " " #min_max " <1000-200000>", \
+ "Limit to reach\n" expl) \
+{ \
+ s_mgr->vswr.limit.variable = atoi(argv[0]); \
+ return CMD_SUCCESS; \
+}
+
+LIMIT_VSWR(vswr_limit, "VSWR\n", thresh_warn_max, warning, max)
+LIMIT_VSWR(vswr_limit, "VSWR\n", thresh_crit_max, critical, max)
+#undef LIMIT_VSWR
+
+#define LIMIT_GPSFIX(limit, expl, variable, criticity, min_max) \
+DEFUN(limit_gpsfix_##variable, limit_gpsfix_##variable##_cmd, \
+ "limit gpsfix " #criticity " " #min_max " <0-365>", \
+ "Limit to reach\n" expl) \
+{ \
+ s_mgr->gps.limit.variable = atoi(argv[0]); \
+ return CMD_SUCCESS; \
+}
+
+LIMIT_GPSFIX(gps_fix_limit, "GPS FIX\n", thresh_warn_max, warning, max)
+#undef LIMIT_GPSFIX
+
+static void register_limit(int limit, uint32_t unit)
+{
+ switch (unit) {
+ case MGR_LIMIT_TYPE_VOLT:
+ install_element(limit, &cfg_thresh_volt_warn_min_cmd);
+ install_element(limit, &cfg_thresh_volt_crit_min_cmd);
+ break;
+ case MGR_LIMIT_TYPE_VSWR:
+ install_element(limit, &cfg_thresh_vswr_warn_max_cmd);
+ install_element(limit, &cfg_thresh_vswr_crit_max_cmd);
+ break;
+ case MGR_LIMIT_TYPE_PWR:
+ install_element(limit, &cfg_thresh_pwr_warn_max_cmd);
+ install_element(limit, &cfg_thresh_pwr_crit_max_cmd);
+ break;
+ default:
+ break;
+ }
+}
+
+static void register_normal_action(int act)
+{
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ install_element(act, &cfg_action_pa_on_cmd);
+ install_element(act, &cfg_no_action_pa_on_cmd);
+ }
+ install_element(act, &cfg_action_bts_srv_on_cmd);
+ install_element(act, &cfg_no_action_bts_srv_on_cmd);
+}
+
+static void register_action(int act)
+{
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ install_element(act, &cfg_action_pa_off_cmd);
+ install_element(act, &cfg_no_action_pa_off_cmd);
+ }
+ install_element(act, &cfg_action_bts_srv_off_cmd);
+ install_element(act, &cfg_no_action_bts_srv_off_cmd);
+}
+
+static void register_hidden_commands()
+{
+ install_element(ENABLE_NODE, &limit_temp_supply_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_supply_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_supply_thresh_warn_min_cmd);
+ install_element(ENABLE_NODE, &limit_temp_soc_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_soc_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_soc_thresh_warn_min_cmd);
+ install_element(ENABLE_NODE, &limit_temp_fpga_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_fpga_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_fpga_thresh_warn_min_cmd);
+ install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_rmsdet_thresh_warn_min_cmd);
+ install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_ocxo_thresh_warn_min_cmd);
+ install_element(ENABLE_NODE, &limit_temp_tx_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_tx_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_tx_thresh_warn_min_cmd);
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) {
+ install_element(ENABLE_NODE, &limit_temp_pa_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_pa_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_temp_pa_thresh_warn_min_cmd);
+ }
+
+ install_element(ENABLE_NODE, &limit_volt_supply_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_volt_supply_thresh_crit_max_cmd);
+ install_element(ENABLE_NODE, &limit_volt_supply_thresh_warn_min_cmd);
+ install_element(ENABLE_NODE, &limit_volt_supply_thresh_crit_min_cmd);
+
+ install_element(ENABLE_NODE, &limit_pwr_supply_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_pwr_supply_thresh_crit_max_cmd);
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ install_element(ENABLE_NODE, &limit_pwr_pa_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_pwr_pa_thresh_crit_max_cmd);
+ }
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ install_element(ENABLE_NODE, &limit_vswr_thresh_warn_max_cmd);
+ install_element(ENABLE_NODE, &limit_vswr_thresh_crit_max_cmd);
+ }
+
+ install_element(ENABLE_NODE, &limit_gpsfix_thresh_warn_max_cmd);
+}
+
+int oc2gbts_mgr_vty_init(void)
+{
+ vty_init(&vty_info);
+
+ install_element_ve(&show_mgr_cmd);
+ install_element_ve(&show_thresh_cmd);
+
+ install_element(ENABLE_NODE, &calibrate_clock_cmd);
+
+ install_node(&mgr_node, config_write_mgr);
+ install_element(CONFIG_NODE, &cfg_mgr_cmd);
+ vty_install_default(MGR_NODE);
+
+ /* install the limit nodes */
+ install_node(&limit_supply_temp_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_supply_temp_cmd);
+ vty_install_default(LIMIT_SUPPLY_TEMP_NODE);
+
+ install_node(&limit_soc_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_soc_temp_cmd);
+ vty_install_default(LIMIT_SOC_NODE);
+
+ install_node(&limit_fpga_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_fpga_temp_cmd);
+ vty_install_default(LIMIT_FPGA_NODE);
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) ||
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ install_node(&limit_rmsdet_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_rmsdet_temp_cmd);
+ vty_install_default(LIMIT_RMSDET_NODE);
+ }
+
+ install_node(&limit_ocxo_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_ocxo_temp_cmd);
+ vty_install_default(LIMIT_OCXO_NODE);
+
+ install_node(&limit_tx_temp_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_tx_temp_cmd);
+ vty_install_default(LIMIT_TX_TEMP_NODE);
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP)) {
+ install_node(&limit_pa_temp_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_pa_temp_cmd);
+ vty_install_default(LIMIT_PA_TEMP_NODE);
+ }
+
+ install_node(&limit_supply_volt_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_supply_volt_cmd);
+ register_limit(LIMIT_SUPPLY_VOLT_NODE, MGR_LIMIT_TYPE_VOLT);
+ vty_install_default(LIMIT_SUPPLY_VOLT_NODE);
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) &&
+ oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)) {
+ install_node(&limit_vswr_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_vswr_cmd);
+ register_limit(LIMIT_VSWR_NODE, MGR_LIMIT_TYPE_VSWR);
+ vty_install_default(LIMIT_VSWR_NODE);
+ }
+
+ install_node(&limit_supply_pwr_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_supply_pwr_cmd);
+ register_limit(LIMIT_SUPPLY_PWR_NODE, MGR_LIMIT_TYPE_PWR);
+ vty_install_default(LIMIT_SUPPLY_PWR_NODE);
+
+ if (oc2gbts_option_get(OC2GBTS_OPTION_PA)) {
+ install_node(&limit_pa_pwr_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_pa_pwr_cmd);
+ vty_install_default(LIMIT_PA_PWR_NODE);
+ }
+
+ install_node(&limit_gps_fix_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_limit_gps_fix_cmd);
+ vty_install_default(LIMIT_GPS_FIX_NODE);
+
+ /* install the normal node */
+ install_node(&act_norm_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_action_normal_cmd);
+ register_normal_action(ACT_NORM_NODE);
+
+ /* install the warning and critical node */
+ install_node(&act_warn_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_action_warn_cmd);
+ register_action(ACT_WARN_NODE);
+ vty_install_default(ACT_WARN_NODE);
+
+ install_node(&act_crit_node, config_write_dummy);
+ install_element(MGR_NODE, &cfg_action_critical_cmd);
+ register_action(ACT_CRIT_NODE);
+ vty_install_default(ACT_CRIT_NODE);
+
+ /* install LED pattern command for debugging purpose */
+ install_element_ve(&set_led_pattern_cmd);
+ install_element_ve(&force_mgr_state_cmd);
+
+ register_hidden_commands();
+
+ return 0;
+}
+
+int oc2gbts_mgr_parse_config(struct oc2gbts_mgr_instance *manager)
+{
+ int rc;
+
+ s_mgr = manager;
+ rc = vty_read_config_file(s_mgr->config_file, NULL);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to parse the config file: '%s'\n",
+ s_mgr->config_file);
+ return rc;
+ }
+
+ return 0;
+}
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_misc.c b/src/osmo-bts-oc2g/misc/oc2gbts_misc.c
new file mode 100644
index 00000000..bacf07bd
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_misc.c
@@ -0,0 +1,381 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_misc.c
+ * (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <time.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/application.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/logging.h>
+
+#include "oc2gbts_mgr.h"
+#include "btsconfig.h"
+#include "oc2gbts_misc.h"
+#include "oc2gbts_par.h"
+#include "oc2gbts_temp.h"
+#include "oc2gbts_power.h"
+#include "oc2gbts_bid.h"
+
+/*********************************************************************
+ * Temperature handling
+ *********************************************************************/
+
+static const struct {
+ const char *name;
+ int has_max;
+ enum oc2gbts_temp_sensor sensor;
+ enum oc2gbts_par ee_par;
+} temp_data[] = {
+ {
+ .name = "supply_temp",
+ .has_max = 1,
+ .sensor = OC2GBTS_TEMP_SUPPLY,
+ .ee_par = OC2GBTS_PAR_TEMP_SUPPLY_MAX,
+ }, {
+ .name = "soc_temp",
+ .has_max = 0,
+ .sensor = OC2GBTS_TEMP_SOC,
+ .ee_par = OC2GBTS_PAR_TEMP_SOC_MAX,
+ }, {
+ .name = "fpga_temp",
+ .has_max = 0,
+ .sensor = OC2GBTS_TEMP_FPGA,
+ .ee_par = OC2GBTS_PAR_TEMP_FPGA_MAX,
+
+ }, {
+ .name = "rmsdet_temp",
+ .has_max = 1,
+ .sensor = OC2GBTS_TEMP_RMSDET,
+ .ee_par = OC2GBTS_PAR_TEMP_RMSDET_MAX,
+ }, {
+ .name = "ocxo_temp",
+ .has_max = 1,
+ .sensor = OC2GBTS_TEMP_OCXO,
+ .ee_par = OC2GBTS_PAR_TEMP_OCXO_MAX,
+ }, {
+ .name = "tx_temp",
+ .has_max = 0,
+ .sensor = OC2GBTS_TEMP_TX,
+ .ee_par = OC2GBTS_PAR_TEMP_TX_MAX,
+ }, {
+ .name = "pa_temp",
+ .has_max = 1,
+ .sensor = OC2GBTS_TEMP_PA,
+ .ee_par = OC2GBTS_PAR_TEMP_PA_MAX,
+ }
+};
+
+static const struct {
+ const char *name;
+ int has_max;
+ enum oc2gbts_power_source sensor_source;
+ enum oc2gbts_power_type sensor_type;
+ enum oc2gbts_par ee_par;
+} power_data[] = {
+ {
+ .name = "supply_volt",
+ .has_max = 1,
+ .sensor_source = OC2GBTS_POWER_SUPPLY,
+ .sensor_type = OC2GBTS_POWER_VOLTAGE,
+ .ee_par = OC2GBTS_PAR_VOLT_SUPPLY_MAX,
+ }, {
+ .name = "supply_pwr",
+ .has_max = 1,
+ .sensor_source = OC2GBTS_POWER_SUPPLY,
+ .sensor_type = OC2GBTS_POWER_POWER,
+ .ee_par = OC2GBTS_PAR_PWR_SUPPLY_MAX,
+ }, {
+ .name = "pa_pwr",
+ .has_max = 1,
+ .sensor_source = OC2GBTS_POWER_PA,
+ .sensor_type = OC2GBTS_POWER_POWER,
+ .ee_par = OC2GBTS_PAR_PWR_PA_MAX,
+ }
+};
+
+static const struct {
+ const char *name;
+ int has_max;
+ enum oc2gbts_vswr_sensor sensor;
+ enum oc2gbts_par ee_par;
+} vswr_data[] = {
+ {
+ .name = "vswr",
+ .has_max = 0,
+ .sensor = OC2GBTS_VSWR,
+ .ee_par = OC2GBTS_PAR_VSWR_MAX,
+ }
+};
+
+static const struct value_string power_unit_strs[] = {
+ { OC2GBTS_POWER_POWER, "W" },
+ { OC2GBTS_POWER_VOLTAGE, "V" },
+ { 0, NULL }
+};
+
+void oc2gbts_check_temp(int no_rom_write)
+{
+ int temp_old[ARRAY_SIZE(temp_data)];
+ int temp_cur[ARRAY_SIZE(temp_data)];
+ int i, rc;
+
+ for (i = 0; i < ARRAY_SIZE(temp_data); i++) {
+ int ret = -99;
+
+ if (temp_data[i].sensor == OC2GBTS_TEMP_PA &&
+ !oc2gbts_option_get(OC2GBTS_OPTION_PA_TEMP))
+ continue;
+
+ rc = oc2gbts_par_get_int(temp_data[i].ee_par, &ret);
+ temp_old[i] = ret * 1000;
+
+ oc2gbts_temp_get(temp_data[i].sensor, &temp_cur[i]);
+ if (temp_cur[i] < 0 && temp_cur[i] > -1000) {
+ LOGP(DTEMP, LOGL_ERROR, "Error reading temperature (%d)\n", temp_data[i].sensor);
+ continue;
+ }
+
+ LOGP(DTEMP, LOGL_DEBUG, "Current %s temperature: %d.%d C\n",
+ temp_data[i].name, temp_cur[i]/1000, temp_cur[i]%1000);
+
+ if (temp_cur[i] > temp_old[i]) {
+ LOGP(DTEMP, LOGL_NOTICE, "New maximum %s "
+ "temperature: %d.%d C\n", temp_data[i].name,
+ temp_cur[i]/1000, temp_old[i]%1000);
+
+ if (!no_rom_write) {
+ rc = oc2gbts_par_set_int(temp_data[i].ee_par,
+ temp_cur[i]/1000);
+ if (rc < 0)
+ LOGP(DTEMP, LOGL_ERROR, "error writing new %s "
+ "max temp %d (%s)\n", temp_data[i].name,
+ rc, strerror(errno));
+ }
+ }
+ }
+}
+
+void oc2gbts_check_power(int no_rom_write)
+{
+ int power_old[ARRAY_SIZE(power_data)];
+ int power_cur[ARRAY_SIZE(power_data)];
+ int i, rc;
+ int div_ratio;
+
+ for (i = 0; i < ARRAY_SIZE(power_data); i++) {
+ int ret = 0;
+
+ if (power_data[i].sensor_source == OC2GBTS_POWER_PA &&
+ !oc2gbts_option_get(OC2GBTS_OPTION_PA))
+ continue;
+
+ rc = oc2gbts_par_get_int(power_data[i].ee_par, &ret);
+ switch(power_data[i].sensor_type) {
+ case OC2GBTS_POWER_VOLTAGE:
+ div_ratio = 1000;
+ break;
+ case OC2GBTS_POWER_POWER:
+ div_ratio = 1000000;
+ break;
+ default:
+ div_ratio = 1000;
+ }
+ power_old[i] = ret * div_ratio;
+
+ oc2gbts_power_sensor_get(power_data[i].sensor_source, power_data[i].sensor_type, &power_cur[i]);
+ if (power_cur[i] < 0 && power_cur[i] > -1000) {
+ LOGP(DTEMP, LOGL_ERROR, "Error reading power (%d) (%d)\n", power_data[i].sensor_source, power_data[i].sensor_type);
+ continue;
+ }
+ LOGP(DTEMP, LOGL_DEBUG, "Current %s power: %d.%d %s\n",
+ power_data[i].name, power_cur[i]/div_ratio, power_cur[i]%div_ratio,
+ get_value_string(power_unit_strs, power_data[i].sensor_type));
+
+ if (power_cur[i] > power_old[i]) {
+ LOGP(DTEMP, LOGL_NOTICE, "New maximum %s "
+ "power: %d.%d %s\n", power_data[i].name,
+ power_cur[i]/div_ratio, power_cur[i]%div_ratio,
+ get_value_string(power_unit_strs, power_data[i].sensor_type));
+
+ if (!no_rom_write) {
+ rc = oc2gbts_par_set_int(power_data[i].ee_par,
+ power_cur[i]/div_ratio);
+ if (rc < 0)
+ LOGP(DTEMP, LOGL_ERROR, "error writing new %s "
+ "max power %d (%s)\n", power_data[i].name,
+ rc, strerror(errno));
+ }
+ }
+ }
+}
+
+void oc2gbts_check_vswr(int no_rom_write)
+{
+ int vswr_old[ARRAY_SIZE(vswr_data)];
+ int vswr_cur[ARRAY_SIZE(vswr_data)];
+ int i, rc;
+
+ for (i = 0; i < ARRAY_SIZE(vswr_data); i++) {
+ int ret = 0;
+
+ if (vswr_data[i].sensor == OC2GBTS_VSWR &&
+ (!oc2gbts_option_get(OC2GBTS_OPTION_RMS_FWD) ||
+ !oc2gbts_option_get(OC2GBTS_OPTION_RMS_REFL)))
+ continue;
+
+ rc = oc2gbts_par_get_int(vswr_data[i].ee_par, &ret);
+ vswr_old[i] = ret * 1000;
+
+ oc2gbts_vswr_get(vswr_data[i].sensor, &vswr_cur[i]);
+ if (vswr_cur[i] < 0 && vswr_cur[i] > -1000) {
+ LOGP(DTEMP, LOGL_ERROR, "Error reading vswr (%d)\n", vswr_data[i].sensor);
+ continue;
+ }
+
+ LOGP(DTEMP, LOGL_DEBUG, "Current %s vswr: %d.%d\n",
+ vswr_data[i].name, vswr_cur[i]/1000, vswr_cur[i]%1000);
+
+ if (vswr_cur[i] > vswr_old[i]) {
+ LOGP(DTEMP, LOGL_NOTICE, "New maximum %s "
+ "vswr: %d.%d C\n", vswr_data[i].name,
+ vswr_cur[i]/1000, vswr_old[i]%1000);
+
+ if (!no_rom_write) {
+ rc = oc2gbts_par_set_int(vswr_data[i].ee_par,
+ vswr_cur[i]/1000);
+ if (rc < 0)
+ LOGP(DTEMP, LOGL_ERROR, "error writing new %s "
+ "max vswr %d (%s)\n", vswr_data[i].name,
+ rc, strerror(errno));
+ }
+ }
+ }
+}
+
+/*********************************************************************
+ * Hours handling
+ *********************************************************************/
+static time_t last_update;
+
+int oc2gbts_update_hours(int no_rom_write)
+{
+ time_t now = time(NULL);
+ int rc, op_hrs = 0;
+
+ /* first time after start of manager program */
+ if (last_update == 0) {
+ last_update = now;
+
+ rc = oc2gbts_par_get_int(OC2GBTS_PAR_HOURS, &op_hrs);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR, "Unable to read "
+ "operational hours: %d (%s)\n", rc,
+ strerror(errno));
+ /* create a new file anyway */
+ if (!no_rom_write)
+ rc = oc2gbts_par_set_int(OC2GBTS_PAR_HOURS, op_hrs);
+
+ return rc;
+ }
+
+ LOGP(DTEMP, LOGL_INFO, "Total hours of Operation: %u\n",
+ op_hrs);
+
+ return 0;
+ }
+
+ if (now >= last_update + 3600) {
+ rc = oc2gbts_par_get_int(OC2GBTS_PAR_HOURS, &op_hrs);
+ if (rc < 0) {
+ LOGP(DTEMP, LOGL_ERROR, "Unable to read "
+ "operational hours: %d (%s)\n", rc,
+ strerror(errno));
+ return rc;
+ }
+
+ /* number of hours to increase */
+ op_hrs += (now-last_update)/3600;
+
+ LOGP(DTEMP, LOGL_INFO, "Total hours of Operation: %u\n",
+ op_hrs);
+
+ if (!no_rom_write) {
+ rc = oc2gbts_par_set_int(OC2GBTS_PAR_HOURS, op_hrs);
+ if (rc < 0)
+ return rc;
+ }
+
+ last_update = now;
+ }
+
+ return 0;
+}
+
+/*********************************************************************
+ * Firmware reloading
+ *********************************************************************/
+
+static const char *fw_sysfs[_NUM_FW] = {
+ [OC2GBTS_FW_DSP] = "/sys/kernel/debug/remoteproc/remoteproc0/recovery",
+};
+
+int oc2gbts_firmware_reload(enum oc2gbts_firmware_type type)
+{
+ int fd;
+ int rc;
+
+ switch (type) {
+ case OC2GBTS_FW_DSP:
+ fd = open(fw_sysfs[type], O_WRONLY);
+ if (fd < 0) {
+ LOGP(DFW, LOGL_ERROR, "unable ot open firmware device %s: %s\n",
+ fw_sysfs[type], strerror(errno));
+ close(fd);
+ return fd;
+ }
+ rc = write(fd, "restart", 8);
+ if (rc < 8) {
+ LOGP(DFW, LOGL_ERROR, "short write during "
+ "fw write to %s\n", fw_sysfs[type]);
+ close(fd);
+ return -EIO;
+ }
+ close(fd);
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_misc.h b/src/osmo-bts-oc2g/misc/oc2gbts_misc.h
new file mode 100644
index 00000000..78315679
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_misc.h
@@ -0,0 +1,17 @@
+#ifndef _OC2GBTS_MISC_H
+#define _OC2GBTS_MISC_H
+
+#include <stdint.h>
+
+void oc2gbts_check_temp(int no_rom_write);
+void oc2gbts_check_power(int no_rom_write);
+void oc2gbts_check_vswr(int no_rom_write);
+
+int oc2gbts_update_hours(int no_rom_write);
+
+enum oc2gbts_firmware_type {
+ OC2GBTS_FW_DSP,
+ _NUM_FW
+};
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_nl.c b/src/osmo-bts-oc2g/misc/oc2gbts_nl.c
new file mode 100644
index 00000000..39f64aae
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_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-oc2g/misc/oc2gbts_nl.h b/src/osmo-bts-oc2g/misc/oc2gbts_nl.h
new file mode 100644
index 00000000..340cf117
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_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-oc2g/misc/oc2gbts_par.c b/src/osmo-bts-oc2g/misc/oc2gbts_par.c
new file mode 100644
index 00000000..f3550243
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_par.c
@@ -0,0 +1,249 @@
+/* oc2gbts - access to hardware related parameters */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_par.c
+ * (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/talloc.h>
+
+#include "oc2gbts_par.h"
+
+const struct value_string oc2gbts_par_names[_NUM_OC2GBTS_PAR+1] = {
+ { OC2GBTS_PAR_TEMP_SUPPLY_MAX, "temp-supply-max" },
+ { OC2GBTS_PAR_TEMP_SOC_MAX, "temp-soc-max" },
+ { OC2GBTS_PAR_TEMP_FPGA_MAX, "temp-fpga-max" },
+ { OC2GBTS_PAR_TEMP_RMSDET_MAX, "temp-rmsdet-max" },
+ { OC2GBTS_PAR_TEMP_OCXO_MAX, "temp-ocxo-max" },
+ { OC2GBTS_PAR_TEMP_TX_MAX, "temp-tx-max" },
+ { OC2GBTS_PAR_TEMP_PA_MAX, "temp-pa-max" },
+ { OC2GBTS_PAR_VOLT_SUPPLY_MAX, "volt-supply-max" },
+ { OC2GBTS_PAR_PWR_SUPPLY_MAX, "pwr-supply-max" },
+ { OC2GBTS_PAR_PWR_PA_MAX, "pwr-pa-max" },
+ { OC2GBTS_PAR_VSWR_MAX, "vswr-max" },
+ { OC2GBTS_PAR_GPS_FIX, "gps-fix" },
+ { OC2GBTS_PAR_SERNR, "serial-nr" },
+ { OC2GBTS_PAR_HOURS, "hours-running" },
+ { OC2GBTS_PAR_BOOTS, "boot-count" },
+ { OC2GBTS_PAR_KEY, "key" },
+ { 0, NULL }
+};
+
+int oc2gbts_par_is_int(enum oc2gbts_par par)
+{
+ switch (par) {
+ case OC2GBTS_PAR_TEMP_SUPPLY_MAX:
+ case OC2GBTS_PAR_TEMP_SOC_MAX:
+ case OC2GBTS_PAR_TEMP_FPGA_MAX:
+ case OC2GBTS_PAR_TEMP_RMSDET_MAX:
+ case OC2GBTS_PAR_TEMP_OCXO_MAX:
+ case OC2GBTS_PAR_TEMP_TX_MAX:
+ case OC2GBTS_PAR_TEMP_PA_MAX:
+ case OC2GBTS_PAR_VOLT_SUPPLY_MAX:
+ case OC2GBTS_PAR_VSWR_MAX:
+ case OC2GBTS_PAR_SERNR:
+ case OC2GBTS_PAR_HOURS:
+ case OC2GBTS_PAR_BOOTS:
+ case OC2GBTS_PAR_PWR_SUPPLY_MAX:
+ case OC2GBTS_PAR_PWR_PA_MAX:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+FILE *oc2gbts_par_get_path(void *ctx, enum oc2gbts_par par, const char* mode)
+{
+ char *fpath;
+ FILE *fp;
+
+ if (par >= _NUM_OC2GBTS_PAR)
+ return NULL;
+
+ fpath = talloc_asprintf(ctx, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par));
+ if (!fpath)
+ return NULL;
+
+ fp = fopen(fpath, mode);
+ if (!fp)
+ fprintf(stderr, "Failed to open %s due to '%s' error\n", fpath, strerror(errno));
+
+ talloc_free(fpath);
+
+ return fp;
+}
+
+int oc2gbts_par_get_int(enum oc2gbts_par par, int *ret)
+{
+ char fpath[PATH_MAX];
+ FILE *fp;
+ int rc;
+
+ if (par >= _NUM_OC2GBTS_PAR)
+ return -ENODEV;
+
+ snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par));
+ fpath[sizeof(fpath)-1] = '\0';
+
+ fp = fopen(fpath, "r");
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fscanf(fp, "%d", ret);
+ if (rc != 1) {
+ fclose(fp);
+ return -EIO;
+ }
+ fclose(fp);
+ return 0;
+}
+
+int oc2gbts_par_set_int(enum oc2gbts_par par, int val)
+{
+ char fpath[PATH_MAX];
+ FILE *fp;
+ int rc;
+
+ if (par >= _NUM_OC2GBTS_PAR)
+ return -ENODEV;
+
+ snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par));
+ fpath[sizeof(fpath)-1] = '\0';
+
+ fp = fopen(fpath, "w");
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fprintf(fp, "%d", val);
+ if (rc < 0) {
+ fclose(fp);
+ return -EIO;
+ }
+ fsync(fp);
+ fclose(fp);
+ return 0;
+}
+
+int oc2gbts_par_get_buf(enum oc2gbts_par par, uint8_t *buf,
+ unsigned int size)
+{
+ char fpath[PATH_MAX];
+ FILE *fp;
+ int rc;
+
+ if (par >= _NUM_OC2GBTS_PAR)
+ return -ENODEV;
+
+ snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par));
+ fpath[sizeof(fpath)-1] = '\0';
+
+ fp = fopen(fpath, "rb");
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fread(buf, 1, size, fp);
+
+ fclose(fp);
+
+ return rc;
+}
+
+int oc2gbts_par_set_buf(enum oc2gbts_par par, const uint8_t *buf,
+ unsigned int size)
+{
+ char fpath[PATH_MAX];
+ FILE *fp;
+ int rc;
+
+ if (par >= _NUM_OC2GBTS_PAR)
+ return -ENODEV;
+
+ snprintf(fpath, sizeof(fpath)-1, "%s/%s", USER_ROM_PATH, get_value_string(oc2gbts_par_names, par));
+ fpath[sizeof(fpath)-1] = '\0';
+
+ fp = fopen(fpath, "wb");
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fwrite(buf, 1, size, fp);
+
+ fsync(fp);
+ fclose(fp);
+
+ return rc;
+}
+
+int oc2gbts_par_get_gps_fix(void *ctx, time_t *ret)
+{
+ FILE *fp;
+ int rc;
+
+ fp = oc2gbts_par_get_path(ctx, OC2GBTS_PAR_GPS_FIX, "r");
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fscanf(fp, "%ld", ret);
+ if (rc != 1) {
+ fclose(fp);
+ return -EIO;
+ }
+ fclose(fp);
+
+ return 0;
+}
+
+int oc2gbts_par_set_gps_fix(void *ctx, time_t val)
+{
+ FILE *fp;
+ int rc;
+
+ fp = oc2gbts_par_get_path(ctx, OC2GBTS_PAR_GPS_FIX, "w");
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ rc = fprintf(fp, "%ld", val);
+ if (rc < 0) {
+ fclose(fp);
+ return -EIO;
+ }
+ fsync(fp);
+ fclose(fp);
+
+ return 0;
+}
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_par.h b/src/osmo-bts-oc2g/misc/oc2gbts_par.h
new file mode 100644
index 00000000..588a3c32
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_par.h
@@ -0,0 +1,43 @@
+#ifndef _OC2GBTS_PAR_H
+#define _OC2GBTS_PAR_H
+
+#include <osmocom/core/utils.h>
+
+#define FACTORY_ROM_PATH "/mnt/rom/factory"
+#define USER_ROM_PATH "/var/run/oc2gbts-mgr"
+#define UPTIME_TMP_PATH "/tmp/uptime"
+
+enum oc2gbts_par {
+ OC2GBTS_PAR_TEMP_SUPPLY_MAX,
+ OC2GBTS_PAR_TEMP_SOC_MAX,
+ OC2GBTS_PAR_TEMP_FPGA_MAX,
+ OC2GBTS_PAR_TEMP_RMSDET_MAX,
+ OC2GBTS_PAR_TEMP_OCXO_MAX,
+ OC2GBTS_PAR_TEMP_TX_MAX,
+ OC2GBTS_PAR_TEMP_PA_MAX,
+ OC2GBTS_PAR_VOLT_SUPPLY_MAX,
+ OC2GBTS_PAR_PWR_SUPPLY_MAX,
+ OC2GBTS_PAR_PWR_PA_MAX,
+ OC2GBTS_PAR_VSWR_MAX,
+ OC2GBTS_PAR_GPS_FIX,
+ OC2GBTS_PAR_SERNR,
+ OC2GBTS_PAR_HOURS,
+ OC2GBTS_PAR_BOOTS,
+ OC2GBTS_PAR_KEY,
+ _NUM_OC2GBTS_PAR
+};
+
+extern const struct value_string oc2gbts_par_names[_NUM_OC2GBTS_PAR+1];
+
+int oc2gbts_par_get_int(enum oc2gbts_par par, int *ret);
+int oc2gbts_par_set_int(enum oc2gbts_par par, int val);
+int oc2gbts_par_get_buf(enum oc2gbts_par par, uint8_t *buf,
+ unsigned int size);
+int oc2gbts_par_set_buf(enum oc2gbts_par par, const uint8_t *buf,
+ unsigned int size);
+
+int oc2gbts_par_is_int(enum oc2gbts_par par);
+int oc2gbts_par_get_gps_fix(void *ctx, time_t *ret);
+int oc2gbts_par_set_gps_fix(void *ctx, time_t val);
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_power.c b/src/osmo-bts-oc2g/misc/oc2gbts_power.c
new file mode 100644
index 00000000..4e2fc95a
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_power.c
@@ -0,0 +1,177 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include "oc2gbts_power.h"
+
+static const char *power_enable_devs[_NUM_POWER_SOURCES] = {
+ [OC2GBTS_POWER_PA] = "/var/oc2g/pa-state/pa0/state",
+};
+
+static const char *power_sensor_devs[_NUM_POWER_SOURCES] = {
+ [OC2GBTS_POWER_SUPPLY] = "/var/oc2g/pwr-sense/main-supply/",
+ [OC2GBTS_POWER_PA] = "/var/oc2g/pwr-sense/pa0/",
+};
+
+static const char *power_sensor_type_str[_NUM_POWER_TYPES] = {
+ [OC2GBTS_POWER_POWER] = "power",
+ [OC2GBTS_POWER_VOLTAGE] = "voltage",
+ [OC2GBTS_POWER_CURRENT] = "current",
+};
+
+int oc2gbts_power_sensor_get(
+ enum oc2gbts_power_source source,
+ enum oc2gbts_power_type type,
+ int *power)
+{
+ char buf[PATH_MAX];
+ char pwrstr[10];
+ int fd, rc;
+
+ if (source >= _NUM_POWER_SOURCES)
+ return -EINVAL;
+
+ if (type >= _NUM_POWER_TYPES)
+ return -EINVAL;
+
+ snprintf(buf, sizeof(buf)-1, "%s%s", power_sensor_devs[source], power_sensor_type_str[type]);
+ buf[sizeof(buf)-1] = '\0';
+
+ fd = open(buf, O_RDONLY);
+ if (fd < 0)
+ return fd;
+
+ rc = read(fd, pwrstr, sizeof(pwrstr));
+ pwrstr[sizeof(pwrstr)-1] = '\0';
+ if (rc < 0) {
+ close(fd);
+ return rc;
+ }
+ if (rc == 0) {
+ close(fd);
+ return -EIO;
+ }
+ close(fd);
+ *power = atoi(pwrstr);
+ return 0;
+}
+
+
+int oc2gbts_power_set(
+ enum oc2gbts_power_source source,
+ int en)
+{
+ int fd;
+ int rc;
+
+ if (source != OC2GBTS_POWER_PA) {
+ return -EINVAL;
+ }
+
+ fd = open(power_enable_devs[source], O_WRONLY);
+ if (fd < 0) {
+ return fd;
+ }
+ rc = write(fd, en?"1":"0", 2);
+ close( fd );
+
+ if (rc != 2) {
+ return -1;
+ }
+
+ if (en) usleep(50*1000);
+
+ return 0;
+}
+
+int oc2gbts_power_get(
+ enum oc2gbts_power_source source)
+{
+ int fd;
+ int rc;
+ int retVal = 0;
+ char enstr[10];
+
+ fd = open(power_enable_devs[source], O_RDONLY);
+ if (fd < 0) {
+ return fd;
+ }
+
+ rc = read(fd, enstr, sizeof(enstr));
+ enstr[rc-1] = '\0';
+
+ close(fd);
+
+ if (rc < 0) {
+ return rc;
+ }
+ if (rc == 0) {
+ return -EIO;
+ }
+
+ rc = strcmp(enstr, "enabled");
+ if(rc == 0) {
+ retVal = 1;
+ }
+
+ return retVal;
+}
+
+static const char *vswr_devs[_NUM_VSWR_SENSORS] = {
+ [OC2GBTS_VSWR] = "/var/oc2g/vswr/tx0/vswr",
+};
+
+int oc2gbts_vswr_get(enum oc2gbts_vswr_sensor sensor, int *vswr)
+{
+ char buf[PATH_MAX];
+ char vswrstr[8];
+ int fd, rc;
+
+ if (sensor < 0 || sensor >= _NUM_VSWR_SENSORS)
+ return -EINVAL;
+
+ snprintf(buf, sizeof(buf)-1, "%s", vswr_devs[sensor]);
+ buf[sizeof(buf)-1] = '\0';
+
+ fd = open(buf, O_RDONLY);
+ if (fd < 0)
+ return fd;
+
+ rc = read(fd, vswrstr, sizeof(vswrstr));
+ vswrstr[sizeof(vswrstr)-1] = '\0';
+ if (rc < 0) {
+ close(fd);
+ return rc;
+ }
+ if (rc == 0) {
+ close(fd);
+ return -EIO;
+ }
+ close(fd);
+ *vswr = atoi(vswrstr);
+ return 0;
+}
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_power.h b/src/osmo-bts-oc2g/misc/oc2gbts_power.h
new file mode 100644
index 00000000..3229f243
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_power.h
@@ -0,0 +1,36 @@
+#ifndef _OC2GBTS_POWER_H
+#define _OC2GBTS_POWER_H
+
+enum oc2gbts_power_source {
+ OC2GBTS_POWER_SUPPLY,
+ OC2GBTS_POWER_PA,
+ _NUM_POWER_SOURCES
+};
+
+enum oc2gbts_power_type {
+ OC2GBTS_POWER_POWER,
+ OC2GBTS_POWER_VOLTAGE,
+ OC2GBTS_POWER_CURRENT,
+ _NUM_POWER_TYPES
+};
+
+int oc2gbts_power_sensor_get(
+ enum oc2gbts_power_source source,
+ enum oc2gbts_power_type type,
+ int *volt);
+
+int oc2gbts_power_set(
+ enum oc2gbts_power_source source,
+ int en);
+
+int oc2gbts_power_get(
+ enum oc2gbts_power_source source);
+
+enum oc2gbts_vswr_sensor {
+ OC2GBTS_VSWR,
+ _NUM_VSWR_SENSORS
+};
+
+int oc2gbts_vswr_get(enum oc2gbts_vswr_sensor sensor, int *vswr);
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_swd.c b/src/osmo-bts-oc2g/misc/oc2gbts_swd.c
new file mode 100644
index 00000000..59b795ac
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_swd.c
@@ -0,0 +1,178 @@
+/* Systemd service wd notification for OC-2G BTS management daemon */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "misc/oc2gbts_mgr.h"
+#include "misc/oc2gbts_swd.h"
+#include <osmocom/core/logging.h>
+
+/* Needed for service watchdog notification */
+#include <systemd/sd-daemon.h>
+
+/* This is the period used to verify if all events have been registered to be allowed
+ to notify the systemd service watchdog
+*/
+#define SWD_PERIOD 30
+
+static void swd_start(struct oc2gbts_mgr_instance *mgr);
+static void swd_process(struct oc2gbts_mgr_instance *mgr);
+static void swd_close(struct oc2gbts_mgr_instance *mgr);
+static void swd_state_reset(struct oc2gbts_mgr_instance *mgr, int reason);
+static int swd_run(struct oc2gbts_mgr_instance *mgr, int from_loop);
+static void swd_loop_run(void *_data);
+
+enum swd_state {
+ SWD_INITIAL,
+ SWD_IN_PROGRESS,
+};
+
+enum swd_result {
+ SWD_FAIL_START,
+ SWD_FAIL_NOTIFY,
+ SWD_SUCCESS,
+};
+
+static void swd_start(struct oc2gbts_mgr_instance *mgr)
+{
+ swd_process(mgr);
+}
+
+static void swd_process(struct oc2gbts_mgr_instance *mgr)
+{
+ int rc = 0, notify = 0;
+
+ /* Did we get all needed conditions ? */
+ if (mgr->swd.swd_eventmasks == mgr->swd.swd_events) {
+ /* Ping systemd service wd if enabled */
+ rc = sd_notify(0, "WATCHDOG=1");
+ LOGP(DSWD, LOGL_INFO, "Watchdog notification attempt\n");
+ notify = 1;
+ }
+ else {
+ LOGP(DSWD, LOGL_INFO, "Missing watchdog events: e:0x%016llx,m:0x%016llx\n",mgr->swd.swd_events,mgr->swd.swd_eventmasks);
+ }
+
+ if (rc < 0) {
+ LOGP(DSWD, LOGL_ERROR,
+ "Failed to notify system service watchdog: %d\n", rc);
+ swd_state_reset(mgr, SWD_FAIL_NOTIFY);
+ return;
+ }
+ else {
+ /* Did we notified the watchdog? */
+ if (notify) {
+ mgr->swd.swd_events = 0;
+ /* Makes sure we really cleared it in case any event was notified at this same moment (it would be lost) */
+ if (mgr->swd.swd_events != 0)
+ mgr->swd.swd_events = 0;
+ }
+ }
+
+ swd_state_reset(mgr, SWD_SUCCESS);
+ return;
+}
+
+static void swd_close(struct oc2gbts_mgr_instance *mgr)
+{
+}
+
+static void swd_state_reset(struct oc2gbts_mgr_instance *mgr, int outcome)
+{
+ if (mgr->swd.swd_from_loop) {
+ mgr->swd.swd_timeout.data = mgr;
+ mgr->swd.swd_timeout.cb = swd_loop_run;
+ osmo_timer_schedule(&mgr->swd.swd_timeout, SWD_PERIOD, 0);
+ }
+
+ mgr->swd.state = SWD_INITIAL;
+ swd_close(mgr);
+}
+
+static int swd_run(struct oc2gbts_mgr_instance *mgr, int from_loop)
+{
+ if (mgr->swd.state != SWD_INITIAL) {
+ LOGP(DSWD, LOGL_ERROR, "Swd is already in progress.\n");
+ return -1;
+ }
+
+ mgr->swd.swd_from_loop = from_loop;
+
+ /* From now on everything will be handled from the failure */
+ mgr->swd.state = SWD_IN_PROGRESS;
+ swd_start(mgr);
+ return 0;
+}
+
+static void swd_loop_run(void *_data)
+{
+ int rc;
+ struct oc2gbts_mgr_instance *mgr = _data;
+
+ LOGP(DSWD, LOGL_INFO, "Going to check for watchdog notification.\n");
+ rc = swd_run(mgr, 1);
+ if (rc != 0) {
+ swd_state_reset(mgr, SWD_FAIL_START);
+ }
+}
+
+/* 'swd_num_events' configures the number of events to be monitored before notifying the
+ systemd service watchdog. It must be in the range of [1,64]. Events are notified
+ through the function 'oc2gbts_swd_event'
+*/
+int oc2gbts_swd_init(struct oc2gbts_mgr_instance *mgr, int swd_num_events)
+{
+ /* Checks for a valid number of events to validate */
+ if (swd_num_events < 1 || swd_num_events > 64)
+ return(-1);
+
+ mgr->swd.state = SWD_INITIAL;
+ mgr->swd.swd_timeout.data = mgr;
+ mgr->swd.swd_timeout.cb = swd_loop_run;
+ osmo_timer_schedule(&mgr->swd.swd_timeout, 0, 0);
+
+ if (swd_num_events == 64){
+ mgr->swd.swd_eventmasks = 0xffffffffffffffffULL;
+ }
+ else {
+ mgr->swd.swd_eventmasks = ((1ULL << swd_num_events) - 1);
+ }
+ mgr->swd.swd_events = 0;
+ mgr->swd.num_events = swd_num_events;
+
+ return 0;
+}
+
+/* Notifies that the specified event 'swd_event' happened correctly;
+ the value must be in the range of [0,'swd_num_events'[ (see oc2gbts_swd_init).
+ For example, if 'swd_num_events' was 64, 'swd_event' events are numbered 0 to 63.
+ WARNING: if this function can be used from multiple threads at the same time,
+ it must be protected with a kind of mutex to avoid loosing event notification.
+*/
+int oc2gbts_swd_event(struct oc2gbts_mgr_instance *mgr, enum mgr_swd_events swd_event)
+{
+ /* Checks for a valid specified event (smaller than max possible) */
+ if ((int)(swd_event) < 0 || (int)(swd_event) >= mgr->swd.num_events)
+ return(-1);
+
+ mgr->swd.swd_events = mgr->swd.swd_events | ((unsigned long long int)(1) << (int)(swd_event));
+
+ /* !!! Uncomment following line to debug events notification */
+ LOGP(DSWD, LOGL_DEBUG,"Swd event notified: %d\n", (int)(swd_event));
+
+ return 0;
+}
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_swd.h b/src/osmo-bts-oc2g/misc/oc2gbts_swd.h
new file mode 100644
index 00000000..313712ac
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_swd.h
@@ -0,0 +1,7 @@
+#ifndef _OC2GBTS_SWD_H
+#define _OC2GBTS_SWD_H
+
+int oc2gbts_swd_init(struct oc2gbts_mgr_instance *mgr, int swd_num_events);
+int oc2gbts_swd_event(struct oc2gbts_mgr_instance *mgr, enum mgr_swd_events swd_event);
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_temp.c b/src/osmo-bts-oc2g/misc/oc2gbts_temp.c
new file mode 100644
index 00000000..8425dda3
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_temp.c
@@ -0,0 +1,71 @@
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include <osmocom/core/utils.h>
+
+#include "oc2gbts_temp.h"
+
+static const char *temp_devs[_NUM_TEMP_SENSORS] = {
+ [OC2GBTS_TEMP_SUPPLY] = "/var/oc2g/temp/main-supply/temp",
+ [OC2GBTS_TEMP_SOC] = "/var/oc2g/temp/cpu/temp",
+ [OC2GBTS_TEMP_FPGA] = "/var/oc2g/temp/fpga/temp",
+ [OC2GBTS_TEMP_RMSDET] = "/var/oc2g/temp/rmsdet/temp",
+ [OC2GBTS_TEMP_OCXO] = "/var/oc2g/temp/ocxo/temp",
+ [OC2GBTS_TEMP_TX] = "/var/oc2g/temp/tx0/temp",
+ [OC2GBTS_TEMP_PA] = "/var/oc2g/temp/pa0/temp",
+};
+
+int oc2gbts_temp_get(enum oc2gbts_temp_sensor sensor, int *temp)
+{
+ char buf[PATH_MAX];
+ char tempstr[8];
+ int fd, rc;
+
+ if (sensor < 0 || sensor >= _NUM_TEMP_SENSORS)
+ return -EINVAL;
+
+ snprintf(buf, sizeof(buf)-1, "%s", temp_devs[sensor]);
+ buf[sizeof(buf)-1] = '\0';
+
+ fd = open(buf, O_RDONLY);
+ if (fd < 0)
+ return fd;
+
+ rc = read(fd, tempstr, sizeof(tempstr));
+ tempstr[sizeof(tempstr)-1] = '\0';
+ if (rc < 0) {
+ close(fd);
+ return rc;
+ }
+ if (rc == 0) {
+ close(fd);
+ return -EIO;
+ }
+ close(fd);
+ *temp = atoi(tempstr);
+ return 0;
+}
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_temp.h b/src/osmo-bts-oc2g/misc/oc2gbts_temp.h
new file mode 100644
index 00000000..6d5dfca8
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_temp.h
@@ -0,0 +1,26 @@
+#ifndef _OC2GBTS_TEMP_H
+#define _OC2GBTS_TEMP_H
+
+enum oc2gbts_temp_sensor {
+ OC2GBTS_TEMP_SUPPLY,
+ OC2GBTS_TEMP_SOC,
+ OC2GBTS_TEMP_FPGA,
+ OC2GBTS_TEMP_RMSDET,
+ OC2GBTS_TEMP_OCXO,
+ OC2GBTS_TEMP_TX,
+ OC2GBTS_TEMP_PA,
+ _NUM_TEMP_SENSORS
+};
+
+enum oc2gbts_temp_type {
+ OC2GBTS_TEMP_INPUT,
+ OC2GBTS_TEMP_LOWEST,
+ OC2GBTS_TEMP_HIGHEST,
+ OC2GBTS_TEMP_FAULT,
+ _NUM_TEMP_TYPES
+};
+
+int oc2gbts_temp_get(enum oc2gbts_temp_sensor sensor, int *temp);
+
+
+#endif
diff --git a/src/osmo-bts-oc2g/misc/oc2gbts_util.c b/src/osmo-bts-oc2g/misc/oc2gbts_util.c
new file mode 100644
index 00000000..b71f0383
--- /dev/null
+++ b/src/osmo-bts-oc2g/misc/oc2gbts_util.c
@@ -0,0 +1,158 @@
+/* oc2gbts-util - access to hardware related parameters */
+
+/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
+ *
+ * Based on sysmoBTS:
+ * sysmobts_misc.c
+ * (C) 2012-2013 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+
+
+#include "oc2gbts_par.h"
+
+enum act {
+ ACT_GET,
+ ACT_SET,
+};
+
+static enum act action;
+static char *write_arg;
+static int void_warranty;
+
+static void print_help()
+{
+ const struct value_string *par = oc2gbts_par_names;
+
+ printf("oc2gbts-util [--void-warranty -r | -w value] param_name\n");
+ printf("Possible param names:\n");
+
+ for (; par->str != NULL; par += 1) {
+ if (!oc2gbts_par_is_int(par->value))
+ continue;
+ printf(" %s\n", par->str);
+ }
+}
+
+static int parse_options(int argc, char **argv)
+{
+ while (1) {
+ int option_idx = 0, c;
+ static const struct option long_options[] = {
+ { "help", 0, 0, 'h' },
+ { "read", 0, 0, 'r' },
+ { "void-warranty", 0, 0, 1000},
+ { "write", 1, 0, 'w' },
+ { 0, 0, 0, 0 }
+ };
+
+ c = getopt_long(argc, argv, "rw:h",
+ long_options, &option_idx);
+ if (c == -1)
+ break;
+ switch (c) {
+ case 'r':
+ action = ACT_GET;
+ break;
+ case 'w':
+ action = ACT_SET;
+ write_arg = optarg;
+ break;
+ case 'h':
+ print_help();
+ return -1;
+ break;
+ case 1000:
+ printf("Will void warranty on write.\n");
+ void_warranty = 1;
+ break;
+ default:
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ const char *parname;
+ enum oc2gbts_par par;
+ int rc, val;
+
+ rc = parse_options(argc, argv);
+ if (rc < 0)
+ exit(2);
+
+ if (optind >= argc) {
+ fprintf(stderr, "You must specify the parameter name\n");
+ exit(2);
+ }
+ parname = argv[optind];
+
+ rc = get_string_value(oc2gbts_par_names, parname);
+ if (rc < 0) {
+ fprintf(stderr, "`%s' is not a valid parameter\n", parname);
+ exit(2);
+ } else
+ par = rc;
+
+ switch (action) {
+ case ACT_GET:
+ rc = oc2gbts_par_get_int(par, &val);
+ if (rc < 0) {
+ fprintf(stderr, "Error %d\n", rc);
+ goto err;
+ }
+ printf("%d\n", val);
+ break;
+ case ACT_SET:
+ rc = oc2gbts_par_get_int(par, &val);
+ if (rc < 0) {
+ fprintf(stderr, "Error %d\n", rc);
+ goto err;
+ }
+ if (val != 0xFFFF && val != 0xFF && val != 0xFFFFFFFF && !void_warranty) {
+ fprintf(stderr, "Parameter is already set!\r\n");
+ goto err;
+ }
+ rc = oc2gbts_par_set_int(par, atoi(write_arg));
+ if (rc < 0) {
+ fprintf(stderr, "Error %d\n", rc);
+ goto err;
+ }
+ printf("Success setting %s=%d\n", parname,
+ atoi(write_arg));
+ break;
+ default:
+ fprintf(stderr, "Unsupported action\n");
+ goto err;
+ }
+
+ exit(0);
+
+err:
+ exit(1);
+}
+