aboutsummaryrefslogtreecommitdiffstats
path: root/src/osmo-bts-trx/trx_if.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/osmo-bts-trx/trx_if.c')
-rw-r--r--src/osmo-bts-trx/trx_if.c560
1 files changed, 560 insertions, 0 deletions
diff --git a/src/osmo-bts-trx/trx_if.c b/src/osmo-bts-trx/trx_if.c
new file mode 100644
index 00000000..69ad8415
--- /dev/null
+++ b/src/osmo-bts-trx/trx_if.c
@@ -0,0 +1,560 @@
+/*
+ * OpenBTS TRX interface handling
+ *
+ * Copyright (C) 2013 Andreas Eversberg <jolly@eversberg.eu>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+
+#include <netinet/in.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/bits.h>
+
+#include <osmo-bts/logging.h>
+#include <osmo-bts/bts.h>
+
+#include "l1_if.h"
+#include "trx_if.h"
+#include "scheduler.h"
+
+/* enable to print RSSI level graph */
+//#define TOA_RSSI_DEBUG
+
+int transceiver_available = 0;
+const char *transceiver_ip = "127.0.0.1";
+int settsc_enabled = 0;
+int setbsic_enabled = 0;
+
+/*
+ * socket
+ */
+
+static uint16_t base_port_local = 5800;
+
+/* open socket */
+static int trx_udp_open(void *priv, struct osmo_fd *ofd, uint16_t port,
+ int (*cb)(struct osmo_fd *fd, unsigned int what))
+{
+ struct sockaddr_storage sas;
+ struct sockaddr *sa = (struct sockaddr *)&sas;
+ socklen_t sa_len;
+
+ int rc;
+
+ /* Init */
+ ofd->fd = -1;
+ ofd->cb = cb;
+ ofd->data = priv;
+
+ /* Listen / Binds */
+ rc = osmo_sock_init_ofd(ofd, AF_UNSPEC, SOCK_DGRAM, 0, transceiver_ip,
+ port, OSMO_SOCK_F_BIND);
+ if (rc < 0)
+ return rc;
+
+ /* Connect */
+ sa_len = sizeof(sas);
+ rc = getsockname(ofd->fd, sa, &sa_len);
+ if (rc)
+ return rc;
+
+ if (sa->sa_family == AF_INET) {
+ struct sockaddr_in *sin = (struct sockaddr_in *)sa;
+ sin->sin_port = htons(ntohs(sin->sin_port) - 100);
+ } else if (sa->sa_family == AF_INET6) {
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
+ sin6->sin6_port = htons(ntohs(sin6->sin6_port) - 100);
+ } else {
+ return -EINVAL;
+ }
+
+ rc = connect(ofd->fd, sa, sa_len);
+ if (rc)
+ return rc;
+
+
+ return 0;
+}
+
+/* close socket */
+static void trx_udp_close(struct osmo_fd *ofd)
+{
+ if (ofd->fd > 0) {
+ osmo_fd_unregister(ofd);
+ close(ofd->fd);
+ ofd->fd = -1;
+ }
+}
+
+
+/*
+ * clock
+ */
+
+static struct osmo_fd trx_ofd_clk;
+
+
+/* get clock from clock socket */
+static int trx_clk_read_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ char buf[1500];
+ int len;
+ uint32_t fn;
+
+ len = recv(ofd->fd, buf, sizeof(buf) - 1, 0);
+ if (len <= 0)
+ return len;
+ buf[len] = '\0';
+
+ if (!!strncmp(buf, "IND CLOCK ", 10)) {
+ LOGP(DTRX, LOGL_NOTICE, "Unknown message on clock port: %s\n",
+ buf);
+ return 0;
+ }
+
+ sscanf(buf, "IND CLOCK %u", &fn);
+ LOGP(DTRX, LOGL_INFO, "Clock indication: fn=%u\n", fn);
+
+ if (fn >= 2715648) {
+ fn %= 2715648;
+ LOGP(DTRX, LOGL_ERROR, "Indicated clock's FN is not wrapping "
+ "correctly, correcting to fn=%u\n", fn);
+ }
+
+ trx_sched_clock(fn);
+
+ return 0;
+}
+
+
+/*
+ * ctrl
+ */
+
+static void trx_ctrl_timer_cb(void *data);
+
+/* send first ctrl message and start timer */
+static void trx_ctrl_send(struct trx_l1h *l1h)
+{
+ struct trx_ctrl_msg *tcm;
+
+ /* get first command */
+ if (llist_empty(&l1h->trx_ctrl_list))
+ return;
+ tcm = llist_entry(l1h->trx_ctrl_list.next, struct trx_ctrl_msg, list);
+
+ LOGP(DTRX, LOGL_DEBUG, "Sending control '%s' to trx=%u\n", tcm->cmd,
+ l1h->trx->nr);
+ /* send command */
+ send(l1h->trx_ofd_ctrl.fd, tcm->cmd, strlen(tcm->cmd)+1, 0);
+
+ /* start timer */
+ l1h->trx_ctrl_timer.cb = trx_ctrl_timer_cb;
+ l1h->trx_ctrl_timer.data = l1h;
+ osmo_timer_schedule(&l1h->trx_ctrl_timer, 2, 0);
+}
+
+/* send first ctrl message and start timer */
+static void trx_ctrl_timer_cb(void *data)
+{
+ struct trx_l1h *l1h = data;
+
+ LOGP(DTRX, LOGL_NOTICE, "No response from transceiver for trx=%d\n",
+ l1h->trx->nr);
+
+ trx_ctrl_send(l1h);
+}
+
+/* add a new ctrl command */
+static int trx_ctrl_cmd(struct trx_l1h *l1h, int critical, const char *cmd,
+ const char *fmt, ...)
+{
+ struct trx_ctrl_msg *tcm;
+ va_list ap;
+ int l, pending = 0;
+
+ if (!transceiver_available && !!strcmp(cmd, "POWEROFF")) {
+ LOGP(DTRX, LOGL_ERROR, "CTRL ignored: No clock from "
+ "transceiver, please fix!\n");
+ return -EIO;
+ }
+
+ if (!llist_empty(&l1h->trx_ctrl_list))
+ pending = 1;
+
+ /* create message */
+ tcm = talloc_zero(tall_bts_ctx, struct trx_ctrl_msg);
+ if (!tcm)
+ return -ENOMEM;
+ if (fmt && fmt[0]) {
+ l = snprintf(tcm->cmd, sizeof(tcm->cmd)-1, "CMD %s ", cmd);
+ va_start(ap, fmt);
+ vsnprintf(tcm->cmd + l, sizeof(tcm->cmd) - l - 1, fmt, ap);
+ va_end(ap);
+ } else
+ snprintf(tcm->cmd, sizeof(tcm->cmd)-1, "CMD %s", cmd);
+ tcm->cmd_len = strlen(cmd);
+ tcm->critical = critical;
+ llist_add_tail(&tcm->list, &l1h->trx_ctrl_list);
+ LOGP(DTRX, LOGL_INFO, "Adding new control '%s'\n", tcm->cmd);
+
+ /* send message, if no pending message */
+ if (!pending)
+ trx_ctrl_send(l1h);
+
+ return 0;
+}
+
+int trx_if_cmd_poweroff(struct trx_l1h *l1h)
+{
+ if (l1h->trx->nr == 0)
+ return trx_ctrl_cmd(l1h, 1, "POWEROFF", "");
+ else
+ return 0;
+}
+
+int trx_if_cmd_poweron(struct trx_l1h *l1h)
+{
+ if (l1h->trx->nr == 0)
+ return trx_ctrl_cmd(l1h, 1, "POWERON", "");
+ else
+ return 0;
+}
+
+int trx_if_cmd_settsc(struct trx_l1h *l1h, uint8_t tsc)
+{
+ if (!settsc_enabled)
+ return 0;
+ /* if TSC is enabled only, the positive response is mandatory */
+ return trx_ctrl_cmd(l1h, (setbsic_enabled) ? 0 : 1, "SETTSC", "%d",
+ tsc);
+}
+
+int trx_if_cmd_setbsic(struct trx_l1h *l1h, uint8_t bsic)
+{
+ if (!setbsic_enabled)
+ return 0;
+ /* if BSIC is enabled only, the positive response is mandatory */
+ return trx_ctrl_cmd(l1h, (settsc_enabled) ? 0 : 1, "SETBSIC", "%d",
+ bsic);
+}
+
+int trx_if_cmd_setrxgain(struct trx_l1h *l1h, int db)
+{
+ return trx_ctrl_cmd(l1h, 0, "SETRXGAIN", "%d", db);
+}
+
+int trx_if_cmd_setpower(struct trx_l1h *l1h, int db)
+{
+ return trx_ctrl_cmd(l1h, 0, "SETPOWER", "%d", db);
+}
+
+int trx_if_cmd_setmaxdly(struct trx_l1h *l1h, int dly)
+{
+ return trx_ctrl_cmd(l1h, 0, "SETMAXDLY", "%d", dly);
+}
+
+int trx_if_cmd_setslot(struct trx_l1h *l1h, uint8_t tn, uint8_t type)
+{
+ return trx_ctrl_cmd(l1h, 1, "SETSLOT", "%d %d", tn, type);
+}
+
+int trx_if_cmd_rxtune(struct trx_l1h *l1h, uint16_t arfcn)
+{
+ uint16_t freq10;
+
+ freq10 = gsm_arfcn2freq10(arfcn, 1); /* RX = uplink */
+ if (freq10 == 0xffff) {
+ LOGP(DTRX, LOGL_ERROR, "Arfcn %d not defined.\n", arfcn);
+ return -ENOTSUP;
+ }
+
+ return trx_ctrl_cmd(l1h, 1, "RXTUNE", "%d", freq10 * 100);
+}
+
+int trx_if_cmd_txtune(struct trx_l1h *l1h, uint16_t arfcn)
+{
+ uint16_t freq10;
+
+ freq10 = gsm_arfcn2freq10(arfcn, 0); /* TX = downlink */
+ if (freq10 == 0xffff) {
+ LOGP(DTRX, LOGL_ERROR, "Arfcn %d not defined.\n", arfcn);
+ return -ENOTSUP;
+ }
+
+ return trx_ctrl_cmd(l1h, 1, "TXTUNE", "%d", freq10 * 100);
+}
+
+int trx_if_cmd_handover(struct trx_l1h *l1h, uint8_t tn, uint8_t ss)
+{
+ return trx_ctrl_cmd(l1h, 1, "HANDOVER", "%d %d", tn, ss);
+}
+
+int trx_if_cmd_nohandover(struct trx_l1h *l1h, uint8_t tn, uint8_t ss)
+{
+ return trx_ctrl_cmd(l1h, 1, "NOHANDOVER", "%d %d", tn, ss);
+}
+
+/* get response from ctrl socket */
+static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct trx_l1h *l1h = ofd->data;
+ char buf[1500];
+ int len, resp;
+
+ len = recv(ofd->fd, buf, sizeof(buf) - 1, 0);
+ if (len <= 0)
+ return len;
+ buf[len] = '\0';
+
+ if (!strncmp(buf, "RSP ", 4)) {
+ struct trx_ctrl_msg *tcm;
+ char *p;
+ int rsp_len = 0;
+
+ /* calculate the length of response item */
+ p = strchr(buf + 4, ' ');
+ if (p)
+ rsp_len = p - buf - 4;
+ else
+ rsp_len = strlen(buf) - 4;
+
+ LOGP(DTRX, LOGL_INFO, "Response message: '%s'\n", buf);
+
+ /* abort timer and send next message, if any */
+ if (osmo_timer_pending(&l1h->trx_ctrl_timer))
+ osmo_timer_del(&l1h->trx_ctrl_timer);
+
+ /* get command for response message */
+ if (llist_empty(&l1h->trx_ctrl_list)) {
+ LOGP(DTRX, LOGL_NOTICE, "Response message without "
+ "command\n");
+ return -EINVAL;
+ }
+ tcm = llist_entry(l1h->trx_ctrl_list.next, struct trx_ctrl_msg,
+ list);
+
+ /* check if respose matches command */
+ if (rsp_len != tcm->cmd_len) {
+ notmatch:
+ LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_NOTICE,
+ "Response message '%s' does not match command "
+ "message '%s'\n", buf, tcm->cmd);
+ goto rsp_error;
+ }
+ if (!!strncmp(buf + 4, tcm->cmd + 4, rsp_len))
+ goto notmatch;
+
+ /* check for response code */
+ sscanf(p + 1, "%d", &resp);
+ if (resp) {
+ LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_NOTICE,
+ "transceiver (trx=%d) rejected TRX command "
+ "with response: '%s'\n", l1h->trx->nr, buf);
+rsp_error:
+ if (tcm->critical) {
+ bts_shutdown(l1h->trx->bts, "SIGINT");
+ /* keep tcm list, so process is stopped */
+ return -EIO;
+ }
+ }
+
+ /* remove command from list */
+ llist_del(&tcm->list);
+ talloc_free(tcm);
+
+ trx_ctrl_send(l1h);
+ } else
+ LOGP(DTRX, LOGL_NOTICE, "Unknown message on ctrl port: %s\n",
+ buf);
+
+ return 0;
+}
+
+
+/*
+ * data
+ */
+
+static int trx_data_read_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct trx_l1h *l1h = ofd->data;
+ uint8_t buf[256];
+ int len;
+ uint8_t tn;
+ int8_t rssi;
+ float toa = 0.0;
+ uint32_t fn;
+ sbit_t bits[148];
+ int i;
+
+ len = recv(ofd->fd, buf, sizeof(buf), 0);
+ if (len <= 0)
+ return len;
+ if (len != 158) {
+ LOGP(DTRX, LOGL_NOTICE, "Got data message with invalid lenght "
+ "'%d'\n", len);
+ return -EINVAL;
+ }
+ tn = buf[0];
+ fn = (buf[1] << 24) | (buf[2] << 16) | (buf[3] << 8) | buf[4];
+ rssi = -(int8_t)buf[5];
+ toa = ((int16_t)(buf[6] << 8) | buf[7]) / 256.0F;
+
+ /* copy and convert bits {254..0} to sbits {-127..127} */
+ for (i = 0; i < 148; i++) {
+ if (buf[8 + i] == 255)
+ bits[i] = -127;
+ else
+ bits[i] = 127 - buf[8 + i];
+ }
+
+ if (tn >= 8) {
+ LOGP(DTRX, LOGL_ERROR, "Illegal TS %d\n", tn);
+ return -EINVAL;
+ }
+ if (fn >= 2715648) {
+ LOGP(DTRX, LOGL_ERROR, "Illegal FN %u\n", fn);
+ return -EINVAL;
+ }
+
+ LOGP(DTRX, LOGL_DEBUG, "RX burst tn=%u fn=%u rssi=%d toa=%.2f\n",
+ tn, fn, rssi, toa);
+
+#ifdef TOA_RSSI_DEBUG
+ char deb[128];
+
+ sprintf(deb, "| 0 "
+ " | rssi=%4d toa=%4.2f fn=%u", rssi, toa, fn);
+ deb[1 + (128 + rssi) / 4] = '*';
+ fprintf(stderr, "%s\n", deb);
+#endif
+
+ trx_sched_ul_burst(l1h, tn, fn, bits, rssi, toa);
+
+ return 0;
+}
+
+int trx_if_data(struct trx_l1h *l1h, uint8_t tn, uint32_t fn, uint8_t pwr,
+ const ubit_t *bits)
+{
+ uint8_t buf[256];
+
+ LOGP(DTRX, LOGL_DEBUG, "TX burst tn=%u fn=%u pwr=%u\n", tn, fn, pwr);
+
+ buf[0] = tn;
+ buf[1] = (fn >> 24) & 0xff;
+ buf[2] = (fn >> 16) & 0xff;
+ buf[3] = (fn >> 8) & 0xff;
+ buf[4] = (fn >> 0) & 0xff;
+ buf[5] = pwr;
+
+ /* copy ubits {0,1} */
+ memcpy(buf + 6, bits, 148);
+
+ /* we must be sure that we have clock, and we have sent all control
+ * data */
+ if (transceiver_available && llist_empty(&l1h->trx_ctrl_list)) {
+ send(l1h->trx_ofd_data.fd, buf, 154, 0);
+ } else
+ LOGP(DTRX, LOGL_DEBUG, "Ignoring TX data, transceiver "
+ "offline.\n");
+
+ return 0;
+}
+
+
+/*
+ * open/close
+ */
+
+int trx_if_open(struct trx_l1h *l1h)
+{
+ int rc;
+
+ LOGP(DTRX, LOGL_NOTICE, "Open transceiver for trx=%u\n", l1h->trx->nr);
+
+ /* initialize ctrl queue */
+ INIT_LLIST_HEAD(&l1h->trx_ctrl_list);
+
+ /* open sockets */
+ if (l1h->trx->nr == 0) {
+ rc = trx_udp_open(NULL, &trx_ofd_clk, base_port_local,
+ trx_clk_read_cb);
+ if (rc < 0)
+ return rc;
+ LOGP(DTRX, LOGL_NOTICE, "Waiting for transceiver send clock\n");
+ }
+ rc = trx_udp_open(l1h, &l1h->trx_ofd_ctrl,
+ base_port_local + (l1h->trx->nr << 1) + 1, trx_ctrl_read_cb);
+ if (rc < 0)
+ goto err;
+ rc = trx_udp_open(l1h, &l1h->trx_ofd_data,
+ base_port_local + (l1h->trx->nr << 1) + 2, trx_data_read_cb);
+ if (rc < 0)
+ goto err;
+
+ /* enable all slots */
+ l1h->config.slotmask = 0xff;
+
+ if (l1h->trx->nr == 0)
+ trx_if_cmd_poweroff(l1h);
+
+ return 0;
+
+err:
+ trx_if_close(l1h);
+ return rc;
+}
+
+/* flush pending control messages */
+void trx_if_flush(struct trx_l1h *l1h)
+{
+ struct trx_ctrl_msg *tcm;
+
+ /* free ctrl message list */
+ while (!llist_empty(&l1h->trx_ctrl_list)) {
+ tcm = llist_entry(l1h->trx_ctrl_list.next, struct trx_ctrl_msg,
+ list);
+ llist_del(&tcm->list);
+ talloc_free(tcm);
+ }
+}
+
+void trx_if_close(struct trx_l1h *l1h)
+{
+ LOGP(DTRX, LOGL_NOTICE, "Close transceiver for trx=%u\n", l1h->trx->nr);
+
+ trx_if_flush(l1h);
+
+ /* close sockets */
+ if (l1h->trx->nr == 0)
+ trx_udp_close(&trx_ofd_clk);
+ trx_udp_close(&l1h->trx_ofd_ctrl);
+ trx_udp_close(&l1h->trx_ofd_data);
+}
+