osmocom-bb/src/host/layer23/src/transceiver/trx.c

559 lines
13 KiB
C

/*
* trx.c
*
* OpenBTS TRX interface handling
*
* Copyright (C) 2013 Sylvain Munaut <tnt@246tNt.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 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 <errno.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <osmocom/core/bits.h>
#include <osmocom/core/select.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/bb/common/logging.h>
#include "l1ctl.h"
#include "l1ctl_link.h"
#include "trx.h"
#include "burst.h"
static int _trx_clk_read_cb(struct osmo_fd *ofd, unsigned int what);
static int _trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what);
static int _trx_data_read_cb(struct osmo_fd *ofd, unsigned int what);
/* ------------------------------------------------------------------------ */
/* Init / Cleanup */
/* ------------------------------------------------------------------------ */
static int
_trx_udp_init(struct trx *trx,
struct osmo_fd *ofd, const char *addr, 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 rv;
/* Init */
ofd->fd = -1;
ofd->cb = cb;
ofd->data = trx;
/* Listen / Binds */
rv = osmo_sock_init_ofd(
ofd,
AF_UNSPEC, SOCK_DGRAM, 0, addr, port,
OSMO_SOCK_F_BIND);
if (rv < 0)
goto err;
/* Connect */
sa_len = sizeof(struct sockaddr_storage);
rv = getsockname(ofd->fd, sa, &sa_len);
if (rv)
goto err;
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 {
rv = -EINVAL;
goto err;
}
rv = connect(ofd->fd, sa, sa_len);
if (rv)
goto err;
return 0;
err:
if (ofd->fd >= 0) {
osmo_fd_unregister(ofd);
close(ofd->fd);
}
return rv;
}
struct trx *
trx_alloc(const char *addr, uint16_t base_port, struct l1ctl_link *l1l)
{
struct trx *trx;
int rv;
/* Alloc */
trx = talloc_zero(NULL, struct trx);
if (!trx)
return NULL;
/* Init */
trx->arfcn = ARFCN_INVAL;
trx->bsic = BSIC_INVAL;
/* L1 link */
trx->l1l = l1l;
/* Clock */
rv = _trx_udp_init(trx, &trx->ofd_clk, addr, base_port, _trx_clk_read_cb);
if (rv)
goto err;
/* Control */
rv = _trx_udp_init(trx, &trx->ofd_ctrl, addr, base_port+1, _trx_ctrl_read_cb);
if (rv)
goto err;
/* Data */
rv = _trx_udp_init(trx, &trx->ofd_data, addr, base_port+2, _trx_data_read_cb);
if (rv)
goto err;
return trx;
err:
/* FIXME */
return NULL;
}
void
trx_free(struct trx *trx)
{
/* FIXME close and remove ofd */
talloc_free(trx);
}
/* ------------------------------------------------------------------------ */
/* Clk interface */
/* ------------------------------------------------------------------------ */
static int
_trx_clk_read_cb(struct osmo_fd *ofd, unsigned int what)
{
char buf[1500];
int l;
l = recv(ofd->fd, buf, sizeof(buf), 0);
if (l <= 0)
return l;
LOGP(DTRX, LOGL_ERROR, "[!] Unexpected data on the CLK interface, discarding\n");
return l;
}
int
trx_clk_ind(struct trx *trx, uint32_t fn)
{
char buf[64];
LOGP(DTRX, LOGL_DEBUG, "TRX CLK Indication %d\n", fn);
snprintf(buf, sizeof(buf), "IND CLOCK %d", fn + 2); /* FIXME Dynamic adjust ? */
send(trx->ofd_clk.fd, buf, strlen(buf)+1, 0);
return 0;
}
/* ------------------------------------------------------------------------ */
/* Control interface */
/* ------------------------------------------------------------------------ */
#define TRX_CMD_BUF_LEN 128
static int
_trx_ctrl_send_resp(struct trx *trx, const char *cmd, const char *fmt, ...)
{
va_list ap;
char buf[TRX_CMD_BUF_LEN];
int l;
l = snprintf(buf, sizeof(buf)-1, "RSP %s ", cmd);
va_start(ap, fmt);
l += vsnprintf(buf+l, sizeof(buf)-l-1, fmt, ap);
va_end(ap);
buf[l] = '\0';
LOGP(DTRX, LOGL_DEBUG, "TRX Control send: |%s|\n", buf);
send(trx->ofd_ctrl.fd, buf, strlen(buf)+1, 0);
return 0;
}
static int
_trx_ctrl_cmd_poweroff(struct trx *trx, const char *cmd, const char *args)
{
l1ctl_tx_bts_mode(trx->l1l, 0, 0, 0);
return _trx_ctrl_send_resp(trx, cmd, "%d", 0);
}
static int
_trx_ctrl_cmd_poweron(struct trx *trx, const char *cmd, const char *args)
{
int rv;
if (trx->bsic == BSIC_INVAL || trx->arfcn == ARFCN_INVAL) {
LOGP(DTRX, LOGL_ERROR,
"TRX received POWERON when not fully configured\n");
rv = -EINVAL;
} else {
rv = l1ctl_tx_bts_mode(trx->l1l, 1, trx->bsic, trx->arfcn);
}
return _trx_ctrl_send_resp(trx, cmd, "%d", rv);
}
static int
_trx_ctrl_cmd_settsc(struct trx *trx, const char *cmd, const char *args)
{
LOGP(DTRX, LOGL_ERROR,
"TRX received SETTSC command ! "
"OpenBTS should be configured to send SETBSIC command !\n");
return _trx_ctrl_send_resp(trx, cmd, "%d", -1);
}
static int
_trx_ctrl_cmd_setbsic(struct trx *trx, const char *cmd, const char *args)
{
int bsic = atoi(args);
if (bsic >= 64) {
LOGP(DTRX, LOGL_ERROR, "Invalid BSIC received\n");
return _trx_ctrl_send_resp(trx, cmd, "%d", -1);
}
trx->bsic = bsic;
return _trx_ctrl_send_resp(trx, cmd, "%d", 0);
}
static int
_trx_ctrl_cmd_setrxgain(struct trx *trx, const char *cmd, const char *args)
{
int db = atoi(args);
return _trx_ctrl_send_resp(trx, cmd, "%d %d", 0, db);
}
static int
_trx_ctrl_cmd_setpower(struct trx *trx, const char *cmd, const char *args)
{
int db = atoi(args);
return _trx_ctrl_send_resp(trx, cmd, "%d %d", 0, db);
}
static int
_trx_ctrl_cmd_setmaxdly(struct trx *trx, const char *cmd, const char *args)
{
int dly = atoi(args);
return _trx_ctrl_send_resp(trx, cmd, "%d %d", 0, dly);
}
static int
_trx_ctrl_cmd_setslot(struct trx *trx, const char *cmd, const char *args)
{
int n, tn, type;
n = sscanf(args, "%d %d", &tn, &type);
if ((n != 2) || (tn < 0) || (tn > 7) || (type < 0) || (type > 8))
return _trx_ctrl_send_resp(trx, cmd, "%d %d %d", -1, tn, type);
return _trx_ctrl_send_resp(trx, cmd, "%d %d", 0, type);
}
static int
_trx_ctrl_cmd_rxtune(struct trx *trx, const char *cmd, const char *args)
{
int freq_khz = atoi(args);
uint16_t arfcn;
int rv = 0;
arfcn = gsm_freq102arfcn(freq_khz / 100, 1);
arfcn &= ~ARFCN_UPLINK;
if ( arfcn == ARFCN_INVAL ||
(trx->arfcn != ARFCN_INVAL && trx->arfcn != arfcn)) {
LOGP(DTRX, LOGL_ERROR, "RXTUNE called with invalid/inconsistent frequency\n");
goto done;
}
if (trx->arfcn == ARFCN_INVAL) {
LOGP(DTRX, LOGL_NOTICE, "Setting C0 ARFCN to %d (%s)\n",
arfcn & ~ARFCN_FLAG_MASK, gsm_band_name(gsm_arfcn2band(arfcn)));
trx->arfcn = arfcn;
}
done:
return _trx_ctrl_send_resp(trx, cmd, "%d %d", rv, freq_khz);
}
static int
_trx_ctrl_cmd_txtune(struct trx *trx, const char *cmd, const char *args)
{
int freq_khz = atoi(args);
uint16_t arfcn;
int rv = 0;
arfcn = gsm_freq102arfcn(freq_khz / 100, 0);
if ( arfcn == ARFCN_INVAL ||
(trx->arfcn != ARFCN_INVAL && trx->arfcn != arfcn)) {
LOGP(DTRX, LOGL_ERROR, "TXTUNE called with invalid/inconsistent frequency\n");
goto done;
}
if (trx->arfcn == ARFCN_INVAL) {
LOGP(DTRX, LOGL_NOTICE, "Setting C0 ARFCN to %d (%s)\n",
arfcn & ~ARFCN_FLAG_MASK, gsm_band_name(gsm_arfcn2band(arfcn)));
trx->arfcn = arfcn;
}
done:
return _trx_ctrl_send_resp(trx, cmd, "%d %d", rv, freq_khz);
}
struct trx_cmd_handler {
const char *cmd;
int (*handler)(struct trx *trx, const char *cmd, const char *args);
};
static const struct trx_cmd_handler trx_handlers[] = {
{ "POWEROFF", _trx_ctrl_cmd_poweroff },
{ "POWERON", _trx_ctrl_cmd_poweron },
{ "SETTSC", _trx_ctrl_cmd_settsc },
{ "SETBSIC", _trx_ctrl_cmd_setbsic },
{ "SETPOWER", _trx_ctrl_cmd_setpower },
{ "SETRXGAIN", _trx_ctrl_cmd_setrxgain },
{ "SETMAXDLY", _trx_ctrl_cmd_setmaxdly },
{ "SETSLOT", _trx_ctrl_cmd_setslot },
{ "RXTUNE", _trx_ctrl_cmd_rxtune },
{ "TXTUNE", _trx_ctrl_cmd_txtune },
{ NULL, NULL }
};
static int
_trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what)
{
struct trx *trx = ofd->data;
const struct trx_cmd_handler *ch;
char buf[TRX_CMD_BUF_LEN];
char *cmd, *args;
ssize_t l;
int rv;
/* Get message */
l = recv(ofd->fd, buf, sizeof(buf)-1, 0);
if (l <= 0) {
/* FIXME handle exception ... */
return l;
}
/* Check 'CMD ' */
if (strncmp(buf, "CMD ", 4))
goto inval;
/* Check length */
buf[l] = '\0'; /* Safety */
if (strlen(buf) != (l-1))
goto inval;
/* Split command name and arguments */
cmd = &buf[4];
args = strchr(cmd, ' ');
if (!args)
args = &buf[l];
else
*args++ = '\0';
LOGP(DTRX, LOGL_DEBUG, "TRX Control recv: |%s|%s|\n", cmd, args);
/* Find handler */
for (ch=trx_handlers; ch->cmd; ch++) {
if (!strcmp(ch->cmd, cmd)) {
rv = ch->handler(trx, cmd, args);
if (rv)
LOGP(DTRX, LOGL_ERROR, "[!] Processing failure for command '%s'\n", cmd);
break;
}
}
if (!ch->cmd) {
LOGP(DTRX, LOGL_ERROR, "[!] No handlers found for command '%s'. Empty response\n", cmd);
_trx_ctrl_send_resp(trx, cmd, "%d", -1);
}
/* Done ! */
return l;
inval:
LOGP(DTRX, LOGL_ERROR, "[!] Invalid command '%s' on CTRL interface, discarding\n", buf);
return l;
}
/* ------------------------------------------------------------------------ */
/* Data interface */
/* ------------------------------------------------------------------------ */
#define TRX_DATA_BUF_LEN 256
static const ubit_t dummy_burst[] = {
0,0,0,
1,1,1,1,1,0,1,1,0,1,1,1,0,1,1,0,0,0,0,0,1,0,1,0,0,1,0,0,1,1,1,0,
0,0,0,0,1,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,1,1,1,0,0,
0,1,0,1,1,1,0,0,0,1,0,1,1,1,0,0,0,1,0,1,0,1,1,1,0,1,0,0,1,0,1,0,
0,0,1,1,0,0,1,1,0,0,1,1,1,0,0,1,1,1,1,0,1,0,0,1,1,1,1,1,0,0,0,1,
0,0,1,0,1,1,1,1,1,0,1,0,1,0,
0,0,0,
};
static int
_trx_data_read_cb(struct osmo_fd *ofd, unsigned int what)
{
struct trx *trx = ofd->data;
uint8_t buf[TRX_DATA_BUF_LEN];
uint32_t fn;
int l, tn, pwr_att;
ubit_t *data;
struct burst_data _burst, *burst = &_burst;
/* Get message */
l = recv(ofd->fd, buf, sizeof(buf)-1, 0);
if (l <= 0) {
/* FIXME handle exception ... */
return l;
}
/* Validate */
if (l != 1+4+1+148)
goto inval;
/* Parse */
tn = buf[0];
fn = (buf[1] << 24) | (buf[2] << 16) | (buf[3] << 8) | buf[4];
pwr_att = buf[5];
data = buf+6;
/* Ignore FCCH and SCH completely, they're handled internally */
if (((fn % 51) % 10) < 2)
goto skip;
/* Detect dummy bursts */
if (!memcmp(data, dummy_burst, sizeof(dummy_burst))) {
LOGP(DTRX, LOGL_DEBUG, "TRX Data %u:%d:%d:DUMMY\n",
fn, tn, pwr_att);
goto skip;
}
/* Pack */
burst->type = BURST_NB;
burst->data[14] = 0x00;
osmo_ubit2pbit_ext(burst->data, 0, data, 3, 58, 0);
osmo_ubit2pbit_ext(burst->data, 58, data, 87, 58, 0);
/* Send to L1 */
if (tn == 0)
l1ctl_tx_bts_burst_req(trx->l1l, fn, tn, burst);
/* Debug */
if (tn == 0)
LOGP(DTRX, LOGL_DEBUG, "TRX Data %u:%d:%d:%s\n",
fn, tn, pwr_att, osmo_hexdump_nospc(burst->data, 15));
/* Done ! */
skip:
return l;
inval:
LOGP(DTRX, LOGL_ERROR, "[!] Invalid data burst on DATA interface, discarding\n");
return l;
}
int
trx_data_ind(struct trx *trx, uint32_t fn, uint8_t tn, sbit_t *data, float toa)
{
char buf[158];
short toa_int = (short)(toa * 256.0f);
int i;
LOGP(DTRX, LOGL_DEBUG, "TRX Data Indication (fn=%d, tn=%d, toa=%.2f)\n", fn, tn, toa);
buf[0] = tn;
buf[1] = (fn >> 24) & 0xff;
buf[2] = (fn >> 16) & 0xff;
buf[3] = (fn >> 8) & 0xff;
buf[4] = (fn >> 0) & 0xff;
/* RSSI */
buf[5] = 0x80;
/* TOA */
buf[6] = (toa_int >> 8) & 0xff;
buf[7] = (toa_int >> 0) & 0xff;
/* Data bits */
for (i=0; i<148; i++)
buf[8+i] = (unsigned char)(127 - ((int)data[i]));
/* End char ? */
buf[9+148] = 0x00;
/* Send result */
send(trx->ofd_data.fd, buf, 158, 0);
return 0;
}