diff options
author | Omar Ramadan <oramadan@fb.com> | 2018-10-23 15:42:46 -0700 |
---|---|---|
committer | Harald Welte <laforge@gnumonks.org> | 2018-10-27 11:35:58 +0000 |
commit | 9c75c387c04175298d7fae3dd432d84bd622fccd (patch) | |
tree | 5275a426f7aa23564b82d4bb7f434aa46219734d /src/osmo-bts-oc2g/misc | |
parent | ee9e8e9eb22257000765f4c6a5138fc0200e864f (diff) |
Add OC-2G BTS sources
Change-Id: I327384fe5ac944dc3996a3f00932d6f1a10d5a35
Diffstat (limited to 'src/osmo-bts-oc2g/misc')
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, ¤t); + 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, ¤t); + 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); +} + |