/* OCXO/TCXO calibration control for SysmoBTS management daemon */
/*
* (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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
*/
#include "misc/sysmobts_mgr.h"
#include "misc/sysmobts_misc.h"
#include "osmo-bts/msg_utils.h"
#include
#include
#include
#include
#include
#include
#include
#include
static int calib_run(struct sysmobts_mgr_instance *mgr, int from_loop);
static void calib_state_reset(struct sysmobts_mgr_instance *mgr, int reason);
static void request_clock_reset(struct sysmobts_mgr_instance *mgr);
static void bts_updown_cb(struct ipa_client_conn *link, int up);
enum calib_state {
CALIB_INITIAL,
CALIB_GPS_WAIT_FOR_FIX,
CALIB_CTR_RESET,
CALIB_CTR_WAIT,
CALIB_COR_SET,
};
enum calib_result {
CALIB_FAIL_START,
CALIB_FAIL_GPS,
CALIB_FAIL_CTRL,
CALIB_SUCCESS,
};
static inline int compat_gps_read(struct gps_data_t *data)
{
#if USE_GPSD2_API
return gps_poll(data);
/* API break in gpsd 6bba8b329fc7687b15863d30471d5af402467802 */
#elif GPSD_API_MAJOR_VERSION >= 7 && GPSD_API_MINOR_VERSION >= 0
return gps_read(data, NULL, 0);
#else
return gps_read(data);
#endif
}
static void calib_loop_run(void *_data)
{
int rc;
struct sysmobts_mgr_instance *mgr = _data;
LOGP(DCALIB, LOGL_NOTICE, "Going to calibrate the system.\n");
rc = calib_run(mgr, 1);
if (rc != 0)
calib_state_reset(mgr, CALIB_FAIL_START);
}
static void mgr_gps_close(struct sysmobts_mgr_instance *mgr)
{
if (!mgr->calib.gps_open)
return;
osmo_timer_del(&mgr->calib.fix_timeout);
osmo_fd_unregister(&mgr->calib.gpsfd);
gps_close(mgr->calib.gpsdata);
#if !USE_GPSD2_API
memset(mgr->calib.gpsdata, 0, sizeof(*(mgr->calib.gpsdata)));
#endif
mgr->calib.gps_open = 0;
}
static void mgr_gps_checkfix(struct sysmobts_mgr_instance *mgr)
{
struct gps_data_t *data = mgr->calib.gpsdata;
/* No 2D fix yet */
if (data->fix.mode < MODE_2D) {
LOGP(DCALIB, LOGL_DEBUG, "Fix mode not enough: %d\n",
data->fix.mode);
return;
}
/* The trimble driver is broken...add some sanity checking */
if (data->satellites_used < 1) {
LOGP(DCALIB, LOGL_DEBUG, "Not enough satellites used: %d\n",
data->satellites_used);
return;
}
LOGP(DCALIB, LOGL_NOTICE, "Got a GPS fix continuing.\n");
osmo_timer_del(&mgr->calib.fix_timeout);
mgr_gps_close(mgr);
request_clock_reset(mgr);
}
static int mgr_gps_read(struct osmo_fd *fd, unsigned int what)
{
int rc;
struct sysmobts_mgr_instance *mgr = fd->data;
rc = compat_gps_read(mgr->calib.gpsdata);
if (rc == -1) {
LOGP(DCALIB, LOGL_ERROR, "gpsd vanished during read.\n");
calib_state_reset(mgr, CALIB_FAIL_GPS);
return -1;
}
if (rc > 0)
mgr_gps_checkfix(mgr);
return 0;
}
static void mgr_gps_fix_timeout(void *_data)
{
struct sysmobts_mgr_instance *mgr = _data;
LOGP(DCALIB, LOGL_ERROR, "Failed to acquire GPRS fix.\n");
calib_state_reset(mgr, CALIB_FAIL_GPS);
}
static void mgr_gps_open(struct sysmobts_mgr_instance *mgr)
{
int rc;
#if USE_GPSD2_API
mgr->calib.gpsdata = gps_open("localhost", DEFAULT_GPSD_PORT);
rc = mgr->calib.gpsdata ? 0 : -1;
#else
mgr->calib.gpsdata = &mgr->calib.gpsdata_buf;
rc = gps_open("localhost", DEFAULT_GPSD_PORT, mgr->calib.gpsdata);
#endif
if (rc != 0) {
LOGP(DCALIB, LOGL_ERROR, "Failed to connect to GPS %d\n", rc);
calib_state_reset(mgr, CALIB_FAIL_GPS);
return;
}
mgr->calib.gps_open = 1;
#if USE_GPSD2_API
gps_query(mgr->calib.gpsdata, "w+x");
#else
gps_stream(mgr->calib.gpsdata, WATCH_ENABLE, NULL);
#endif
osmo_fd_setup(&mgr->calib.gpsfd, mgr->calib.gpsdata->gps_fd, OSMO_FD_READ | OSMO_FD_EXCEPT,
mgr_gps_read, mgr, 0);
if (osmo_fd_register(&mgr->calib.gpsfd) < 0) {
LOGP(DCALIB, LOGL_ERROR, "Failed to register GPSD fd\n");
calib_state_reset(mgr, CALIB_FAIL_GPS);
}
mgr->calib.state = CALIB_GPS_WAIT_FOR_FIX;
mgr->calib.fix_timeout.data = mgr;
mgr->calib.fix_timeout.cb = mgr_gps_fix_timeout;
osmo_timer_schedule(&mgr->calib.fix_timeout, 60, 0);
LOGP(DCALIB, LOGL_NOTICE,
"Opened the GPSD connection waiting for fix: %d\n",
mgr->calib.gpsfd.fd);
}
static void send_ctrl_cmd(struct sysmobts_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->calib.bts_conn, msg);
}
static void send_set_ctrl_cmd_int(struct sysmobts_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->calib.last_seqno++, key, val);
msg->l2h = msgb_put(msg, ret);
return send_ctrl_cmd(mgr, msg);
}
static void send_set_ctrl_cmd(struct sysmobts_mgr_instance *mgr,
const char *key, const char *val)
{
struct msgb *msg;
int ret;
msg = msgb_alloc_headroom(1024, 128, "CTRL SET");
ret = snprintf((char *) msg->data, 4096, "SET %u %s %s",
mgr->calib.last_seqno++, key, val);
msg->l2h = msgb_put(msg, ret);
return send_ctrl_cmd(mgr, msg);
}
static void send_get_ctrl_cmd(struct sysmobts_mgr_instance *mgr,
const char *key)
{
struct msgb *msg;
int ret;
msg = msgb_alloc_headroom(1024, 128, "CTRL GET");
ret = snprintf((char *) msg->data, 4096, "GET %u %s",
mgr->calib.last_seqno++, key);
msg->l2h = msgb_put(msg, ret);
return send_ctrl_cmd(mgr, msg);
}
static int calib_run(struct sysmobts_mgr_instance *mgr, int from_loop)
{
if (!mgr->calib.is_up) {
LOGP(DCALIB, LOGL_ERROR, "Control interface not connected.\n");
return -1;
}
if (mgr->calib.state != CALIB_INITIAL) {
LOGP(DCALIB, LOGL_ERROR, "Calib is already in progress.\n");
return -2;
}
mgr->calib.calib_from_loop = from_loop;
/* From now on everything will be handled from the failure */
mgr->calib.initial_calib_started = 1;
mgr_gps_open(mgr);
return 0;
}
int sysmobts_mgr_calib_run(struct sysmobts_mgr_instance *mgr)
{
return calib_run(mgr, 0);
}
static void request_clock_reset(struct sysmobts_mgr_instance *mgr)
{
send_set_ctrl_cmd(mgr, "trx.0.clock-info", "1");
mgr->calib.state = CALIB_CTR_RESET;
}
static void calib_state_reset(struct sysmobts_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.
*/
int timeout = 2 * 60 * 60;
if (outcome != CALIB_SUCCESS)
timeout = 5 * 60;
mgr->calib.calib_timeout.data = mgr;
mgr->calib.calib_timeout.cb = calib_loop_run;
osmo_timer_schedule(&mgr->calib.calib_timeout, timeout, 0);
}
mgr->calib.state = CALIB_INITIAL;
osmo_timer_del(&mgr->calib.timer);
mgr_gps_close(mgr);
}
static void calib_get_clock_err_cb(void *_data)
{
struct sysmobts_mgr_instance *mgr = _data;
LOGP(DCALIB, LOGL_DEBUG,
"Requesting current clock-info.\n");
send_get_ctrl_cmd(mgr, "trx.0.clock-info");
}
static void handle_ctrl_reset_resp(
struct sysmobts_mgr_instance *mgr,
struct ctrl_cmd *cmd)
{
if (strcmp(cmd->variable, "trx.0.clock-info") != 0) {
LOGP(DCALIB, LOGL_ERROR,
"Unexpected variable: %s\n", cmd->variable);
calib_state_reset(mgr, CALIB_FAIL_CTRL);
return;
}
if (strcmp(cmd->reply, "success") != 0) {
LOGP(DCALIB, LOGL_ERROR,
"Unexpected reply: %s\n", cmd->variable);
calib_state_reset(mgr, CALIB_FAIL_CTRL);
return;
}
mgr->calib.state = CALIB_CTR_WAIT;
mgr->calib.timer.cb = calib_get_clock_err_cb;
mgr->calib.timer.data = mgr;
osmo_timer_schedule(&mgr->calib.timer, 60, 0);
LOGP(DCALIB, LOGL_DEBUG,
"Reset the calibration counter. Waiting 60 seconds.\n");
}
static void handle_ctrl_get_resp(
struct sysmobts_mgr_instance *mgr,
struct ctrl_cmd *cmd)
{
char *saveptr = NULL;
char *clk_cur;
char *clk_src;
char *cal_err;
char *cal_res;
char *cal_src;
int cal_err_int;
if (strcmp(cmd->variable, "trx.0.clock-info") != 0) {
LOGP(DCALIB, LOGL_ERROR,
"Unexpected variable: %s\n", cmd->variable);
calib_state_reset(mgr, CALIB_FAIL_CTRL);
return;
}
clk_cur = strtok_r(cmd->reply, ",", &saveptr);
clk_src = strtok_r(NULL, ",", &saveptr);
cal_err = strtok_r(NULL, ",", &saveptr);
cal_res = strtok_r(NULL, ",", &saveptr);
cal_src = strtok_r(NULL, ",", &saveptr);
if (!clk_cur || !clk_src || !cal_err || !cal_res || !cal_src) {
LOGP(DCALIB, LOGL_ERROR, "Parse error on clock-info reply\n");
calib_state_reset(mgr, CALIB_FAIL_CTRL);
return;
}
cal_err_int = atoi(cal_err);
LOGP(DCALIB, LOGL_NOTICE,
"Calibration CUR(%s) SRC(%s) ERR(%s/%d) RES(%s) SRC(%s)\n",
clk_cur, clk_src, cal_err, cal_err_int, cal_res, cal_src);
if (strcmp(cal_res, "0") == 0) {
LOGP(DCALIB, LOGL_ERROR, "Invalid clock resolution. Giving up\n");
calib_state_reset(mgr, CALIB_FAIL_CTRL);
return;
}
/* Now we can finally set the new value */
LOGP(DCALIB, LOGL_NOTICE,
"Going to apply %d as new clock correction.\n",
-cal_err_int);
send_set_ctrl_cmd_int(mgr, "trx.0.clock-correction", -cal_err_int);
mgr->calib.state = CALIB_COR_SET;
}
static void handle_ctrl_set_cor(
struct sysmobts_mgr_instance *mgr,
struct ctrl_cmd *cmd)
{
if (strcmp(cmd->variable, "trx.0.clock-correction") != 0) {
LOGP(DCALIB, LOGL_ERROR,
"Unexpected variable: %s\n", cmd->variable);
calib_state_reset(mgr, CALIB_FAIL_CTRL);
return;
}
if (strcmp(cmd->reply, "success") != 0) {
LOGP(DCALIB, LOGL_ERROR,
"Unexpected reply: %s\n", cmd->variable);
calib_state_reset(mgr, CALIB_FAIL_CTRL);
return;
}
LOGP(DCALIB, LOGL_NOTICE,
"Calibration process completed\n");
calib_state_reset(mgr, CALIB_SUCCESS);
}
static void handle_ctrl(struct sysmobts_mgr_instance *mgr, struct msgb *msg)
{
struct ctrl_cmd *cmd = ctrl_cmd_parse(tall_mgr_ctx, msg);
if (!cmd) {
LOGP(DCALIB, LOGL_ERROR, "Failed to parse command/response\n");
return;
}
switch (cmd->type) {
case CTRL_TYPE_GET_REPLY:
switch (mgr->calib.state) {
case CALIB_CTR_WAIT:
handle_ctrl_get_resp(mgr, cmd);
break;
default:
LOGP(DCALIB, LOGL_ERROR,
"Unhandled response in state: %d %s/%s\n",
mgr->calib.state, cmd->variable, cmd->reply);
calib_state_reset(mgr, CALIB_FAIL_CTRL);
break;
};
break;
case CTRL_TYPE_SET_REPLY:
switch (mgr->calib.state) {
case CALIB_CTR_RESET:
handle_ctrl_reset_resp(mgr, cmd);
break;
case CALIB_COR_SET:
handle_ctrl_set_cor(mgr, cmd);
break;
default:
LOGP(DCALIB, LOGL_ERROR,
"Unhandled response in state: %d %s/%s\n",
mgr->calib.state, cmd->variable, cmd->reply);
calib_state_reset(mgr, CALIB_FAIL_CTRL);
break;
};
break;
case CTRL_TYPE_TRAP:
/* ignore any form of trap */
break;
default:
LOGP(DCALIB, LOGL_ERROR,
"Unhandled CTRL response: %d. Resetting state\n",
cmd->type);
calib_state_reset(mgr, CALIB_FAIL_CTRL);
break;
}
talloc_free(cmd);
}
/* Schedule a connect towards the BTS */
static void schedule_bts_connect(struct sysmobts_mgr_instance *mgr)
{
DEBUGP(DLCTRL, "Scheduling BTS connect\n");
osmo_timer_schedule(&mgr->calib.recon_timer, 1, 0);
}
/* BTS re-connect timer call-back */
static void bts_recon_timer_cb(void *data)
{
int rc;
struct sysmobts_mgr_instance *mgr = data;
/* The connection failures are to be expected during boot */
osmo_fd_write_enable(mgr->calib.bts_conn->ofd);
rc = ipa_client_conn_open(mgr->calib.bts_conn);
if (rc < 0) {
LOGP(DLCTRL, LOGL_NOTICE, "Failed to connect to BTS.\n");
schedule_bts_connect(mgr);
}
}
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;
DEBUGP(DCALIB, "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_IPACCESS:
/* handle the core IPA CCM messages in libosmoabis */
ipa_ccm_rcvmsg_bts_base(msg, link->ofd);
msgb_free(msg);
break;
case IPAC_PROTO_OSMO:
hh_ext = (struct ipaccess_head_ext *) hh->data;
switch (hh_ext->proto) {
case IPAC_PROTO_EXT_CTRL:
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;
}
/* link to BSC has gone up or down */
static void bts_updown_cb(struct ipa_client_conn *link, int up)
{
struct sysmobts_mgr_instance *mgr = link->data;
LOGP(DLCTRL, LOGL_INFO, "BTS connection %s\n", up ? "up" : "down");
if (up) {
mgr->calib.is_up = 1;
mgr->calib.last_seqno = 0;
if (!mgr->calib.initial_calib_started)
calib_run(mgr, 1);
} else {
mgr->calib.is_up = 0;
schedule_bts_connect(mgr);
calib_state_reset(mgr, CALIB_FAIL_CTRL);
}
}
int sysmobts_mgr_calib_init(struct sysmobts_mgr_instance *mgr)
{
if (!is_sbts2050_master()) {
LOGP(DCALIB, LOGL_NOTICE,
"Calib is only possible on the sysmoBTS2050 master\n");
return 0;
}
mgr->calib.bts_conn = ipa_client_conn_create2(tall_mgr_ctx, NULL, 0,
NULL, 0, "localhost", 4238,
bts_updown_cb, bts_read_cb,
NULL, mgr);
if (!mgr->calib.bts_conn) {
LOGP(DCALIB, LOGL_ERROR,
"Failed to create IPA connection\n");
return -1;
}
mgr->calib.recon_timer.cb = bts_recon_timer_cb;
mgr->calib.recon_timer.data = mgr;
schedule_bts_connect(mgr);
return 0;
}