Merge 'fixeria/trx' into master

Change-Id: I5586fd8c9eb281285f4a59e63cb17dbc3641e1c1
This commit is contained in:
Harald Welte 2018-04-07 19:34:19 +02:00
commit 05d95a46fd
47 changed files with 10305 additions and 1 deletions

View File

@ -13,7 +13,7 @@ TOPDIR=$(shell pwd)
all: libosmocore-target nofirmware firmware mtk-firmware
nofirmware: layer23 osmocon gsmmap gprsdecode virtphy
nofirmware: layer23 osmocon trxcon gsmmap gprsdecode virtphy
libosmocore-target: shared/libosmocore/build-target/src/.libs/libosmocore.a
@ -58,6 +58,19 @@ host/virt_phy/Makefile: host/virt_phy/configure
host/virt_phy/virtphy: host/virt_phy/Makefile
make -C host/virt_phy
.PHONY: trxcon
trxcon: host/trxcon/trxcon
host/trxcon/configure: host/trxcon/configure.ac
cd host/trxcon && autoreconf -i
host/trxcon/Makefile: host/trxcon/configure
cd host/trxcon && ./configure $(HOST_CONFARGS)
host/trxcon/trxcon: host/trxcon/Makefile
make -C host/trxcon
.PHONY: gsmmap
gsmmap: host/gsmmap/gsmmap
@ -111,6 +124,7 @@ clean:
make -C host/gsmmap $@
make -C host/gprsdecode $@
make -C host/virt_phy $@
make -C host/trxcon $@
make -C target/firmware $@
make -C target/firmware -f Makefile.mtk $@
@ -121,5 +135,6 @@ distclean:
make -C host/gsmmap $@
make -C host/gprsdecode $@
make -C host/virt_phy $@
make -C host/trxcon $@
# 'firmware' also handles 'mtk-firmware'
make -C target/firmware $@

27
src/host/trxcon/.gitignore vendored Normal file
View File

@ -0,0 +1,27 @@
# autoreconf by-products
*.in
aclocal.m4
autom4te.cache/
configure
depcomp
install-sh
missing
compile
# configure by-products
.deps/
Makefile
config.status
version.h
# build by-products
*.o
*.a
trxcon
# various
.version
.tarball-version

View File

@ -0,0 +1,50 @@
AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6
# versioning magic
BUILT_SOURCES = $(top_srcdir)/.version
$(top_srcdir)/.version:
echo $(VERSION) > $@-t && mv $@-t $@
dist-hook:
echo $(VERSION) > $(distdir)/.tarball-version
AM_CPPFLAGS = \
$(all_includes) \
-I$(top_srcdir)/include \
$(NULL)
AM_CFLAGS = \
-Wall \
$(LIBOSMOCORE_CFLAGS) \
$(LIBOSMOCODING_CFLAGS) \
$(LIBOSMOGSM_CFLAGS) \
$(NULL)
bin_PROGRAMS = trxcon
trxcon_SOURCES = \
l1ctl_link.c \
l1ctl.c \
trx_if.c \
logging.c \
trxcon.c \
$(NULL)
# Scheduler
trxcon_SOURCES += \
sched_lchan_common.c \
sched_lchan_desc.c \
sched_lchan_xcch.c \
sched_lchan_tchf.c \
sched_lchan_rach.c \
sched_lchan_sch.c \
sched_mframe.c \
sched_clck.c \
sched_prim.c \
sched_trx.c \
$(NULL)
trxcon_LDADD = \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOCODING_LIBS) \
$(LIBOSMOGSM_LIBS) \
$(NULL)

View File

@ -0,0 +1,35 @@
dnl Process this file with autoconf to produce a configure script
AC_INIT([trxcon], [0.0.0])
AM_INIT_AUTOMAKE
dnl kernel style compile messages
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
dnl checks for programs
AC_PROG_MAKE_SET
AC_PROG_CC
AC_PROG_INSTALL
dnl checks for libraries
PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore)
PKG_CHECK_MODULES(LIBOSMOCODING, libosmocoding)
PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm)
dnl checks for header files
AC_HEADER_STDC
AC_ARG_ENABLE(sanitize,
[AS_HELP_STRING(
[--enable-sanitize],
[Compile with address sanitizer enabled],
)], [sanitize=$enableval], [sanitize="no"])
if test x"$sanitize" = x"yes"
then
CFLAGS="$CFLAGS -fsanitize=address -fsanitize=undefined"
CPPFLAGS="$CPPFLAGS -fsanitize=address -fsanitize=undefined"
fi
dnl Checks for typedefs, structures and compiler characteristics
AC_OUTPUT(
Makefile)

809
src/host/trxcon/l1ctl.c Normal file
View File

@ -0,0 +1,809 @@
/*
* OsmocomBB <-> SDR connection bridge
* GSM L1 control interface handlers
*
* (C) 2014 by Sylvain Munaut <tnt@246tNt.com>
* (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <arpa/inet.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/select.h>
#include <osmocom/gsm/gsm_utils.h>
#include "trxcon.h"
#include "logging.h"
#include "l1ctl_link.h"
#include "l1ctl_proto.h"
#include "trx_if.h"
#include "sched_trx.h"
static struct msgb *l1ctl_alloc_msg(uint8_t msg_type)
{
struct l1ctl_hdr *l1h;
struct msgb *msg;
/**
* Each L1CTL message gets its own length pushed in front
* before sending. This is why we need this small headroom.
*/
msg = msgb_alloc_headroom(L1CTL_LENGTH + L1CTL_MSG_LEN_FIELD,
L1CTL_MSG_LEN_FIELD, "l1ctl_tx_msg");
if (!msg) {
LOGP(DL1C, LOGL_ERROR, "Failed to allocate memory\n");
return NULL;
}
msg->l1h = msgb_put(msg, sizeof(*l1h));
l1h = (struct l1ctl_hdr *) msg->l1h;
l1h->msg_type = msg_type;
return msg;
}
int l1ctl_tx_pm_conf(struct l1ctl_link *l1l, uint16_t band_arfcn,
int dbm, int last)
{
struct l1ctl_pm_conf *pmc;
struct msgb *msg;
msg = l1ctl_alloc_msg(L1CTL_PM_CONF);
if (!msg)
return -ENOMEM;
LOGP(DL1C, LOGL_DEBUG, "Send PM Conf (%s %d = %d dBm)\n",
gsm_band_name(gsm_arfcn2band(band_arfcn)),
band_arfcn &~ ARFCN_FLAG_MASK, dbm);
pmc = (struct l1ctl_pm_conf *) msgb_put(msg, sizeof(*pmc));
pmc->band_arfcn = htons(band_arfcn);
pmc->pm[0] = dbm2rxlev(dbm);
pmc->pm[1] = 0;
if (last) {
struct l1ctl_hdr *l1h = (struct l1ctl_hdr *) msg->l1h;
l1h->flags |= L1CTL_F_DONE;
}
return l1ctl_link_send(l1l, msg);
}
int l1ctl_tx_reset_ind(struct l1ctl_link *l1l, uint8_t type)
{
struct msgb *msg;
struct l1ctl_reset *res;
msg = l1ctl_alloc_msg(L1CTL_RESET_IND);
if (!msg)
return -ENOMEM;
LOGP(DL1C, LOGL_DEBUG, "Send Reset Ind (%u)\n", type);
res = (struct l1ctl_reset *) msgb_put(msg, sizeof(*res));
res->type = type;
return l1ctl_link_send(l1l, msg);
}
int l1ctl_tx_reset_conf(struct l1ctl_link *l1l, uint8_t type)
{
struct msgb *msg;
struct l1ctl_reset *res;
msg = l1ctl_alloc_msg(L1CTL_RESET_CONF);
if (!msg)
return -ENOMEM;
LOGP(DL1C, LOGL_DEBUG, "Send Reset Conf (%u)\n", type);
res = (struct l1ctl_reset *) msgb_put(msg, sizeof(*res));
res->type = type;
return l1ctl_link_send(l1l, msg);
}
int l1ctl_tx_fbsb_conf(struct l1ctl_link *l1l, uint8_t result,
struct l1ctl_info_dl *dl_info, uint8_t bsic)
{
struct l1ctl_fbsb_conf *conf;
struct l1ctl_info_dl *dl;
struct msgb *msg;
size_t len;
msg = l1ctl_alloc_msg(L1CTL_FBSB_CONF);
if (msg == NULL)
return -ENOMEM;
LOGP(DL1C, LOGL_DEBUG, "Send FBSB Conf (result=%u, bsic=%u)\n",
result, bsic);
/* Copy DL info provided by handler */
len = sizeof(struct l1ctl_info_dl);
dl = (struct l1ctl_info_dl *) msgb_put(msg, len);
memcpy(dl, dl_info, len);
talloc_free(dl_info);
/* Fill in FBSB payload: BSIC and sync result */
conf = (struct l1ctl_fbsb_conf *) msgb_put(msg, sizeof(*conf));
conf->result = result;
conf->bsic = bsic;
/* FIXME: set proper value */
conf->initial_freq_err = 0;
/* Ask SCH handler not to send L1CTL_FBSB_CONF anymore */
l1l->fbsb_conf_sent = 1;
/* Abort FBSB expire timer */
if (osmo_timer_pending(&l1l->fbsb_timer))
osmo_timer_del(&l1l->fbsb_timer);
return l1ctl_link_send(l1l, msg);
}
int l1ctl_tx_ccch_mode_conf(struct l1ctl_link *l1l, uint8_t mode)
{
struct l1ctl_ccch_mode_conf *conf;
struct msgb *msg;
msg = l1ctl_alloc_msg(L1CTL_CCCH_MODE_CONF);
if (msg == NULL)
return -ENOMEM;
conf = (struct l1ctl_ccch_mode_conf *) msgb_put(msg, sizeof(*conf));
conf->ccch_mode = mode;
return l1ctl_link_send(l1l, msg);
}
/**
* Handles both L1CTL_DATA_IND and L1CTL_TRAFFIC_IND.
*/
int l1ctl_tx_dt_ind(struct l1ctl_link *l1l, struct l1ctl_info_dl *data,
uint8_t *l2, size_t l2_len, bool traffic)
{
struct l1ctl_info_dl *dl;
struct msgb *msg;
uint8_t *msg_l2;
msg = l1ctl_alloc_msg(traffic ?
L1CTL_TRAFFIC_IND : L1CTL_DATA_IND);
if (msg == NULL)
return -ENOMEM;
/* Copy DL header */
dl = (struct l1ctl_info_dl *) msgb_put(msg, sizeof(*dl));
memcpy(dl, data, sizeof(*dl));
/* Copy the L2 payload if preset */
if (l2 && l2_len > 0) {
msg_l2 = (uint8_t *) msgb_put(msg, l2_len);
memcpy(msg_l2, l2, l2_len);
}
/* Put message to upper layers */
return l1ctl_link_send(l1l, msg);
}
int l1ctl_tx_rach_conf(struct l1ctl_link *l1l, uint32_t fn)
{
struct l1ctl_info_dl *dl;
struct msgb *msg;
size_t len;
msg = l1ctl_alloc_msg(L1CTL_RACH_CONF);
if (msg == NULL)
return -ENOMEM;
len = sizeof(struct l1ctl_info_dl);
dl = (struct l1ctl_info_dl *) msgb_put(msg, len);
memset(dl, 0x00, len);
dl->band_arfcn = htons(l1l->trx->band_arfcn);
dl->frame_nr = htonl(fn);
return l1ctl_link_send(l1l, msg);
}
/**
* Handles both L1CTL_DATA_CONF and L1CTL_TRAFFIC_CONF.
*/
int l1ctl_tx_dt_conf(struct l1ctl_link *l1l,
struct l1ctl_info_dl *data, bool traffic)
{
struct l1ctl_info_dl *dl;
struct msgb *msg;
size_t len;
msg = l1ctl_alloc_msg(traffic ?
L1CTL_TRAFFIC_CONF : L1CTL_DATA_CONF);
if (msg == NULL)
return -ENOMEM;
/* Copy DL frame header from source message */
len = sizeof(struct l1ctl_info_dl);
dl = (struct l1ctl_info_dl *) msgb_put(msg, len);
memcpy(dl, data, len);
return l1ctl_link_send(l1l, msg);
}
/* FBSB expire timer */
static void fbsb_timer_cb(void *data)
{
struct l1ctl_link *l1l = (struct l1ctl_link *) data;
struct l1ctl_fbsb_conf *conf;
struct l1ctl_info_dl *dl;
struct msgb *msg;
size_t len;
msg = l1ctl_alloc_msg(L1CTL_FBSB_CONF);
if (msg == NULL)
return;
LOGP(DL1C, LOGL_DEBUG, "Send FBSB Conf (result=255, bsic=0)\n");
/* Compose DL info header */
len = sizeof(struct l1ctl_info_dl);
dl = (struct l1ctl_info_dl *) msgb_put(msg, len);
memset(dl, 0x00, len);
/* Fill in current ARFCN */
dl->band_arfcn = htons(l1l->trx->band_arfcn);
/* Fill in FBSB payload: BSIC and sync result */
conf = (struct l1ctl_fbsb_conf *) msgb_put(msg, sizeof(*conf));
conf->result = 255;
conf->bsic = 0;
/* Ask SCH handler not to send L1CTL_FBSB_CONF anymore */
l1l->fbsb_conf_sent = 1;
l1ctl_link_send(l1l, msg);
}
static int l1ctl_rx_fbsb_req(struct l1ctl_link *l1l, struct msgb *msg)
{
struct l1ctl_fbsb_req *fbsb;
uint16_t band_arfcn;
uint16_t timeout;
int rc = 0;
fbsb = (struct l1ctl_fbsb_req *) msg->l1h;
if (msgb_l1len(msg) < sizeof(*fbsb)) {
LOGP(DL1C, LOGL_ERROR, "MSG too short FBSB Req: %u\n",
msgb_l1len(msg));
rc = -EINVAL;
goto exit;
}
band_arfcn = ntohs(fbsb->band_arfcn);
timeout = ntohs(fbsb->timeout);
LOGP(DL1C, LOGL_NOTICE, "Received FBSB request (%s %d)\n",
gsm_band_name(gsm_arfcn2band(band_arfcn)),
band_arfcn &~ ARFCN_FLAG_MASK);
/* Reset scheduler and clock counter */
sched_trx_reset(l1l->trx, 1);
/* Configure a single timeslot */
if (fbsb->ccch_mode == CCCH_MODE_COMBINED)
sched_trx_configure_ts(l1l->trx, 0, GSM_PCHAN_CCCH_SDCCH4);
else
sched_trx_configure_ts(l1l->trx, 0, GSM_PCHAN_CCCH);
/* Ask SCH handler to send L1CTL_FBSB_CONF */
l1l->fbsb_conf_sent = 0;
/* Only if current ARFCN differs */
if (l1l->trx->band_arfcn != band_arfcn) {
/* Update current ARFCN */
l1l->trx->band_arfcn = band_arfcn;
/* Tune transceiver to required ARFCN */
trx_if_cmd_rxtune(l1l->trx, band_arfcn);
trx_if_cmd_txtune(l1l->trx, band_arfcn);
}
trx_if_cmd_poweron(l1l->trx);
/* Start FBSB expire timer */
/* TODO: share FRAME_DURATION_uS=4615 from scheduler.c */
l1l->fbsb_timer.data = l1l;
l1l->fbsb_timer.cb = fbsb_timer_cb;
osmo_timer_schedule(&l1l->fbsb_timer, 0, timeout * 4615);
exit:
msgb_free(msg);
return rc;
}
static int l1ctl_rx_pm_req(struct l1ctl_link *l1l, struct msgb *msg)
{
uint16_t arfcn_start, arfcn_stop;
struct l1ctl_pm_req *pmr;
int rc = 0;
pmr = (struct l1ctl_pm_req *) msg->l1h;
if (msgb_l1len(msg) < sizeof(*pmr)) {
LOGP(DL1C, LOGL_ERROR, "MSG too short PM Req: %u\n",
msgb_l1len(msg));
rc = -EINVAL;
goto exit;
}
arfcn_start = ntohs(pmr->range.band_arfcn_from);
arfcn_stop = ntohs(pmr->range.band_arfcn_to);
LOGP(DL1C, LOGL_NOTICE, "Received power measurement "
"request (%s: %d -> %d)\n",
gsm_band_name(gsm_arfcn2band(arfcn_start)),
arfcn_start &~ ARFCN_FLAG_MASK,
arfcn_stop &~ ARFCN_FLAG_MASK);
/* Send measurement request to transceiver */
rc = trx_if_cmd_measure(l1l->trx, arfcn_start, arfcn_stop);
exit:
msgb_free(msg);
return rc;
}
static int l1ctl_rx_reset_req(struct l1ctl_link *l1l, struct msgb *msg)
{
struct l1ctl_reset *res;
int rc = 0;
res = (struct l1ctl_reset *) msg->l1h;
if (msgb_l1len(msg) < sizeof(*res)) {
LOGP(DL1C, LOGL_ERROR, "MSG too short Reset Req: %u\n",
msgb_l1len(msg));
rc = -EINVAL;
goto exit;
}
LOGP(DL1C, LOGL_NOTICE, "Received reset request (%u)\n",
res->type);
switch (res->type) {
case L1CTL_RES_T_FULL:
/* TODO: implement trx_if_reset() */
trx_if_cmd_poweroff(l1l->trx);
trx_if_cmd_echo(l1l->trx);
/* Fall through */
case L1CTL_RES_T_SCHED:
sched_trx_reset(l1l->trx, 1);
break;
default:
LOGP(DL1C, LOGL_ERROR, "Unknown L1CTL_RESET_REQ type\n");
goto exit;
}
/* Confirm */
rc = l1ctl_tx_reset_conf(l1l, res->type);
exit:
msgb_free(msg);
return rc;
}
static int l1ctl_rx_echo_req(struct l1ctl_link *l1l, struct msgb *msg)
{
struct l1ctl_hdr *l1h;
LOGP(DL1C, LOGL_NOTICE, "Recv Echo Req\n");
LOGP(DL1C, LOGL_NOTICE, "Send Echo Conf\n");
/* Nothing to do, just send it back */
l1h = (struct l1ctl_hdr *) msg->l1h;
l1h->msg_type = L1CTL_ECHO_CONF;
msg->data = msg->l1h;
return l1ctl_link_send(l1l, msg);
}
static int l1ctl_rx_ccch_mode_req(struct l1ctl_link *l1l, struct msgb *msg)
{
struct l1ctl_ccch_mode_req *req;
struct trx_ts *ts;
int mode, rc = 0;
req = (struct l1ctl_ccch_mode_req *) msg->l1h;
if (msgb_l1len(msg) < sizeof(*req)) {
LOGP(DL1C, LOGL_ERROR, "MSG too short Reset Req: %u\n",
msgb_l1len(msg));
rc = -EINVAL;
goto exit;
}
LOGP(DL1C, LOGL_NOTICE, "Received CCCH mode request (%s)\n",
req->ccch_mode == CCCH_MODE_COMBINED ?
"combined" : "not combined");
/* Make sure that TS0 is allocated and configured */
ts = l1l->trx->ts_list[0];
if (ts == NULL || ts->mf_layout == NULL) {
LOGP(DL1C, LOGL_ERROR, "TS0 is not configured");
rc = -EINVAL;
goto exit;
}
/* Choose corresponding channel combination */
mode = req->ccch_mode == CCCH_MODE_COMBINED ?
GSM_PCHAN_CCCH_SDCCH4 : GSM_PCHAN_CCCH;
/* Do nothing if the current mode matches required */
if (ts->mf_layout->chan_config != mode)
rc = sched_trx_configure_ts(l1l->trx, 0, mode);
/* Confirm reconfiguration */
if (!rc)
rc = l1ctl_tx_ccch_mode_conf(l1l, req->ccch_mode);
exit:
msgb_free(msg);
return rc;
}
static int l1ctl_rx_rach_req(struct l1ctl_link *l1l, struct msgb *msg)
{
struct l1ctl_rach_req *req;
struct l1ctl_info_ul *ul;
struct trx_ts_prim *prim;
uint8_t chan_nr, link_id;
size_t len;
int rc;
ul = (struct l1ctl_info_ul *) msg->l1h;
req = (struct l1ctl_rach_req *) ul->payload;
len = sizeof(struct l1ctl_rach_req);
/* Convert offset value to host format */
req->offset = ntohs(req->offset);
/**
* FIXME: l1ctl_info_ul doesn't provide channel description
* FIXME: Can we use other than TS0?
*/
chan_nr = 0x88;
link_id = 0x00;
LOGP(DL1C, LOGL_NOTICE, "Received RACH request "
"(offset=%u ra=0x%02x)\n", req->offset, req->ra);
/* Init a new primitive */
rc = sched_prim_init(l1l->trx, &prim, len, chan_nr, link_id);
if (rc)
goto exit;
/**
* Push this primitive to transmit queue
*
* FIXME: what if requested TS is not configured?
* Or what if one (such as TCH) has no TRXC_RACH slots?
*/
rc = sched_prim_push(l1l->trx, prim, chan_nr);
if (rc) {
talloc_free(prim);
goto exit;
}
/* Fill in the payload */
memcpy(prim->payload, req, len);
exit:
msgb_free(msg);
return rc;
}
static int l1ctl_rx_dm_est_req(struct l1ctl_link *l1l, struct msgb *msg)
{
enum gsm_phys_chan_config config;
struct l1ctl_dm_est_req *est_req;
struct l1ctl_info_ul *ul;
struct trx_ts *ts;
uint16_t band_arfcn;
uint8_t chan_nr, tn;
int rc = 0;
ul = (struct l1ctl_info_ul *) msg->l1h;
est_req = (struct l1ctl_dm_est_req *) ul->payload;
band_arfcn = ntohs(est_req->h0.band_arfcn);
chan_nr = ul->chan_nr;
LOGP(DL1C, LOGL_NOTICE, "Received L1CTL_DM_EST_REQ (arfcn=%u, "
"chan_nr=0x%02x, tsc=%u, tch_mode=0x%02x)\n", (band_arfcn &~ ARFCN_FLAG_MASK),
chan_nr, est_req->tsc, est_req->tch_mode);
if (est_req->h) {
LOGP(DL1C, LOGL_ERROR, "FHSS is not supported\n");
rc = -ENOTSUP;
goto exit;
}
/* Update TSC (Training Sequence Code) */
l1l->trx->tsc = est_req->tsc;
/* Determine channel config */
config = sched_trx_chan_nr2pchan_config(chan_nr);
if (config == GSM_PCHAN_NONE) {
LOGP(DL1C, LOGL_ERROR, "Couldn't determine channel config\n");
rc = -EINVAL;
goto exit;
}
/* Determine TS index */
tn = chan_nr & 0x7;
if (tn > 7) {
LOGP(DL1C, LOGL_ERROR, "Incorrect TS index %u\n", tn);
rc = -EINVAL;
goto exit;
}
/* Configure requested TS */
rc = sched_trx_configure_ts(l1l->trx, tn, config);
ts = l1l->trx->ts_list[tn];
if (rc) {
rc = -EINVAL;
goto exit;
}
/* Deactivate all lchans */
sched_trx_deactivate_all_lchans(ts);
/* Activate only requested lchans */
rc = sched_trx_set_lchans(ts, chan_nr, 1, est_req->tch_mode);
if (rc) {
LOGP(DL1C, LOGL_ERROR, "Couldn't activate requested lchans\n");
rc = -EINVAL;
goto exit;
}
exit:
msgb_free(msg);
return rc;
}
static int l1ctl_rx_dm_rel_req(struct l1ctl_link *l1l, struct msgb *msg)
{
LOGP(DL1C, LOGL_NOTICE, "Received L1CTL_DM_REL_REQ, "
"switching back to CCCH\n");
/* Reset scheduler */
sched_trx_reset(l1l->trx, 0);
msgb_free(msg);
return 0;
}
/**
* Handles both L1CTL_DATA_REQ and L1CTL_TRAFFIC_REQ.
*/
static int l1ctl_rx_dt_req(struct l1ctl_link *l1l,
struct msgb *msg, bool traffic)
{
struct l1ctl_info_ul *ul;
struct trx_ts_prim *prim;
uint8_t chan_nr, link_id;
size_t payload_len;
int rc;
/* Extract UL frame header */
ul = (struct l1ctl_info_ul *) msg->l1h;
/* Calculate the payload len */
msg->l2h = ul->payload;
payload_len = msgb_l2len(msg);
/* Obtain channel description */
chan_nr = ul->chan_nr;
link_id = ul->link_id & 0x40;
LOGP(DL1D, LOGL_DEBUG, "Recv %s Req (chan_nr=0x%02x, "
"link_id=0x%02x, len=%zu)\n", traffic ? "TRAFFIC" : "DATA",
chan_nr, link_id, payload_len);
/* Init a new primitive */
rc = sched_prim_init(l1l->trx, &prim, payload_len,
chan_nr, link_id);
if (rc)
goto exit;
/* Push this primitive to transmit queue */
rc = sched_prim_push(l1l->trx, prim, chan_nr);
if (rc) {
talloc_free(prim);
goto exit;
}
/* Fill in the payload */
memcpy(prim->payload, ul->payload, payload_len);
exit:
msgb_free(msg);
return rc;
}
static int l1ctl_rx_param_req(struct l1ctl_link *l1l, struct msgb *msg)
{
struct l1ctl_par_req *par_req;
struct l1ctl_info_ul *ul;
int rc = 0;
ul = (struct l1ctl_info_ul *) msg->l1h;
par_req = (struct l1ctl_par_req *) ul->payload;
LOGP(DL1C, LOGL_NOTICE, "Received L1CTL_PARAM_REQ "
"(ta=%d, tx_power=%u)\n", par_req->ta, par_req->tx_power);
rc |= trx_if_cmd_setta(l1l->trx, par_req->ta);
l1l->trx->ta = par_req->ta;
l1l->trx->tx_power = par_req->tx_power;
msgb_free(msg);
return rc;
}
static int l1ctl_rx_tch_mode_req(struct l1ctl_link *l1l, struct msgb *msg)
{
struct l1ctl_tch_mode_req *req;
struct trx_lchan_state *lchan;
struct trx_ts *ts;
int i;
req = (struct l1ctl_tch_mode_req *) msg->l1h;
LOGP(DL1C, LOGL_NOTICE, "Received L1CTL_TCH_MODE_REQ "
"(tch_mode=%u, audio_mode=%u)\n", req->tch_mode, req->audio_mode);
/* Iterate over timeslot list */
for (i = 0; i < TRX_TS_COUNT; i++) {
/* Timeslot is not allocated */
ts = l1l->trx->ts_list[i];
if (ts == NULL)
continue;
/* Timeslot is not configured */
if (ts->mf_layout == NULL)
continue;
/* Iterate over all allocated lchans */
llist_for_each_entry(lchan, &ts->lchans, list) {
/* Omit inactive channels */
if (!lchan->active)
continue;
/* Set TCH mode */
lchan->tch_mode = req->tch_mode;
}
}
/* TODO: do we need to care about audio_mode? */
msgb_free(msg);
return 0;
}
static int l1ctl_rx_crypto_req(struct l1ctl_link *l1l, struct msgb *msg)
{
struct l1ctl_crypto_req *req;
struct l1ctl_info_ul *ul;
struct trx_ts *ts;
uint8_t tn;
int rc = 0;
ul = (struct l1ctl_info_ul *) msg->l1h;
req = (struct l1ctl_crypto_req *) ul->payload;
LOGP(DL1C, LOGL_NOTICE, "L1CTL_CRYPTO_REQ (algo=A5/%u, key_len=%u)\n",
req->algo, req->key_len);
/* Determine TS index */
tn = ul->chan_nr & 0x7;
if (tn > 7) {
LOGP(DL1C, LOGL_ERROR, "Incorrect TS index %u\n", tn);
rc = -EINVAL;
goto exit;
}
/* Make sure that required TS is allocated and configured */
ts = l1l->trx->ts_list[tn];
if (ts == NULL || ts->mf_layout == NULL) {
LOGP(DL1C, LOGL_ERROR, "TS %u is not configured\n", tn);
rc = -EINVAL;
goto exit;
}
/* Poke scheduler */
rc = sched_trx_start_ciphering(ts, req->algo, req->key, req->key_len);
if (rc) {
LOGP(DL1C, LOGL_ERROR, "Couldn't configure ciphering\n");
rc = -EINVAL;
goto exit;
}
exit:
msgb_free(msg);
return rc;
}
int l1ctl_rx_cb(struct l1ctl_link *l1l, struct msgb *msg)
{
struct l1ctl_hdr *l1h;
l1h = (struct l1ctl_hdr *) msg->l1h;
msg->l1h = l1h->data;
switch (l1h->msg_type) {
case L1CTL_FBSB_REQ:
return l1ctl_rx_fbsb_req(l1l, msg);
case L1CTL_PM_REQ:
return l1ctl_rx_pm_req(l1l, msg);
case L1CTL_RESET_REQ:
return l1ctl_rx_reset_req(l1l, msg);
case L1CTL_ECHO_REQ:
return l1ctl_rx_echo_req(l1l, msg);
case L1CTL_CCCH_MODE_REQ:
return l1ctl_rx_ccch_mode_req(l1l, msg);
case L1CTL_RACH_REQ:
return l1ctl_rx_rach_req(l1l, msg);
case L1CTL_DM_EST_REQ:
return l1ctl_rx_dm_est_req(l1l, msg);
case L1CTL_DM_REL_REQ:
return l1ctl_rx_dm_rel_req(l1l, msg);
case L1CTL_DATA_REQ:
return l1ctl_rx_dt_req(l1l, msg, false);
case L1CTL_TRAFFIC_REQ:
return l1ctl_rx_dt_req(l1l, msg, true);
case L1CTL_PARAM_REQ:
return l1ctl_rx_param_req(l1l, msg);
case L1CTL_TCH_MODE_REQ:
return l1ctl_rx_tch_mode_req(l1l, msg);
case L1CTL_CRYPTO_REQ:
return l1ctl_rx_crypto_req(l1l, msg);
default:
LOGP(DL1C, LOGL_ERROR, "Unknown MSG type %u: %s\n", l1h->msg_type,
osmo_hexdump(msgb_data(msg), msgb_length(msg)));
msgb_free(msg);
return -EINVAL;
}
}
void l1ctl_shutdown_cb(struct l1ctl_link *l1l)
{
/* Abort FBSB expire timer */
if (osmo_timer_pending(&l1l->fbsb_timer))
osmo_timer_del(&l1l->fbsb_timer);
}

25
src/host/trxcon/l1ctl.h Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#include <stdint.h>
#include <osmocom/core/msgb.h>
#include "l1ctl_link.h"
#include "l1ctl_proto.h"
/* Event handlers */
int l1ctl_rx_cb(struct l1ctl_link *l1l, struct msgb *msg);
void l1ctl_shutdown_cb(struct l1ctl_link *l1l);
int l1ctl_tx_fbsb_conf(struct l1ctl_link *l1l, uint8_t result,
struct l1ctl_info_dl *dl_info, uint8_t bsic);
int l1ctl_tx_ccch_mode_conf(struct l1ctl_link *l1l, uint8_t mode);
int l1ctl_tx_pm_conf(struct l1ctl_link *l1l, uint16_t band_arfcn,
int dbm, int last);
int l1ctl_tx_reset_conf(struct l1ctl_link *l1l, uint8_t type);
int l1ctl_tx_reset_ind(struct l1ctl_link *l1l, uint8_t type);
int l1ctl_tx_dt_ind(struct l1ctl_link *l1l, struct l1ctl_info_dl *data,
uint8_t *l2, size_t l2_len, bool traffic);
int l1ctl_tx_dt_conf(struct l1ctl_link *l1l,
struct l1ctl_info_dl *data, bool traffic);
int l1ctl_tx_rach_conf(struct l1ctl_link *l1l, uint32_t fn);

View File

@ -0,0 +1,310 @@
/*
* OsmocomBB <-> SDR connection bridge
* GSM L1 control socket (/tmp/osmocom_l2) handlers
*
* (C) 2013 by Sylvain Munaut <tnt@246tNt.com>
* (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <osmocom/core/fsm.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/select.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/write_queue.h>
#include "trxcon.h"
#include "logging.h"
#include "l1ctl_link.h"
#include "l1ctl.h"
static struct value_string l1ctl_evt_names[] = {
{ 0, NULL } /* no events? */
};
static struct osmo_fsm_state l1ctl_fsm_states[] = {
[L1CTL_STATE_IDLE] = {
.out_state_mask = GEN_MASK(L1CTL_STATE_CONNECTED),
.name = "IDLE",
},
[L1CTL_STATE_CONNECTED] = {
.out_state_mask = GEN_MASK(L1CTL_STATE_IDLE),
.name = "CONNECTED",
},
};
static struct osmo_fsm l1ctl_fsm = {
.name = "l1ctl_link_fsm",
.states = l1ctl_fsm_states,
.num_states = ARRAY_SIZE(l1ctl_fsm_states),
.log_subsys = DL1C,
.event_names = l1ctl_evt_names,
};
static int l1ctl_link_read_cb(struct osmo_fd *bfd)
{
struct l1ctl_link *l1l = (struct l1ctl_link *) bfd->data;
struct msgb *msg;
uint16_t len;
int rc;
/* Attempt to read from socket */
rc = read(bfd->fd, &len, L1CTL_MSG_LEN_FIELD);
if (rc < L1CTL_MSG_LEN_FIELD) {
LOGP(DL1D, LOGL_NOTICE, "L1CTL has lost connection\n");
if (rc >= 0)
rc = -EIO;
l1ctl_link_close_conn(l1l);
return rc;
}
/* Check message length */
len = ntohs(len);
if (len > L1CTL_LENGTH) {
LOGP(DL1D, LOGL_ERROR, "Length is too big: %u\n", len);
return -EINVAL;
}
/* Allocate a new msg */
msg = msgb_alloc_headroom(L1CTL_LENGTH + L1CTL_HEADROOM,
L1CTL_HEADROOM, "l1ctl_rx_msg");
if (!msg) {
LOGP(DL1D, LOGL_ERROR, "Failed to allocate msg\n");
return -ENOMEM;
}
msg->l1h = msgb_put(msg, len);
rc = read(bfd->fd, msg->l1h, msgb_l1len(msg));
if (rc != len) {
LOGP(DL1D, LOGL_ERROR, "Can not read data: len=%d < rc=%d: "
"%s\n", len, rc, strerror(errno));
msgb_free(msg);
return rc;
}
/* Debug print */
LOGP(DL1D, LOGL_DEBUG, "RX: '%s'\n",
osmo_hexdump(msg->data, msg->len));
/* Call L1CTL handler */
l1ctl_rx_cb(l1l, msg);
return 0;
}
static int l1ctl_link_write_cb(struct osmo_fd *bfd, struct msgb *msg)
{
int len;
if (bfd->fd <= 0)
return -EINVAL;
len = write(bfd->fd, msg->data, msg->len);
if (len != msg->len) {
LOGP(DL1D, LOGL_ERROR, "Failed to write data: "
"written (%d) < msg_len (%d)\n", len, msg->len);
return -1;
}
return 0;
}
/* Connection handler */
static int l1ctl_link_accept(struct osmo_fd *bfd, unsigned int flags)
{
struct l1ctl_link *l1l = (struct l1ctl_link *) bfd->data;
struct osmo_fd *conn_bfd = &l1l->wq.bfd;
struct sockaddr_un un_addr;
socklen_t len;
int cfd;
len = sizeof(un_addr);
cfd = accept(bfd->fd, (struct sockaddr *) &un_addr, &len);
if (cfd < 0) {
LOGP(DL1C, LOGL_ERROR, "Failed to accept a new connection\n");
return -1;
}
/* Check if we already have an active connection */
if (conn_bfd->fd != -1) {
LOGP(DL1C, LOGL_NOTICE, "A new connection rejected: "
"we already have another active\n");
close(cfd);
return 0;
}
osmo_wqueue_init(&l1l->wq, 100);
INIT_LLIST_HEAD(&conn_bfd->list);
l1l->wq.write_cb = l1ctl_link_write_cb;
l1l->wq.read_cb = l1ctl_link_read_cb;
conn_bfd->when = BSC_FD_READ;
conn_bfd->data = l1l;
conn_bfd->fd = cfd;
if (osmo_fd_register(conn_bfd) != 0) {
LOGP(DL1C, LOGL_ERROR, "Failed to register new connection fd\n");
close(conn_bfd->fd);
conn_bfd->fd = -1;
return -1;
}
osmo_fsm_inst_dispatch(trxcon_fsm, L1CTL_EVENT_CONNECT, l1l);
osmo_fsm_inst_state_chg(l1l->fsm, L1CTL_STATE_CONNECTED, 0, 0);
LOGP(DL1C, LOGL_NOTICE, "L1CTL has a new connection\n");
return 0;
}
int l1ctl_link_send(struct l1ctl_link *l1l, struct msgb *msg)
{
uint16_t *len;
/* Debug print */
LOGP(DL1D, LOGL_DEBUG, "TX: '%s'\n",
osmo_hexdump(msg->data, msg->len));
if (msg->l1h != msg->data)
LOGP(DL1D, LOGL_INFO, "Message L1 header != Message Data\n");
/* Prepend 16-bit length before sending */
len = (uint16_t *) msgb_push(msg, L1CTL_MSG_LEN_FIELD);
*len = htons(msg->len - L1CTL_MSG_LEN_FIELD);
if (osmo_wqueue_enqueue(&l1l->wq, msg) != 0) {
LOGP(DL1D, LOGL_ERROR, "Failed to enqueue msg!\n");
msgb_free(msg);
return -EIO;
}
return 0;
}
int l1ctl_link_close_conn(struct l1ctl_link *l1l)
{
struct osmo_fd *conn_bfd = &l1l->wq.bfd;
if (conn_bfd->fd <= 0)
return -EINVAL;
/* Close connection socket */
osmo_fd_unregister(conn_bfd);
close(conn_bfd->fd);
conn_bfd->fd = -1;
/* Clear pending messages */
osmo_wqueue_clear(&l1l->wq);
osmo_fsm_inst_dispatch(trxcon_fsm, L1CTL_EVENT_DISCONNECT, l1l);
osmo_fsm_inst_state_chg(l1l->fsm, L1CTL_STATE_IDLE, 0, 0);
return 0;
}
int l1ctl_link_init(struct l1ctl_link **l1l, const char *sock_path)
{
struct l1ctl_link *l1l_new;
struct osmo_fd *bfd;
int rc;
LOGP(DL1C, LOGL_NOTICE, "Init L1CTL link (%s)\n", sock_path);
l1l_new = talloc_zero(tall_trx_ctx, struct l1ctl_link);
if (!l1l_new) {
LOGP(DL1C, LOGL_ERROR, "Failed to allocate memory\n");
return -ENOMEM;
}
/* Create a socket and bind handlers */
bfd = &l1l_new->listen_bfd;
rc = osmo_sock_unix_init_ofd(bfd, SOCK_STREAM, 0, sock_path,
OSMO_SOCK_F_BIND);
if (rc < 0) {
LOGP(DL1C, LOGL_ERROR, "Could not create UNIX socket: %s\n",
strerror(errno));
talloc_free(l1l_new);
return rc;
}
/* Bind shutdown handler */
l1l_new->shutdown_cb = l1ctl_shutdown_cb;
/* Bind connection handler */
bfd->cb = l1ctl_link_accept;
bfd->when = BSC_FD_READ;
bfd->data = l1l_new;
/**
* To be able to accept first connection and
* drop others, it should be set to -1
*/
l1l_new->wq.bfd.fd = -1;
/* Allocate a new dedicated state machine */
osmo_fsm_register(&l1ctl_fsm);
l1l_new->fsm = osmo_fsm_inst_alloc(&l1ctl_fsm, l1l_new,
NULL, LOGL_DEBUG, "l1ctl_link");
*l1l = l1l_new;
return 0;
}
void l1ctl_link_shutdown(struct l1ctl_link *l1l)
{
struct osmo_fd *listen_bfd;
/* May be unallocated due to init error */
if (!l1l)
return;
LOGP(DL1C, LOGL_NOTICE, "Shutdown L1CTL link\n");
/* Call shutdown callback */
if (l1l->shutdown_cb != NULL)
l1l->shutdown_cb(l1l);
listen_bfd = &l1l->listen_bfd;
/* Check if we have an established connection */
if (l1l->wq.bfd.fd != -1)
l1ctl_link_close_conn(l1l);
/* Unbind listening socket */
if (listen_bfd->fd != -1) {
osmo_fd_unregister(listen_bfd);
close(listen_bfd->fd);
listen_bfd->fd = -1;
}
osmo_fsm_inst_free(l1l->fsm);
talloc_free(l1l);
}

View File

@ -0,0 +1,48 @@
#pragma once
#include <stdint.h>
#include <osmocom/core/write_queue.h>
#include <osmocom/core/select.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/fsm.h>
#define L1CTL_LENGTH 256
#define L1CTL_HEADROOM 32
/**
* Each L1CTL message gets its own length pushed
* as two bytes in front before sending.
*/
#define L1CTL_MSG_LEN_FIELD 2
/* Forward declaration to avoid mutual include */
struct trx_instance;
enum l1ctl_fsm_states {
L1CTL_STATE_IDLE = 0,
L1CTL_STATE_CONNECTED,
};
struct l1ctl_link {
struct osmo_fsm_inst *fsm;
struct osmo_fd listen_bfd;
struct osmo_wqueue wq;
/* Bind TRX instance */
struct trx_instance *trx;
/* L1CTL handlers specific */
struct osmo_timer_list fbsb_timer;
uint8_t fbsb_conf_sent;
/* Shutdown callback */
void (*shutdown_cb)(struct l1ctl_link *l1l);
};
int l1ctl_link_init(struct l1ctl_link **l1l, const char *sock_path);
void l1ctl_link_shutdown(struct l1ctl_link *l1l);
int l1ctl_link_send(struct l1ctl_link *l1l, struct msgb *msg);
int l1ctl_link_close_conn(struct l1ctl_link *l1l);

View File

@ -0,0 +1 @@
../../../include/l1ctl_proto.h

88
src/host/trxcon/logging.c Normal file
View File

@ -0,0 +1,88 @@
/*
* OsmocomBB <-> SDR connection bridge
*
* (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <osmocom/core/application.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/utils.h>
#include "logging.h"
static struct log_info_cat trx_log_info_cat[] = {
[DAPP] = {
.name = "DAPP",
.description = "Application",
.color = "\033[1;35m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DL1C] = {
.name = "DL1C",
.description = "Layer 1 control interface",
.color = "\033[1;31m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DL1D] = {
.name = "DL1D",
.description = "Layer 1 data",
.color = "\033[1;31m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DTRX] = {
.name = "DTRX",
.description = "Transceiver control interface",
.color = "\033[1;33m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DTRXD] = {
.name = "DTRXD",
.description = "Transceiver data interface",
.color = "\033[1;33m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DSCH] = {
.name = "DSCH",
.description = "Scheduler management",
.color = "\033[1;36m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
[DSCHD] = {
.name = "DSCHD",
.description = "Scheduler data",
.color = "\033[1;36m",
.enabled = 1, .loglevel = LOGL_NOTICE,
},
};
static const struct log_info trx_log_info = {
.cat = trx_log_info_cat,
.num_cat = ARRAY_SIZE(trx_log_info_cat),
};
int trx_log_init(const char *category_mask)
{
osmo_init_logging(&trx_log_info);
if (category_mask)
log_parse_category_mask(osmo_stderr_target, category_mask);
return 0;
}

17
src/host/trxcon/logging.h Normal file
View File

@ -0,0 +1,17 @@
#pragma once
#include <osmocom/core/logging.h>
#define DEBUG_DEFAULT "DAPP:DL1C:DL1D:DTRX:DTRXD:DSCH:DSCHD"
enum {
DAPP,
DL1C,
DL1D,
DTRX,
DTRXD,
DSCH,
DSCHD,
};
int trx_log_init(const char *category_mask);

View File

@ -0,0 +1,215 @@
/*
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: clock synchronization
*
* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
* (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
* (C) 2015 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 <errno.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/bits.h>
#include <osmocom/core/fsm.h>
#include <osmocom/gsm/a5.h>
#include "scheduler.h"
#include "logging.h"
#include "trx_if.h"
#include "trxcon.h"
#define FRAME_DURATION_uS 4615
#define MAX_FN_SKEW 50
#define TRX_LOSS_FRAMES 400
static void sched_clck_tick(void *data)
{
struct trx_sched *sched = (struct trx_sched *) data;
struct timeval tv_now, *tv_clock;
int32_t elapsed;
/* Check if transceiver is still alive */
if (sched->fn_counter_lost++ == TRX_LOSS_FRAMES) {
LOGP(DSCH, LOGL_DEBUG, "No more clock from transceiver\n");
sched->state = SCH_CLCK_STATE_WAIT;
return;
}
/* Get actual / previous frame time */
gettimeofday(&tv_now, NULL);
tv_clock = &sched->clock;
elapsed = (tv_now.tv_sec - tv_clock->tv_sec) * 1000000
+ (tv_now.tv_usec - tv_clock->tv_usec);
/* If someone played with clock, or if the process stalled */
if (elapsed > FRAME_DURATION_uS * MAX_FN_SKEW || elapsed < 0) {
LOGP(DSCH, LOGL_NOTICE, "PC clock skew: "
"elapsed uS %d\n", elapsed);
sched->state = SCH_CLCK_STATE_WAIT;
return;
}
/* Schedule next FN clock */
while (elapsed > FRAME_DURATION_uS / 2) {
tv_clock->tv_usec += FRAME_DURATION_uS;
elapsed -= FRAME_DURATION_uS;
if (tv_clock->tv_usec >= 1000000) {
tv_clock->tv_sec++;
tv_clock->tv_usec -= 1000000;
}
sched->fn_counter_proc = (sched->fn_counter_proc + 1)
% GSM_HYPERFRAME;
/* Call frame callback */
if (sched->clock_cb)
sched->clock_cb(sched);
}
osmo_timer_schedule(&sched->clock_timer, 0,
FRAME_DURATION_uS - elapsed);
}
static void sched_clck_correct(struct trx_sched *sched,
struct timeval *tv_now, uint32_t fn)
{
sched->fn_counter_proc = fn;
/* Call frame callback */
if (sched->clock_cb)
sched->clock_cb(sched);
/* Schedule first FN clock */
memcpy(&sched->clock, tv_now, sizeof(struct timeval));
memset(&sched->clock_timer, 0, sizeof(sched->clock_timer));
sched->clock_timer.cb = sched_clck_tick;
sched->clock_timer.data = sched;
osmo_timer_schedule(&sched->clock_timer, 0, FRAME_DURATION_uS);
}
int sched_clck_handle(struct trx_sched *sched, uint32_t fn)
{
struct timeval tv_now, *tv_clock;
int32_t elapsed, elapsed_fn;
/* Reset lost counter */
sched->fn_counter_lost = 0;
/* Get actual / previous frame time */
gettimeofday(&tv_now, NULL);
tv_clock = &sched->clock;
/* If this is the first CLCK IND */
if (sched->state == SCH_CLCK_STATE_WAIT) {
sched_clck_correct(sched, &tv_now, fn);
LOGP(DSCH, LOGL_DEBUG, "Initial clock received: fn=%u\n", fn);
sched->state = SCH_CLCK_STATE_OK;
return 0;
}
LOGP(DSCH, LOGL_NOTICE, "Clock indication: fn=%u\n", fn);
osmo_timer_del(&sched->clock_timer);
/* Calculate elapsed time / frames since last processed fn */
elapsed = (tv_now.tv_sec - tv_clock->tv_sec) * 1000000
+ (tv_now.tv_usec - tv_clock->tv_usec);
elapsed_fn = (fn + GSM_HYPERFRAME - sched->fn_counter_proc)
% GSM_HYPERFRAME;
if (elapsed_fn >= 135774)
elapsed_fn -= GSM_HYPERFRAME;
/* Check for max clock skew */
if (elapsed_fn > MAX_FN_SKEW || elapsed_fn < -MAX_FN_SKEW) {
LOGP(DSCH, LOGL_NOTICE, "GSM clock skew: old fn=%u, "
"new fn=%u\n", sched->fn_counter_proc, fn);
sched_clck_correct(sched, &tv_now, fn);
return 0;
}
LOGP(DSCH, LOGL_INFO, "GSM clock jitter: %d\n",
elapsed_fn * FRAME_DURATION_uS - elapsed);
/* Too many frames have been processed already */
if (elapsed_fn < 0) {
/**
* Set clock to the time or last FN should
* have been transmitted
*/
tv_clock->tv_sec = tv_now.tv_sec;
tv_clock->tv_usec = tv_now.tv_usec +
(0 - elapsed_fn) * FRAME_DURATION_uS;
if (tv_clock->tv_usec >= 1000000) {
tv_clock->tv_sec++;
tv_clock->tv_usec -= 1000000;
}
/* Set time to the time our next FN has to be transmitted */
osmo_timer_schedule(&sched->clock_timer, 0,
FRAME_DURATION_uS * (1 - elapsed_fn));
return 0;
}
/* Transmit what we still need to transmit */
while (fn != sched->fn_counter_proc) {
sched->fn_counter_proc = (sched->fn_counter_proc + 1)
% GSM_HYPERFRAME;
/* Call frame callback */
if (sched->clock_cb)
sched->clock_cb(sched);
}
/* Schedule next FN to be transmitted */
memcpy(tv_clock, &tv_now, sizeof(struct timeval));
osmo_timer_schedule(&sched->clock_timer, 0, FRAME_DURATION_uS);
return 0;
}
void sched_clck_reset(struct trx_sched *sched)
{
/* Reset internal state */
sched->state = SCH_CLCK_STATE_WAIT;
/* Stop clock timer */
osmo_timer_del(&sched->clock_timer);
/* Flush counters */
sched->fn_counter_proc = 0;
sched->fn_counter_lost = 0;
}

View File

@ -0,0 +1,169 @@
/*
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: common routines for lchan handlers
*
* (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <errno.h>
#include <string.h>
#include <talloc.h>
#include <stdint.h>
#include <stdbool.h>
#include <arpa/inet.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/bits.h>
#include <osmocom/codec/codec.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>
#include <osmocom/gsm/protocol/gsm_08_58.h>
#include "l1ctl_proto.h"
#include "scheduler.h"
#include "sched_trx.h"
#include "logging.h"
#include "trx_if.h"
#include "trxcon.h"
#include "l1ctl.h"
/* GSM 05.02 Chapter 5.2.3 Normal Burst (NB) */
const uint8_t sched_nb_training_bits[8][26] = {
{
0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0,
0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1,
},
{
0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1,
1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1,
},
{
0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1,
0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0,
},
{
0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0,
},
{
0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0,
1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1,
},
{
0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0,
0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0,
},
{
1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1,
0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1,
},
{
1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0,
0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0,
},
};
int sched_send_dt_ind(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint8_t *l2, size_t l2_len,
int bit_error_count, bool dec_failed, bool traffic)
{
const struct trx_lchan_desc *lchan_desc;
struct l1ctl_info_dl dl_hdr;
/* Set up pointers */
lchan_desc = &trx_lchan_desc[lchan->type];
/* Fill in known downlink info */
dl_hdr.chan_nr = lchan_desc->chan_nr | ts->index;
dl_hdr.link_id = lchan_desc->link_id;
dl_hdr.band_arfcn = htons(trx->band_arfcn);
dl_hdr.frame_nr = htonl(lchan->rx_first_fn);
dl_hdr.rx_level = -(lchan->meas.rssi_sum / lchan->meas.rssi_num);
dl_hdr.num_biterr = bit_error_count;
/* FIXME: set proper values */
dl_hdr.snr = 0;
/* Mark frame as broken if so */
dl_hdr.fire_crc = dec_failed ? 2 : 0;
/* Put a packet to higher layers */
l1ctl_tx_dt_ind(trx->l1l, &dl_hdr, l2, l2_len, traffic);
return 0;
}
int sched_send_dt_conf(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, bool traffic)
{
const struct trx_lchan_desc *lchan_desc;
struct l1ctl_info_dl dl_hdr;
/* Set up pointers */
lchan_desc = &trx_lchan_desc[lchan->type];
/* Zero-initialize DL header, because we don't set all fields */
memset(&dl_hdr, 0x00, sizeof(struct l1ctl_info_dl));
/* Fill in known downlink info */
dl_hdr.chan_nr = lchan_desc->chan_nr | ts->index;
dl_hdr.link_id = lchan_desc->link_id;
dl_hdr.band_arfcn = htons(trx->band_arfcn);
dl_hdr.frame_nr = htonl(fn);
l1ctl_tx_dt_conf(trx->l1l, &dl_hdr, traffic);
return 0;
}
/**
* Composes a bad frame indication message
* according to the current tch_mode.
*
* @param l2 Pointer to allocated byte array
* @param tch_mode Current TCH mode
* @return How much bytes were written
*/
size_t sched_bad_frame_ind(uint8_t *l2, uint8_t rsl_cmode, uint8_t tch_mode)
{
/* BFI is only required for speech */
if (rsl_cmode != RSL_CMOD_SPD_SPEECH)
return 0;
switch (tch_mode) {
case GSM48_CMODE_SIGN:
case GSM48_CMODE_SPEECH_V1: /* Full Rate */
memset(l2, 0x00, GSM_FR_BYTES);
l2[0] = 0xd0;
return GSM_FR_BYTES;
case GSM48_CMODE_SPEECH_EFR: /* Enhanced Full Rate */
memset(l2, 0x00, GSM_EFR_BYTES);
l2[0] = 0xc0;
return GSM_EFR_BYTES;
case GSM48_CMODE_SPEECH_AMR: /* Adaptive Multi Rate */
/* FIXME: AMR is not implemented yet */
return 0;
default:
LOGP(DSCH, LOGL_ERROR, "Invalid TCH mode: %u\n", tch_mode);
return 0;
}
}

View File

@ -0,0 +1,298 @@
/*
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: logical channels, RX / TX handlers
*
* (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
* (C) 2015 by Alexander Chemeris <Alexander.Chemeris@fairwaves.co>
* (C) 2015 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 "sched_trx.h"
/* TODO: implement */
#define tx_pdtch_fn NULL
#define tx_tchh_fn NULL
#define rx_pdtch_fn NULL
#define rx_tchh_fn NULL
/* Forward declaration of handlers */
int rx_data_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
sbit_t *bits, int8_t rssi, int16_t toa256);
int tx_data_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid);
int rx_sch_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
sbit_t *bits, int8_t rssi, int16_t toa256);
int tx_rach_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid);
int rx_tchf_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
sbit_t *bits, int8_t rssi, int16_t toa256);
int tx_tchf_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid);
const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX] = {
{
TRXC_IDLE, "IDLE",
0x00, TRX_CH_LID_DEDIC,
0x00, 0x00,
/**
* MS: do nothing, save power...
* BTS: send dummy burst on C0
*/
NULL, NULL,
},
{
TRXC_FCCH, "FCCH",
0x00, TRX_CH_LID_DEDIC,
0x00, 0x00,
/* FCCH is handled by transceiver */
NULL, NULL,
},
{
TRXC_SCH, "SCH",
0x00, TRX_CH_LID_DEDIC,
0x00, TRX_CH_FLAG_AUTO,
/**
* We already have clock indications from TRX,
* but we also need BSIC (BCC / NCC) value.
*/
rx_sch_fn, NULL,
},
{
TRXC_BCCH, "BCCH",
0x80, TRX_CH_LID_DEDIC,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_AUTO,
rx_data_fn, NULL,
},
{
TRXC_RACH, "RACH",
0x88, TRX_CH_LID_DEDIC,
0x00, TRX_CH_FLAG_AUTO,
NULL, tx_rach_fn,
},
{
TRXC_CCCH, "CCCH",
0x90, TRX_CH_LID_DEDIC,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_AUTO,
rx_data_fn, NULL,
},
{
TRXC_TCHF, "TCH/F",
0x08, TRX_CH_LID_DEDIC,
8 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_tchf_fn, tx_tchf_fn,
},
{
TRXC_TCHH_0, "TCH/H(0)",
0x10, TRX_CH_LID_DEDIC,
6 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_tchh_fn, tx_tchh_fn,
},
{
TRXC_TCHH_1, "TCH/H(1)",
0x18, TRX_CH_LID_DEDIC,
6 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_tchh_fn, tx_tchh_fn,
},
{
TRXC_SDCCH4_0, "SDCCH/4(0)",
0x20, TRX_CH_LID_DEDIC,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_SDCCH4_1, "SDCCH/4(1)",
0x28, TRX_CH_LID_DEDIC,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_SDCCH4_2, "SDCCH/4(2)",
0x30, TRX_CH_LID_DEDIC,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_SDCCH4_3, "SDCCH/4(3)",
0x38, TRX_CH_LID_DEDIC,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_SDCCH8_0, "SDCCH/8(0)",
0x40, TRX_CH_LID_DEDIC,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_SDCCH8_1, "SDCCH/8(1)",
0x48, TRX_CH_LID_DEDIC,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_SDCCH8_2, "SDCCH/8(2)",
0x50, TRX_CH_LID_DEDIC,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_SDCCH8_3, "SDCCH/8(3)",
0x58, TRX_CH_LID_DEDIC,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_SDCCH8_4, "SDCCH/8(4)",
0x60, TRX_CH_LID_DEDIC,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_SDCCH8_5, "SDCCH/8(5)",
0x68, TRX_CH_LID_DEDIC,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_SDCCH8_6, "SDCCH/8(6)",
0x70, TRX_CH_LID_DEDIC,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_SDCCH8_7, "SDCCH/8(7)",
0x78, TRX_CH_LID_DEDIC,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_SACCHTF, "SACCH/TF",
0x08, TRX_CH_LID_SACCH,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_SACCHTH_0, "SACCH/TH(0)",
0x10, TRX_CH_LID_SACCH,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_SACCHTH_1, "SACCH/TH(1)",
0x18, TRX_CH_LID_SACCH,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_SACCH4_0, "SACCH/4(0)",
0x20, TRX_CH_LID_SACCH,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_SACCH4_1, "SACCH/4(1)",
0x28, TRX_CH_LID_SACCH,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_SACCH4_2, "SACCH/4(2)",
0x30, TRX_CH_LID_SACCH,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_SACCH4_3, "SACCH/4(3)",
0x38, TRX_CH_LID_SACCH,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_SACCH8_0, "SACCH/8(0)",
0x40, TRX_CH_LID_SACCH,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_SACCH8_1, "SACCH/8(1)",
0x48, TRX_CH_LID_SACCH,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_SACCH8_2, "SACCH/8(2)",
0x50, TRX_CH_LID_SACCH,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_SACCH8_3, "SACCH/8(3)",
0x58, TRX_CH_LID_SACCH,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_SACCH8_4, "SACCH/8(4)",
0x60, TRX_CH_LID_SACCH,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_SACCH8_5, "SACCH/8(5)",
0x68, TRX_CH_LID_SACCH,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_SACCH8_6, "SACCH/8(6)",
0x70, TRX_CH_LID_SACCH,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_SACCH8_7, "SACCH/8(7)",
0x78, TRX_CH_LID_SACCH,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_CBTX,
rx_data_fn, tx_data_fn,
},
{
TRXC_PDTCH, "PDTCH",
0x08, TRX_CH_LID_DEDIC,
12 * GSM_BURST_PL_LEN, TRX_CH_FLAG_PDCH,
rx_pdtch_fn, tx_pdtch_fn,
},
{
TRXC_PTCCH, "PTCCH",
0x08, TRX_CH_LID_DEDIC,
4 * GSM_BURST_PL_LEN, TRX_CH_FLAG_PDCH,
rx_data_fn, tx_data_fn,
},
};

View File

@ -0,0 +1,105 @@
/*
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: handlers for DL / UL bursts on logical channels
*
* (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/bits.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/coding/gsm0503_coding.h>
#include "l1ctl_proto.h"
#include "scheduler.h"
#include "sched_trx.h"
#include "logging.h"
#include "trx_if.h"
#include "trxcon.h"
#include "l1ctl.h"
/**
* 41-bit RACH synchronization sequence
* GSM 05.02 Chapter 5.2.7 Access burst (AB)
*/
static ubit_t rach_synch_seq[] = {
0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1,
1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0,
1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
};
/* Obtain a to-be-transmitted RACH burst */
int tx_rach_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid)
{
struct l1ctl_rach_req *req;
uint8_t burst[GSM_BURST_LEN];
uint8_t payload[36];
int rc;
/* Get the payload from a current primitive */
req = (struct l1ctl_rach_req *) lchan->prim->payload;
/* Delay RACH sending according to offset value */
if (req->offset-- > 0)
return 0;
/* Encode (8-bit) payload */
rc = gsm0503_rach_ext_encode(payload, req->ra, trx->bsic, false);
if (rc) {
LOGP(DSCHD, LOGL_ERROR, "Could not encode RACH burst\n");
/* Forget this primitive */
sched_prim_drop(lchan);
return rc;
}
/* Compose RACH burst */
memset(burst, 0, 8); /* TB */
memcpy(burst + 8, rach_synch_seq, 41); /* sync seq */
memcpy(burst + 49, payload, 36); /* payload */
memset(burst + 85, 0, 63); /* TB + GP */
LOGP(DSCHD, LOGL_DEBUG, "Transmitting RACH fn=%u\n", fn);
/* Forward burst to scheduler */
rc = sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst);
if (rc) {
/* Forget this primitive */
sched_prim_drop(lchan);
return rc;
}
/* Confirm RACH request */
l1ctl_tx_rach_conf(trx->l1l, fn);
/* Forget processed primitive */
sched_prim_drop(lchan);
return 0;
}

View File

@ -0,0 +1,134 @@
/*
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: handlers for DL / UL bursts on logical channels
*
* (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <errno.h>
#include <string.h>
#include <talloc.h>
#include <stdint.h>
#include <arpa/inet.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/bits.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/coding/gsm0503_coding.h>
#include "l1ctl_proto.h"
#include "scheduler.h"
#include "sched_trx.h"
#include "logging.h"
#include "trx_if.h"
#include "trxcon.h"
#include "l1ctl.h"
static void decode_sb(struct gsm_time *time, uint8_t *bsic, uint8_t *sb_info)
{
uint8_t t3p;
uint32_t sb;
sb = (sb_info[3] << 24)
| (sb_info[2] << 16)
| (sb_info[1] << 8)
| sb_info[0];
*bsic = (sb >> 2) & 0x3f;
/* TS 05.02 Chapter 3.3.2.2.1 SCH Frame Numbers */
time->t1 = ((sb >> 23) & 0x01)
| ((sb >> 7) & 0x1fe)
| ((sb << 9) & 0x600);
time->t2 = (sb >> 18) & 0x1f;
t3p = ((sb >> 24) & 0x01) | ((sb >> 15) & 0x06);
time->t3 = t3p * 10 + 1;
/* TS 05.02 Chapter 4.3.3 TDMA frame number */
time->fn = gsm_gsmtime2fn(time);
}
int rx_sch_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
sbit_t *bits, int8_t rssi, int16_t toa256)
{
sbit_t payload[2 * 39];
struct gsm_time time;
uint8_t sb_info[4];
uint8_t bsic;
int rc;
/* Obtain payload from burst */
memcpy(payload, bits + 3, 39);
memcpy(payload + 39, bits + 3 + 39 + 64, 39);
/* Attempt to decode */
rc = gsm0503_sch_decode(sb_info, payload);
if (rc) {
LOGP(DSCHD, LOGL_ERROR, "Received bad SCH burst at fn=%u\n", fn);
return rc;
}
/* Decode BSIC and TDMA frame number */
decode_sb(&time, &bsic, sb_info);
LOGP(DSCHD, LOGL_DEBUG, "Received SCH: bsic=%u, fn=%u, sched_fn=%u\n",
bsic, time.fn, trx->sched.fn_counter_proc);
/* Check if decoded frame number matches */
if (time.fn != fn) {
LOGP(DSCHD, LOGL_ERROR, "Decoded fn=%u does not match "
"fn=%u provided by scheduler\n", time.fn, fn);
return -EINVAL;
}
/* We don't need to send L1CTL_FBSB_CONF */
if (trx->l1l->fbsb_conf_sent)
return 0;
/* Send L1CTL_FBSB_CONF to higher layers */
struct l1ctl_info_dl *data;
data = talloc_zero_size(ts, sizeof(struct l1ctl_info_dl));
if (data == NULL)
return -ENOMEM;
/* Fill in some downlink info */
data->chan_nr = trx_lchan_desc[lchan->type].chan_nr | ts->index;
data->link_id = trx_lchan_desc[lchan->type].link_id;
data->band_arfcn = htons(trx->band_arfcn);
data->frame_nr = htonl(fn);
data->rx_level = -rssi;
/* FIXME: set proper values */
data->num_biterr = 0;
data->fire_crc = 0;
data->snr = 0;
l1ctl_tx_fbsb_conf(trx->l1l, 0, data, bsic);
/* Update BSIC value of trx_instance */
trx->bsic = bsic;
return 0;
}

View File

@ -0,0 +1,298 @@
/*
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: handlers for DL / UL bursts on logical channels
*
* (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/bits.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>
#include <osmocom/gsm/protocol/gsm_08_58.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/coding/gsm0503_coding.h>
#include <osmocom/codec/codec.h>
#include "l1ctl_proto.h"
#include "scheduler.h"
#include "sched_trx.h"
#include "logging.h"
#include "trx_if.h"
#include "trxcon.h"
#include "l1ctl.h"
int rx_tchf_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
sbit_t *bits, int8_t rssi, int16_t toa256)
{
const struct trx_lchan_desc *lchan_desc;
int n_errors = -1, n_bits_total, rc;
uint8_t rsl_cmode, tch_mode, mode;
sbit_t *buffer, *offset;
uint8_t l2[128], *mask;
uint32_t *first_fn;
size_t l2_len;
/* Set up pointers */
lchan_desc = &trx_lchan_desc[lchan->type];
first_fn = &lchan->rx_first_fn;
mask = &lchan->rx_burst_mask;
buffer = lchan->rx_bursts;
LOGP(DSCHD, LOGL_DEBUG, "Traffic received on %s: fn=%u ts=%u bid=%u\n",
lchan_desc->name, fn, ts->index, bid);
/* Reset internal state */
if (bid == 0) {
/* Clean up old measurements */
memset(&lchan->meas, 0x00, sizeof(lchan->meas));
*first_fn = fn;
*mask = 0x00;
}
/* Update mask */
*mask |= (1 << bid);
/* Update mask and RSSI */
lchan->meas.rssi_sum += rssi;
lchan->meas.toa256_sum += toa256;
lchan->meas.rssi_num++;
lchan->meas.toa256_num++;
/* Copy burst to end of buffer of 8 bursts */
offset = buffer + bid * 116 + 464;
memcpy(offset, bits + 3, 58);
memcpy(offset + 58, bits + 87, 58);
/* Wait until complete set of bursts */
if (bid != 3)
return 0;
/**
* Get current RSL / TCH modes
*
* FIXME: we do support speech only, and
* CSD support may be implemented latter.
*/
rsl_cmode = RSL_CMOD_SPD_SPEECH;
tch_mode = lchan->tch_mode;
/* Check for complete set of bursts */
if ((*mask & 0xf) != 0xf) {
LOGP(DSCHD, LOGL_ERROR, "Received incomplete traffic frame at "
"fn=%u (%u/%u) for %s\n", *first_fn,
(*first_fn) % ts->mf_layout->period,
ts->mf_layout->period,
lchan_desc->name);
/* Send BFI */
goto bfi;
}
mode = rsl_cmode != RSL_CMOD_SPD_SPEECH ?
GSM48_CMODE_SPEECH_V1 : tch_mode;
switch (mode) {
case GSM48_CMODE_SIGN:
case GSM48_CMODE_SPEECH_V1: /* FR */
rc = gsm0503_tch_fr_decode(l2, buffer,
1, 0, &n_errors, &n_bits_total);
break;
case GSM48_CMODE_SPEECH_EFR: /* EFR */
rc = gsm0503_tch_fr_decode(l2, buffer,
1, 1, &n_errors, &n_bits_total);
break;
case GSM48_CMODE_SPEECH_AMR: /* AMR */
/**
* TODO: AMR requires a dedicated loop,
* which will be implemented later...
*/
LOGP(DSCHD, LOGL_ERROR, "AMR isn't supported yet\n");
return -ENOTSUP;
default:
LOGP(DSCHD, LOGL_ERROR, "Invalid TCH mode: %u\n", tch_mode);
return -EINVAL;
}
/* Shift buffer by 4 bursts for interleaving */
memcpy(buffer, buffer + 464, 464);
/* Check decoding result */
if (rc < 4) {
LOGP(DSCHD, LOGL_ERROR, "Received bad TCH frame ending at "
"fn=%u for %s\n", fn, lchan_desc->name);
/* Send BFI */
goto bfi;
} else if (rc == GSM_MACBLOCK_LEN) {
/* FACCH received, forward it to the higher layers */
sched_send_dt_ind(trx, ts, lchan, l2, GSM_MACBLOCK_LEN,
n_errors, false, false);
/* Send BFI instead of stolen TCH frame */
goto bfi;
} else {
/* A good TCH frame received */
l2_len = rc;
}
/* Send a traffic frame to the higher layers */
return sched_send_dt_ind(trx, ts, lchan, l2, l2_len,
n_errors, false, true);
bfi:
/* Bad frame indication */
l2_len = sched_bad_frame_ind(l2, rsl_cmode, tch_mode);
/* Didn't try to decode */
if (n_errors < 0)
n_errors = 116 * 4;
/* Send a BFI frame to the higher layers */
return sched_send_dt_ind(trx, ts, lchan, l2, l2_len,
n_errors, true, true);
}
int tx_tchf_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid)
{
const struct trx_lchan_desc *lchan_desc;
ubit_t burst[GSM_BURST_LEN];
ubit_t *buffer, *offset;
const uint8_t *tsc;
uint8_t *mask;
size_t l2_len;
int rc;
/* Set up pointers */
lchan_desc = &trx_lchan_desc[lchan->type];
mask = &lchan->tx_burst_mask;
buffer = lchan->tx_bursts;
/* If we have encoded bursts */
if (*mask)
goto send_burst;
/* Wait until a first burst in period */
if (bid > 0)
return 0;
/* Check the current TCH mode */
switch (lchan->tch_mode) {
case GSM48_CMODE_SIGN:
case GSM48_CMODE_SPEECH_V1: /* FR */
l2_len = GSM_FR_BYTES;
break;
case GSM48_CMODE_SPEECH_EFR: /* EFR */
l2_len = GSM_EFR_BYTES;
break;
case GSM48_CMODE_SPEECH_AMR: /* AMR */
/**
* TODO: AMR requires a dedicated loop,
* which will be implemented later...
*/
LOGP(DSCHD, LOGL_ERROR, "AMR isn't supported yet, "
"dropping frame...\n");
/* Forget this primitive */
sched_prim_drop(lchan);
return -ENOTSUP;
default:
LOGP(DSCHD, LOGL_ERROR, "Invalid TCH mode: %u, "
"dropping frame...\n", lchan->tch_mode);
/* Forget this primitive */
sched_prim_drop(lchan);
return -EINVAL;
}
/* Determine payload length */
if (lchan->prim->payload_len == GSM_MACBLOCK_LEN)
l2_len = GSM_MACBLOCK_LEN;
/* Shift buffer by 4 bursts back for interleaving */
memcpy(buffer, buffer + 464, 464);
/* Encode payload */
rc = gsm0503_tch_fr_encode(buffer, lchan->prim->payload, l2_len, 1);
if (rc) {
LOGP(DSCHD, LOGL_ERROR, "Failed to encode L2 payload\n");
/* Forget this primitive */
sched_prim_drop(lchan);
return -EINVAL;
}
send_burst:
/* Determine which burst should be sent */
offset = buffer + bid * 116;
/* Update mask */
*mask |= (1 << bid);
/* Choose proper TSC */
tsc = sched_nb_training_bits[trx->tsc];
/* Compose a new burst */
memset(burst, 0, 3); /* TB */
memcpy(burst + 3, offset, 58); /* Payload 1/2 */
memcpy(burst + 61, tsc, 26); /* TSC */
memcpy(burst + 87, offset + 58, 58); /* Payload 2/2 */
memset(burst + 145, 0, 3); /* TB */
LOGP(DSCHD, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u burst=%u\n",
lchan_desc->name, fn, ts->index, bid);
/* Forward burst to scheduler */
rc = sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst);
if (rc) {
/* Forget this primitive */
sched_prim_drop(lchan);
/* Reset mask */
*mask = 0x00;
return rc;
}
/* If we have sent the last (4/4) burst */
if (*mask == 0x0f) {
/* Confirm data / traffic sending */
sched_send_dt_conf(trx, ts, lchan, fn, PRIM_IS_TCH(lchan->prim));
/* Forget processed primitive */
sched_prim_drop(lchan);
/* Reset mask */
*mask = 0x00;
}
return 0;
}

View File

@ -0,0 +1,200 @@
/*
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: handlers for DL / UL bursts on logical channels
*
* (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/bits.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>
#include <osmocom/coding/gsm0503_coding.h>
#include "l1ctl_proto.h"
#include "scheduler.h"
#include "sched_trx.h"
#include "logging.h"
#include "trx_if.h"
#include "trxcon.h"
#include "l1ctl.h"
int rx_data_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid,
sbit_t *bits, int8_t rssi, int16_t toa256)
{
const struct trx_lchan_desc *lchan_desc;
uint8_t l2[GSM_MACBLOCK_LEN], *mask;
int n_errors, n_bits_total, rc;
sbit_t *buffer, *offset;
uint32_t *first_fn;
/* Set up pointers */
lchan_desc = &trx_lchan_desc[lchan->type];
first_fn = &lchan->rx_first_fn;
mask = &lchan->rx_burst_mask;
buffer = lchan->rx_bursts;
LOGP(DSCHD, LOGL_DEBUG, "Data received on %s: fn=%u ts=%u bid=%u\n",
lchan_desc->name, fn, ts->index, bid);
/* Reset internal state */
if (bid == 0) {
/* Clean up old measurements */
memset(&lchan->meas, 0x00, sizeof(lchan->meas));
*first_fn = fn;
*mask = 0x0;
}
/* Update mask */
*mask |= (1 << bid);
/* Update measurements */
lchan->meas.rssi_sum += rssi;
lchan->meas.toa256_sum += toa256;
lchan->meas.rssi_num++;
lchan->meas.toa256_num++;
/* Copy burst to buffer of 4 bursts */
offset = buffer + bid * 116;
memcpy(offset, bits + 3, 58);
memcpy(offset + 58, bits + 87, 58);
/* Wait until complete set of bursts */
if (bid != 3)
return 0;
/* Check for complete set of bursts */
if ((*mask & 0xf) != 0xf) {
LOGP(DSCHD, LOGL_ERROR, "Received incomplete data frame at "
"fn=%u (%u/%u) for %s\n", *first_fn,
(*first_fn) % ts->mf_layout->period,
ts->mf_layout->period,
lchan_desc->name);
}
/* Attempt to decode */
rc = gsm0503_xcch_decode(l2, buffer, &n_errors, &n_bits_total);
if (rc) {
LOGP(DSCHD, LOGL_ERROR, "Received bad data frame at fn=%u "
"(%u/%u) for %s\n", *first_fn,
(*first_fn) % ts->mf_layout->period,
ts->mf_layout->period,
lchan_desc->name);
/**
* We should anyway send dummy frame for
* proper measurement reporting...
*/
return sched_send_dt_ind(trx, ts, lchan, NULL, 0,
n_errors, true, false);
}
/* Send a L2 frame to the higher layers */
return sched_send_dt_ind(trx, ts, lchan, l2, GSM_MACBLOCK_LEN,
n_errors, false, false);
}
int tx_data_fn(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, uint8_t bid)
{
const struct trx_lchan_desc *lchan_desc;
ubit_t burst[GSM_BURST_LEN];
ubit_t *buffer, *offset;
const uint8_t *tsc;
uint8_t *mask;
int rc;
/* Set up pointers */
lchan_desc = &trx_lchan_desc[lchan->type];
mask = &lchan->tx_burst_mask;
buffer = lchan->tx_bursts;
if (bid > 0) {
/* If we have encoded bursts */
if (*mask)
goto send_burst;
else
return 0;
}
/* Encode payload */
rc = gsm0503_xcch_encode(buffer, lchan->prim->payload);
if (rc) {
LOGP(DSCHD, LOGL_ERROR, "Failed to encode L2 payload\n");
/* Forget this primitive */
sched_prim_drop(lchan);
return -EINVAL;
}
send_burst:
/* Determine which burst should be sent */
offset = buffer + bid * 116;
/* Update mask */
*mask |= (1 << bid);
/* Choose proper TSC */
tsc = sched_nb_training_bits[trx->tsc];
/* Compose a new burst */
memset(burst, 0, 3); /* TB */
memcpy(burst + 3, offset, 58); /* Payload 1/2 */
memcpy(burst + 61, tsc, 26); /* TSC */
memcpy(burst + 87, offset + 58, 58); /* Payload 2/2 */
memset(burst + 145, 0, 3); /* TB */
LOGP(DSCHD, LOGL_DEBUG, "Transmitting %s fn=%u ts=%u burst=%u\n",
lchan_desc->name, fn, ts->index, bid);
/* Forward burst to scheduler */
rc = sched_trx_handle_tx_burst(trx, ts, lchan, fn, burst);
if (rc) {
/* Forget this primitive */
sched_prim_drop(lchan);
/* Reset mask */
*mask = 0x00;
return rc;
}
/* If we have sent the last (4/4) burst */
if ((*mask & 0x0f) == 0x0f) {
/* Forget processed primitive */
sched_prim_drop(lchan);
/* Reset mask */
*mask = 0x00;
/* Confirm data sending */
sched_send_dt_conf(trx, ts, lchan, fn, false);
}
return 0;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,332 @@
/*
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: primitive management
*
* (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <talloc.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>
#include <osmocom/gsm/protocol/gsm_08_58.h>
#include "scheduler.h"
#include "sched_trx.h"
#include "trx_if.h"
#include "logging.h"
/**
* Initializes a new primitive by allocating memory
* and filling some meta-information (e.g. lchan type).
*
* @param trx TRX instance to be used as initial talloc context
* @param prim external prim pointer (will point to the allocated prim)
* @param pl_len prim payload length
* @param chan_nr RSL channel description (used to set a proper chan)
* @param link_id RSL link description (used to set a proper chan)
* @return zero in case of success, otherwise a error number
*/
int sched_prim_init(struct trx_instance *trx,
struct trx_ts_prim **prim, size_t pl_len,
uint8_t chan_nr, uint8_t link_id)
{
enum trx_lchan_type lchan_type;
struct trx_ts_prim *new_prim;
uint8_t len;
/* Determine lchan type */
lchan_type = sched_trx_chan_nr2lchan_type(chan_nr, link_id);
if (!lchan_type) {
LOGP(DSCH, LOGL_ERROR, "Couldn't determine lchan type "
"for chan_nr=%02x and link_id=%02x\n", chan_nr, link_id);
return -EINVAL;
}
/* How much memory do we need? */
len = sizeof(struct trx_ts_prim); /* Primitive header */
len += pl_len; /* Requested payload size */
/* Allocate a new primitive */
new_prim = talloc_zero_size(trx, len);
if (new_prim == NULL) {
LOGP(DSCH, LOGL_ERROR, "Failed to allocate memory\n");
return -ENOMEM;
}
/* Init primitive header */
new_prim->payload_len = pl_len;
new_prim->chan = lchan_type;
/* Set external pointer */
*prim = new_prim;
return 0;
}
/**
* Adds a primitive to the end of transmit queue of a particular
* timeslot, whose index is parsed from chan_nr.
*
* @param trx TRX instance
* @param prim to be enqueued primitive
* @param chan_nr RSL channel description
* @return zero in case of success, otherwise a error number
*/
int sched_prim_push(struct trx_instance *trx,
struct trx_ts_prim *prim, uint8_t chan_nr)
{
struct trx_ts *ts;
uint8_t tn;
/* Determine TS index */
tn = chan_nr & 0x7;
if (tn > 7) {
LOGP(DSCH, LOGL_ERROR, "Incorrect TS index %u\n", tn);
return -EINVAL;
}
/* Check whether required timeslot is allocated and configured */
ts = trx->ts_list[tn];
if (ts == NULL || ts->mf_layout == NULL) {
LOGP(DSCH, LOGL_ERROR, "Timeslot %u isn't configured\n", tn);
return -EINVAL;
}
/**
* Change talloc context of primitive
* from trx to the parent ts
*/
talloc_steal(ts, prim);
/* Add primitive to TS transmit queue */
llist_add_tail(&prim->list, &ts->tx_prims);
return 0;
}
/**
* Dequeues a TCH or FACCH frame, prioritizing the second.
* In case if a FACCH frame is found, a TCH frame is being
* dropped (i.e. replaced).
*
* @param queue a transmit queue to take a prim from
* @return a FACCH or TCH primitive, otherwise NULL
*/
static struct trx_ts_prim *sched_prim_dequeue_tch(struct llist_head *queue)
{
struct trx_ts_prim *facch = NULL;
struct trx_ts_prim *tch = NULL;
struct trx_ts_prim *i;
/* Attempt to find a pair of FACCH and TCH frames */
llist_for_each_entry(i, queue, list) {
/* Find one FACCH frame */
if (!facch && PRIM_IS_FACCH(i))
facch = i;
/* Find one TCH frame */
if (!tch && PRIM_IS_TCH(i))
tch = i;
/* If both are found */
if (facch && tch)
break;
}
/* Prioritize FACCH */
if (facch && tch) {
/* We found a pair, dequeue both */
llist_del(&facch->list);
llist_del(&tch->list);
/* Drop TCH */
talloc_free(tch);
/* FACCH replaces TCH */
return facch;
} else if (facch) {
/* Only FACCH was found */
llist_del(&facch->list);
return facch;
} else if (tch) {
/* Only TCH was found */
llist_del(&tch->list);
return tch;
}
/**
* Nothing was found,
* e.g. only SACCH frames are in queue
*/
return NULL;
}
/**
* Dequeues a single primitive of required type
* from a specified transmit queue.
*
* @param queue a transmit queue to take a prim from
* @param lchan_type required primitive type
* @return a primitive or NULL if not found
*/
struct trx_ts_prim *sched_prim_dequeue(struct llist_head *queue,
enum trx_lchan_type lchan_type)
{
struct trx_ts_prim *prim;
/* There is nothing to dequeue */
if (llist_empty(queue))
return NULL;
/* TCH requires FACCH prioritization, so handle it separately */
if (CHAN_IS_TCH(lchan_type))
return sched_prim_dequeue_tch(queue);
llist_for_each_entry(prim, queue, list) {
if (prim->chan == lchan_type) {
llist_del(&prim->list);
return prim;
}
}
return NULL;
}
/**
* Drops the current primitive of specified logical channel
*
* @param lchan a logical channel to drop prim from
*/
void sched_prim_drop(struct trx_lchan_state *lchan)
{
/* Forget this primitive */
talloc_free(lchan->prim);
lchan->prim = NULL;
}
/**
* Assigns a dummy primitive to a lchan depending on its type.
* Could be used when there is nothing to transmit, but
* CBTX (Continuous Burst Transmission) is assumed.
*
* @param lchan lchan to assign a primitive
* @return zero in case of success, otherwise a error code
*/
int sched_prim_dummy(struct trx_lchan_state *lchan)
{
enum trx_lchan_type chan = lchan->type;
uint8_t tch_mode = lchan->tch_mode;
struct trx_ts_prim *prim;
uint8_t prim_buffer[40];
size_t prim_len = 0;
int i;
/**
* TS 144.006, section 8.4.2.3 "Fill frames"
* A fill frame is a UI command frame for SAPI 0, P=0
* and with an information field of 0 octet length.
*/
static const uint8_t lapdm_fill_frame[] = {
0x01, 0x03, 0x01, 0x2b,
/* Pending part is to be randomized */
};
/* Make sure that there is no existing primitive */
OSMO_ASSERT(lchan->prim == NULL);
/**
* Determine what actually should be generated:
* TCH in GSM48_CMODE_SIGN: LAPDm fill frame;
* TCH in other modes: silence frame;
* other channels: LAPDm fill frame.
*/
if (CHAN_IS_TCH(chan) && TCH_MODE_IS_SPEECH(tch_mode)) {
/**
* Silence frame indication
* HACK: use actual rsl_cmode!
*/
prim_len = sched_bad_frame_ind(prim_buffer,
RSL_CMOD_SPD_SPEECH, tch_mode);
} else if (CHAN_IS_TCH(chan) && TCH_MODE_IS_DATA(tch_mode)) {
/* FIXME: should we do anything for CSD? */
return 0;
} else {
/* Copy a fill frame payload */
memcpy(prim_buffer, lapdm_fill_frame, sizeof(lapdm_fill_frame));
/**
* TS 144.006, section 5.2 "Frame delimitation and fill bits"
* Except for the first octet containing fill bits which shall
* be set to the binary value "00101011", each fill bit should
* be set to a random value when sent by the network.
*/
for (i = sizeof(lapdm_fill_frame); i < GSM_MACBLOCK_LEN; i++)
prim_buffer[i] = (uint8_t) rand();
/* Define a prim length */
prim_len = GSM_MACBLOCK_LEN;
}
/* Nothing to allocate / assign */
if (!prim_len)
return 0;
/* Allocate a new primitive */
prim = talloc_zero_size(lchan, sizeof(struct trx_ts_prim) + prim_len);
if (prim == NULL)
return -ENOMEM;
/* Init primitive header */
prim->payload_len = prim_len;
prim->chan = lchan->type;
/* Fill in the payload */
memcpy(prim->payload, prim_buffer, prim_len);
/* Assign the current prim */
lchan->prim = prim;
LOGP(DSCHD, LOGL_DEBUG, "Transmitting a dummy / silence frame "
"on lchan=%s\n", trx_lchan_desc[chan].name);
return 0;
}
/**
* Flushes a queue of primitives
*
* @param list list of prims going to be flushed
*/
void sched_prim_flush_queue(struct llist_head *list)
{
struct trx_ts_prim *prim, *prim_next;
llist_for_each_entry_safe(prim, prim_next, list, list) {
llist_del(&prim->list);
talloc_free(prim);
}
}

689
src/host/trxcon/sched_trx.c Normal file
View File

@ -0,0 +1,689 @@
/*
* OsmocomBB <-> SDR connection bridge
* TDMA scheduler: GSM PHY routines
*
* (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <error.h>
#include <errno.h>
#include <string.h>
#include <talloc.h>
#include <osmocom/gsm/a5.h>
#include <osmocom/core/bits.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/linuxlist.h>
#include "scheduler.h"
#include "sched_trx.h"
#include "trx_if.h"
#include "logging.h"
static void sched_frame_clck_cb(struct trx_sched *sched)
{
struct trx_instance *trx = (struct trx_instance *) sched->data;
const struct trx_frame *frame;
struct trx_lchan_state *lchan;
trx_lchan_tx_func *handler;
enum trx_lchan_type chan;
uint8_t offset, bid;
struct trx_ts *ts;
uint32_t fn;
int i;
/* Iterate over timeslot list */
for (i = 0; i < TRX_TS_COUNT; i++) {
/* Timeslot is not allocated */
ts = trx->ts_list[i];
if (ts == NULL)
continue;
/* Timeslot is not configured */
if (ts->mf_layout == NULL)
continue;
/**
* Advance frame number, giving the transceiver more
* time until a burst must be transmitted...
*/
fn = (sched->fn_counter_proc + sched->fn_counter_advance)
% GSM_HYPERFRAME;
/* Get frame from multiframe */
offset = fn % ts->mf_layout->period;
frame = ts->mf_layout->frames + offset;
/* Get required info from frame */
bid = frame->ul_bid;
chan = frame->ul_chan;
handler = trx_lchan_desc[chan].tx_fn;
/* Omit lchans without handler */
if (!handler)
continue;
/* Make sure that lchan was allocated and activated */
lchan = sched_trx_find_lchan(ts, chan);
if (lchan == NULL)
continue;
/* Omit inactive lchans */
if (!lchan->active)
continue;
/**
* If we aren't processing any primitive yet,
* attempt to obtain a new one from queue
*/
if (lchan->prim == NULL)
lchan->prim = sched_prim_dequeue(&ts->tx_prims, chan);
/* TODO: report TX buffers health to the higher layers */
/* If CBTX (Continuous Burst Transmission) is assumed */
if (trx_lchan_desc[chan].flags & TRX_CH_FLAG_CBTX) {
/**
* Probably, a TX buffer is empty. Nevertheless,
* we shall continuously transmit anything on
* CBTX channels.
*/
if (lchan->prim == NULL)
sched_prim_dummy(lchan);
}
/* If there is no primitive, do nothing */
if (lchan->prim == NULL)
continue;
/* Poke lchan handler */
handler(trx, ts, lchan, fn, bid);
}
}
int sched_trx_init(struct trx_instance *trx, uint32_t fn_advance)
{
struct trx_sched *sched;
if (!trx)
return -EINVAL;
LOGP(DSCH, LOGL_NOTICE, "Init scheduler\n");
/* Obtain a scheduler instance from TRX */
sched = &trx->sched;
/* Register frame clock callback */
sched->clock_cb = sched_frame_clck_cb;
/* Set pointers */
sched = &trx->sched;
sched->data = trx;
/* Set frame counter advance */
sched->fn_counter_advance = fn_advance;
return 0;
}
int sched_trx_shutdown(struct trx_instance *trx)
{
int i;
if (!trx)
return -EINVAL;
LOGP(DSCH, LOGL_NOTICE, "Shutdown scheduler\n");
/* Free all potentially allocated timeslots */
for (i = 0; i < TRX_TS_COUNT; i++)
sched_trx_del_ts(trx, i);
return 0;
}
int sched_trx_reset(struct trx_instance *trx, int reset_clock)
{
int i;
if (!trx)
return -EINVAL;
LOGP(DSCH, LOGL_NOTICE, "Reset scheduler %s\n",
reset_clock ? "and clock counter" : "");
/* Free all potentially allocated timeslots */
for (i = 0; i < TRX_TS_COUNT; i++)
sched_trx_del_ts(trx, i);
/* Stop and reset clock counter if required */
if (reset_clock)
sched_clck_reset(&trx->sched);
return 0;
}
struct trx_ts *sched_trx_add_ts(struct trx_instance *trx, int tn)
{
/* Make sure that ts isn't allocated yet */
if (trx->ts_list[tn] != NULL) {
LOGP(DSCH, LOGL_ERROR, "Timeslot #%u already allocated\n", tn);
return NULL;
}
LOGP(DSCH, LOGL_NOTICE, "Add a new TDMA timeslot #%u\n", tn);
/* Allocate a new one */
trx->ts_list[tn] = talloc_zero(trx, struct trx_ts);
/* Assign TS index */
trx->ts_list[tn]->index = tn;
return trx->ts_list[tn];
}
void sched_trx_del_ts(struct trx_instance *trx, int tn)
{
struct trx_lchan_state *lchan, *lchan_next;
struct trx_ts *ts;
/* Find ts in list */
ts = trx->ts_list[tn];
if (ts == NULL)
return;
LOGP(DSCH, LOGL_NOTICE, "Delete TDMA timeslot #%u\n", tn);
/* Deactivate all logical channels */
sched_trx_deactivate_all_lchans(ts);
/* Free channel states */
llist_for_each_entry_safe(lchan, lchan_next, &ts->lchans, list) {
llist_del(&lchan->list);
talloc_free(lchan);
}
/* Flush queue primitives for TX */
sched_prim_flush_queue(&ts->tx_prims);
/* Remove ts from list and free memory */
trx->ts_list[tn] = NULL;
talloc_free(ts);
/* Notify transceiver about that */
trx_if_cmd_setslot(trx, tn, 0);
}
#define LAYOUT_HAS_LCHAN(layout, lchan) \
(layout->lchan_mask & ((uint64_t) 0x01 << lchan))
int sched_trx_configure_ts(struct trx_instance *trx, int tn,
enum gsm_phys_chan_config config)
{
struct trx_lchan_state *lchan;
enum trx_lchan_type type;
struct trx_ts *ts;
/* Try to find specified ts */
ts = trx->ts_list[tn];
if (ts != NULL) {
/* Reconfiguration of existing one */
sched_trx_reset_ts(trx, tn);
} else {
/* Allocate a new one if doesn't exist */
ts = sched_trx_add_ts(trx, tn);
if (ts == NULL)
return -ENOMEM;
}
/* Choose proper multiframe layout */
ts->mf_layout = sched_mframe_layout(config, tn);
if (ts->mf_layout->chan_config != config)
return -EINVAL;
LOGP(DSCH, LOGL_NOTICE, "(Re)configure TDMA timeslot #%u as %s\n",
tn, ts->mf_layout->name);
/* Init queue primitives for TX */
INIT_LLIST_HEAD(&ts->tx_prims);
/* Init logical channels list */
INIT_LLIST_HEAD(&ts->lchans);
/* Allocate channel states */
for (type = 0; type < _TRX_CHAN_MAX; type++) {
if (!LAYOUT_HAS_LCHAN(ts->mf_layout, type))
continue;
/* Allocate a channel state */
lchan = talloc_zero(ts, struct trx_lchan_state);
if (!lchan)
return -ENOMEM;
/* Set channel type */
lchan->type = type;
/* Add to the list of channel states */
llist_add_tail(&lchan->list, &ts->lchans);
/* Enable channel automatically if required */
if (trx_lchan_desc[type].flags & TRX_CH_FLAG_AUTO)
sched_trx_activate_lchan(ts, type);
}
/* Notify transceiver about TS activation */
/* FIXME: set proper channel type */
trx_if_cmd_setslot(trx, tn, 1);
return 0;
}
int sched_trx_reset_ts(struct trx_instance *trx, int tn)
{
struct trx_lchan_state *lchan, *lchan_next;
struct trx_ts *ts;
/* Try to find specified ts */
ts = trx->ts_list[tn];
if (ts == NULL)
return -EINVAL;
/* Flush TS frame counter */
ts->mf_last_fn = 0;
/* Undefine multiframe layout */
ts->mf_layout = NULL;
/* Flush queue primitives for TX */
sched_prim_flush_queue(&ts->tx_prims);
/* Deactivate all logical channels */
sched_trx_deactivate_all_lchans(ts);
/* Free channel states */
llist_for_each_entry_safe(lchan, lchan_next, &ts->lchans, list) {
llist_del(&lchan->list);
talloc_free(lchan);
}
/* Notify transceiver about that */
trx_if_cmd_setslot(trx, tn, 0);
return 0;
}
int sched_trx_start_ciphering(struct trx_ts *ts, uint8_t algo,
uint8_t *key, uint8_t key_len)
{
struct trx_lchan_state *lchan;
/* Prevent NULL-pointer deference */
if (!ts)
return -EINVAL;
/* Make sure we can store this key */
if (key_len > MAX_A5_KEY_LEN)
return -ERANGE;
/* Iterate over all allocated logical channels */
llist_for_each_entry(lchan, &ts->lchans, list) {
/* Omit inactive channels */
if (!lchan->active)
continue;
/* Set key length and algorithm */
lchan->a5.key_len = key_len;
lchan->a5.algo = algo;
/* Copy requested key */
if (key_len)
memcpy(lchan->a5.key, key, key_len);
}
return 0;
}
struct trx_lchan_state *sched_trx_find_lchan(struct trx_ts *ts,
enum trx_lchan_type chan)
{
struct trx_lchan_state *lchan;
llist_for_each_entry(lchan, &ts->lchans, list)
if (lchan->type == chan)
return lchan;
return NULL;
}
int sched_trx_set_lchans(struct trx_ts *ts, uint8_t chan_nr, int active, uint8_t tch_mode)
{
const struct trx_lchan_desc *lchan_desc;
struct trx_lchan_state *lchan;
int rc = 0;
/* Prevent NULL-pointer deference */
if (ts == NULL) {
LOGP(DSCH, LOGL_ERROR, "Timeslot isn't configured\n");
return -EINVAL;
}
/* Iterate over all allocated lchans */
llist_for_each_entry(lchan, &ts->lchans, list) {
lchan_desc = &trx_lchan_desc[lchan->type];
if (lchan_desc->chan_nr == (chan_nr & 0xf8)) {
if (active) {
rc |= sched_trx_activate_lchan(ts, lchan->type);
lchan->tch_mode = tch_mode;
} else
rc |= sched_trx_deactivate_lchan(ts, lchan->type);
}
}
return rc;
}
int sched_trx_activate_lchan(struct trx_ts *ts, enum trx_lchan_type chan)
{
const struct trx_lchan_desc *lchan_desc = &trx_lchan_desc[chan];
struct trx_lchan_state *lchan;
/* Try to find requested logical channel */
lchan = sched_trx_find_lchan(ts, chan);
if (lchan == NULL)
return -EINVAL;
if (lchan->active) {
LOGP(DSCH, LOGL_ERROR, "Logical channel %s already activated "
"on ts=%d\n", trx_lchan_desc[chan].name, ts->index);
return -EINVAL;
}
LOGP(DSCH, LOGL_NOTICE, "Activating lchan=%s "
"on ts=%d\n", trx_lchan_desc[chan].name, ts->index);
/* Conditionally allocate memory for bursts */
if (lchan_desc->rx_fn && lchan_desc->burst_buf_size > 0) {
lchan->rx_bursts = talloc_zero_size(lchan,
lchan_desc->burst_buf_size);
if (lchan->rx_bursts == NULL)
return -ENOMEM;
}
if (lchan_desc->tx_fn && lchan_desc->burst_buf_size > 0) {
lchan->tx_bursts = talloc_zero_size(lchan,
lchan_desc->burst_buf_size);
if (lchan->tx_bursts == NULL)
return -ENOMEM;
}
/* Finally, update channel status */
lchan->active = 1;
return 0;
}
static void sched_trx_reset_lchan(struct trx_lchan_state *lchan)
{
/* Prevent NULL-pointer deference */
OSMO_ASSERT(lchan != NULL);
/* Reset internal state variables */
lchan->rx_burst_mask = 0x00;
lchan->tx_burst_mask = 0x00;
lchan->rx_first_fn = 0;
/* Free burst memory */
talloc_free(lchan->rx_bursts);
talloc_free(lchan->tx_bursts);
lchan->rx_bursts = NULL;
lchan->tx_bursts = NULL;
/* Forget the current prim */
sched_prim_drop(lchan);
/* TCH specific variables */
if (CHAN_IS_TCH(lchan->type)) {
lchan->dl_ongoing_facch = 0;
lchan->ul_ongoing_facch = 0;
lchan->rsl_cmode = 0x00;
lchan->tch_mode = 0x00;
/* Reset AMR state */
memset(&lchan->amr, 0x00, sizeof(lchan->amr));
}
/* Reset ciphering state */
memset(&lchan->a5, 0x00, sizeof(lchan->a5));
}
int sched_trx_deactivate_lchan(struct trx_ts *ts, enum trx_lchan_type chan)
{
struct trx_lchan_state *lchan;
/* Try to find requested logical channel */
lchan = sched_trx_find_lchan(ts, chan);
if (lchan == NULL)
return -EINVAL;
if (!lchan->active) {
LOGP(DSCH, LOGL_ERROR, "Logical channel %s already deactivated "
"on ts=%d\n", trx_lchan_desc[chan].name, ts->index);
return -EINVAL;
}
LOGP(DSCH, LOGL_DEBUG, "Deactivating lchan=%s "
"on ts=%d\n", trx_lchan_desc[chan].name, ts->index);
/* Reset internal state, free memory */
sched_trx_reset_lchan(lchan);
/* Update activation flag */
lchan->active = 0;
return 0;
}
void sched_trx_deactivate_all_lchans(struct trx_ts *ts)
{
struct trx_lchan_state *lchan;
LOGP(DSCH, LOGL_DEBUG, "Deactivating all logical channels "
"on ts=%d\n", ts->index);
llist_for_each_entry(lchan, &ts->lchans, list) {
/* Omit inactive channels */
if (!lchan->active)
continue;
/* Reset internal state, free memory */
sched_trx_reset_lchan(lchan);
/* Update activation flag */
lchan->active = 0;
}
}
enum gsm_phys_chan_config sched_trx_chan_nr2pchan_config(uint8_t chan_nr)
{
uint8_t cbits = chan_nr >> 3;
if (cbits == 0x01)
return GSM_PCHAN_TCH_F;
else if ((cbits & 0x1e) == 0x02)
return GSM_PCHAN_TCH_H;
else if ((cbits & 0x1c) == 0x04)
return GSM_PCHAN_CCCH_SDCCH4;
else if ((cbits & 0x18) == 0x08)
return GSM_PCHAN_SDCCH8_SACCH8C;
return GSM_PCHAN_NONE;
}
enum trx_lchan_type sched_trx_chan_nr2lchan_type(uint8_t chan_nr,
uint8_t link_id)
{
int i;
/* Iterate over all known lchan types */
for (i = 0; i < _TRX_CHAN_MAX; i++)
if (trx_lchan_desc[i].chan_nr == (chan_nr & 0xf8))
if (trx_lchan_desc[i].link_id == link_id)
return i;
return TRXC_IDLE;
}
static void sched_trx_a5_burst_dec(struct trx_lchan_state *lchan,
uint32_t fn, sbit_t *burst)
{
ubit_t ks[114];
int i;
/* Generate keystream for a DL burst */
osmo_a5(lchan->a5.algo, lchan->a5.key, fn, ks, NULL);
/* Apply keystream over ciphertext */
for (i = 0; i < 57; i++) {
if (ks[i])
burst[i + 3] *= -1;
if (ks[i + 57])
burst[i + 88] *= -1;
}
}
static void sched_trx_a5_burst_enc(struct trx_lchan_state *lchan,
uint32_t fn, ubit_t *burst)
{
ubit_t ks[114];
int i;
/* Generate keystream for an UL burst */
osmo_a5(lchan->a5.algo, lchan->a5.key, fn, NULL, ks);
/* Apply keystream over plaintext */
for (i = 0; i < 57; i++) {
burst[i + 3] ^= ks[i];
burst[i + 88] ^= ks[i + 57];
}
}
int sched_trx_handle_rx_burst(struct trx_instance *trx, uint8_t tn,
uint32_t burst_fn, sbit_t *bits, uint16_t nbits,
int8_t rssi, int16_t toa256)
{
struct trx_lchan_state *lchan;
const struct trx_frame *frame;
struct trx_ts *ts;
trx_lchan_rx_func *handler;
enum trx_lchan_type chan;
uint32_t fn, elapsed;
uint8_t offset, bid;
/* Check whether required timeslot is allocated and configured */
ts = trx->ts_list[tn];
if (ts == NULL || ts->mf_layout == NULL) {
LOGP(DSCHD, LOGL_DEBUG, "TDMA timeslot #%u isn't configured, "
"ignoring burst...\n", tn);
return -EINVAL;
}
/* Calculate how many frames have been elapsed */
elapsed = (burst_fn + GSM_HYPERFRAME - ts->mf_last_fn);
elapsed %= GSM_HYPERFRAME;
/**
* If not too many frames have been elapsed,
* start counting from last fn + 1
*/
if (elapsed < 10)
fn = (ts->mf_last_fn + 1) % GSM_HYPERFRAME;
else
fn = burst_fn;
while (1) {
/* Get frame from multiframe */
offset = fn % ts->mf_layout->period;
frame = ts->mf_layout->frames + offset;
/* Get required info from frame */
bid = frame->dl_bid;
chan = frame->dl_chan;
handler = trx_lchan_desc[chan].rx_fn;
/* Omit bursts which have no handler, like IDLE bursts */
if (!handler)
goto next_frame;
/* Find required channel state */
lchan = sched_trx_find_lchan(ts, chan);
if (lchan == NULL)
goto next_frame;
/* Ensure that channel is active */
if (!lchan->active)
goto next_frame;
/* Reached current fn */
if (fn == burst_fn) {
/* Perform A5/X decryption if required */
if (lchan->a5.algo)
sched_trx_a5_burst_dec(lchan, fn, bits);
/* Put burst to handler */
handler(trx, ts, lchan, fn, bid, bits, rssi, toa256);
}
next_frame:
/* Reached current fn */
if (fn == burst_fn)
break;
fn = (fn + 1) % GSM_HYPERFRAME;
}
/* Set last processed frame number */
ts->mf_last_fn = fn;
return 0;
}
int sched_trx_handle_tx_burst(struct trx_instance *trx,
struct trx_ts *ts, struct trx_lchan_state *lchan,
uint32_t fn, ubit_t *bits)
{
int rc;
/* Perform A5/X burst encryption if required */
if (lchan->a5.algo)
sched_trx_a5_burst_enc(lchan, fn, bits);
/* Forward burst to transceiver */
rc = trx_if_tx_burst(trx, ts->index, fn, trx->tx_power, bits);
if (rc) {
LOGP(DSCHD, LOGL_ERROR, "Could not send burst to transceiver\n");
return rc;
}
return 0;
}

325
src/host/trxcon/sched_trx.h Normal file
View File

@ -0,0 +1,325 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <osmocom/core/bits.h>
#include <osmocom/core/utils.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/core/linuxlist.h>
#include "logging.h"
#include "scheduler.h"
#define GSM_BURST_LEN 148
#define GSM_BURST_PL_LEN 116
#define GPRS_BURST_LEN GSM_BURST_LEN
#define EDGE_BURST_LEN 444
#define TRX_CH_LID_DEDIC 0x00
#define TRX_CH_LID_SACCH 0x40
/* Is a channel related to PDCH (GPRS) */
#define TRX_CH_FLAG_PDCH (1 << 0)
/* Should a channel be activated automatically */
#define TRX_CH_FLAG_AUTO (1 << 1)
/* Is continuous burst transmission assumed */
#define TRX_CH_FLAG_CBTX (1 << 2)
#define MAX_A5_KEY_LEN (128 / 8)
#define TRX_TS_COUNT 8
/* Forward declaration to avoid mutual include */
struct trx_lchan_state;
struct trx_instance;
struct trx_ts;
enum trx_burst_type {
TRX_BURST_GMSK,
TRX_BURST_8PSK,
};
/**
* These types define the different channels on a multiframe.
* Each channel has queues and can be activated individually.
*/
enum trx_lchan_type {
TRXC_IDLE = 0,
TRXC_FCCH,
TRXC_SCH,
TRXC_BCCH,
TRXC_RACH,
TRXC_CCCH,
TRXC_TCHF,
TRXC_TCHH_0,
TRXC_TCHH_1,
TRXC_SDCCH4_0,
TRXC_SDCCH4_1,
TRXC_SDCCH4_2,
TRXC_SDCCH4_3,
TRXC_SDCCH8_0,
TRXC_SDCCH8_1,
TRXC_SDCCH8_2,
TRXC_SDCCH8_3,
TRXC_SDCCH8_4,
TRXC_SDCCH8_5,
TRXC_SDCCH8_6,
TRXC_SDCCH8_7,
TRXC_SACCHTF,
TRXC_SACCHTH_0,
TRXC_SACCHTH_1,
TRXC_SACCH4_0,
TRXC_SACCH4_1,
TRXC_SACCH4_2,
TRXC_SACCH4_3,
TRXC_SACCH8_0,
TRXC_SACCH8_1,
TRXC_SACCH8_2,
TRXC_SACCH8_3,
TRXC_SACCH8_4,
TRXC_SACCH8_5,
TRXC_SACCH8_6,
TRXC_SACCH8_7,
TRXC_PDTCH,
TRXC_PTCCH,
_TRX_CHAN_MAX
};
typedef int trx_lchan_rx_func(struct trx_instance *trx,
struct trx_ts *ts, struct trx_lchan_state *lchan,
uint32_t fn, uint8_t bid, sbit_t *bits,
int8_t rssi, int16_t toa256);
typedef int trx_lchan_tx_func(struct trx_instance *trx,
struct trx_ts *ts, struct trx_lchan_state *lchan,
uint32_t fn, uint8_t bid);
struct trx_lchan_desc {
/*! \brief TRX Channel Type */
enum trx_lchan_type chan;
/*! \brief Human-readable name */
const char *name;
/*! \brief Channel Number (like in RSL) */
uint8_t chan_nr;
/*! \brief Link ID (like in RSL) */
uint8_t link_id;
/*! \brief How much memory do we need to store bursts */
size_t burst_buf_size;
/*! \brief Channel specific flags */
uint8_t flags;
/*! \brief Function to call when burst received from PHY */
trx_lchan_rx_func *rx_fn;
/*! \brief Function to call when data received from L2 */
trx_lchan_tx_func *tx_fn;
};
struct trx_frame {
/*! \brief Downlink TRX channel type */
enum trx_lchan_type dl_chan;
/*! \brief Downlink block ID */
uint8_t dl_bid;
/*! \brief Uplink TRX channel type */
enum trx_lchan_type ul_chan;
/*! \brief Uplink block ID */
uint8_t ul_bid;
};
struct trx_multiframe {
/*! \brief Channel combination */
enum gsm_phys_chan_config chan_config;
/*! \brief Human-readable name */
const char *name;
/*! \brief Repeats how many frames */
uint8_t period;
/*! \brief Applies to which timeslots */
uint8_t slotmask;
/*! \brief Contains which lchans */
uint64_t lchan_mask;
/*! \brief Pointer to scheduling structure */
const struct trx_frame *frames;
};
/* States each channel on a multiframe */
struct trx_lchan_state {
/*! \brief Channel type */
enum trx_lchan_type type;
/*! \brief Channel status */
uint8_t active;
/*! \brief Link to a list of channels */
struct llist_head list;
/*! \brief Burst type: GMSK or 8PSK */
enum trx_burst_type burst_type;
/*! \brief Frame number of first burst */
uint32_t rx_first_fn;
/*! \brief Mask of received bursts */
uint8_t rx_burst_mask;
/*! \brief Mask of transmitted bursts */
uint8_t tx_burst_mask;
/*! \brief Burst buffer for RX */
sbit_t *rx_bursts;
/*! \brief Burst buffer for TX */
ubit_t *tx_bursts;
/*! \brief A primitive being sent */
struct trx_ts_prim *prim;
/*! \brief Mode for TCH channels */
uint8_t rsl_cmode, tch_mode;
/*! \brief FACCH/H on downlink */
uint8_t dl_ongoing_facch;
/*! \brief FACCH/H on uplink */
uint8_t ul_ongoing_facch;
struct {
/*! \brief Number of RSSI values */
uint8_t rssi_num;
/*! \brief Sum of RSSI values */
float rssi_sum;
/*! \brief Number of TOA values */
uint8_t toa256_num;
/*! \brief Sum of TOA values */
int32_t toa256_sum;
} meas;
/* AMR specific */
struct {
/*! \brief 4 possible codecs for AMR */
uint8_t codec[4];
/*! \brief Number of possible codecs */
uint8_t codecs;
/*! \brief Current uplink FT index */
uint8_t ul_ft;
/*! \brief Current downlink FT index */
uint8_t dl_ft;
/*! \brief Current uplink CMR index */
uint8_t ul_cmr;
/*! \brief Current downlink CMR index */
uint8_t dl_cmr;
/*! \brief If AMR loop is enabled */
uint8_t amr_loop;
/*! \brief Number of bit error rates */
uint8_t ber_num;
/*! \brief Sum of bit error rates */
float ber_sum;
} amr;
/*! \brief A5/X encryption state */
struct {
uint8_t key[MAX_A5_KEY_LEN];
uint8_t key_len;
uint8_t algo;
} a5;
};
struct trx_ts {
/*! \brief Timeslot index within a frame (0..7) */
uint8_t index;
/*! \brief Last received frame number */
uint32_t mf_last_fn;
/*! \brief Pointer to multiframe layout */
const struct trx_multiframe *mf_layout;
/*! \brief Channel states for logical channels */
struct llist_head lchans;
/*! \brief Queue primitives for TX */
struct llist_head tx_prims;
};
/* Represents one TX primitive in the queue of trx_ts */
struct trx_ts_prim {
/*! \brief Link to queue of TS */
struct llist_head list;
/*! \brief Logical channel type */
enum trx_lchan_type chan;
/*! \brief Payload length */
size_t payload_len;
/*! \brief Payload */
uint8_t payload[0];
};
extern const struct trx_lchan_desc trx_lchan_desc[_TRX_CHAN_MAX];
const struct trx_multiframe *sched_mframe_layout(
enum gsm_phys_chan_config config, int tn);
/* Scheduler management functions */
int sched_trx_init(struct trx_instance *trx, uint32_t fn_advance);
int sched_trx_reset(struct trx_instance *trx, int reset_clock);
int sched_trx_shutdown(struct trx_instance *trx);
/* Timeslot management functions */
struct trx_ts *sched_trx_add_ts(struct trx_instance *trx, int tn);
void sched_trx_del_ts(struct trx_instance *trx, int tn);
int sched_trx_reset_ts(struct trx_instance *trx, int tn);
int sched_trx_configure_ts(struct trx_instance *trx, int tn,
enum gsm_phys_chan_config config);
int sched_trx_start_ciphering(struct trx_ts *ts, uint8_t algo,
uint8_t *key, uint8_t key_len);
/* Logical channel management functions */
enum gsm_phys_chan_config sched_trx_chan_nr2pchan_config(uint8_t chan_nr);
enum trx_lchan_type sched_trx_chan_nr2lchan_type(uint8_t chan_nr,
uint8_t link_id);
void sched_trx_deactivate_all_lchans(struct trx_ts *ts);
int sched_trx_set_lchans(struct trx_ts *ts, uint8_t chan_nr, int active, uint8_t tch_mode);
int sched_trx_activate_lchan(struct trx_ts *ts, enum trx_lchan_type chan);
int sched_trx_deactivate_lchan(struct trx_ts *ts, enum trx_lchan_type chan);
struct trx_lchan_state *sched_trx_find_lchan(struct trx_ts *ts,
enum trx_lchan_type chan);
/* Primitive management functions */
int sched_prim_init(struct trx_instance *trx, struct trx_ts_prim **prim,
size_t pl_len, uint8_t chan_nr, uint8_t link_id);
int sched_prim_push(struct trx_instance *trx,
struct trx_ts_prim *prim, uint8_t chan_nr);
#define TCH_MODE_IS_SPEECH(mode) \
(mode == GSM48_CMODE_SPEECH_V1 \
|| mode == GSM48_CMODE_SPEECH_EFR \
|| mode == GSM48_CMODE_SPEECH_AMR)
#define TCH_MODE_IS_DATA(mode) \
(mode == GSM48_CMODE_DATA_14k5 \
|| mode == GSM48_CMODE_DATA_12k0 \
|| mode == GSM48_CMODE_DATA_6k0 \
|| mode == GSM48_CMODE_DATA_3k6)
#define CHAN_IS_TCH(chan) \
(chan == TRXC_TCHF || chan == TRXC_TCHH_0 || chan == TRXC_TCHH_1)
#define CHAN_IS_SACCH(chan) \
(trx_lchan_desc[chan].link_id & TRX_CH_LID_SACCH)
#define PRIM_IS_TCH(prim) \
CHAN_IS_TCH(prim->chan) && prim->payload_len != GSM_MACBLOCK_LEN
#define PRIM_IS_FACCH(prim) \
CHAN_IS_TCH(prim->chan) && prim->payload_len == GSM_MACBLOCK_LEN
struct trx_ts_prim *sched_prim_dequeue(struct llist_head *queue,
enum trx_lchan_type lchan_type);
int sched_prim_dummy(struct trx_lchan_state *lchan);
void sched_prim_drop(struct trx_lchan_state *lchan);
void sched_prim_flush_queue(struct llist_head *list);
int sched_trx_handle_rx_burst(struct trx_instance *trx, uint8_t tn,
uint32_t burst_fn, sbit_t *bits, uint16_t nbits,
int8_t rssi, int16_t toa256);
int sched_trx_handle_tx_burst(struct trx_instance *trx,
struct trx_ts *ts, struct trx_lchan_state *lchan,
uint32_t fn, ubit_t *bits);
/* Shared declarations for lchan handlers */
extern const uint8_t sched_nb_training_bits[8][26];
size_t sched_bad_frame_ind(uint8_t *l2, uint8_t rsl_cmode, uint8_t tch_mode);
int sched_send_dt_ind(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint8_t *l2, size_t l2_len,
int bit_error_count, bool dec_failed, bool traffic);
int sched_send_dt_conf(struct trx_instance *trx, struct trx_ts *ts,
struct trx_lchan_state *lchan, uint32_t fn, bool traffic);

View File

@ -0,0 +1,40 @@
#pragma once
#include <stdint.h>
#include <time.h>
#include <osmocom/core/timer.h>
#define GSM_SUPERFRAME (26 * 51)
#define GSM_HYPERFRAME (2048 * GSM_SUPERFRAME)
enum tdma_sched_clck_state {
SCH_CLCK_STATE_WAIT,
SCH_CLCK_STATE_OK,
};
/* Forward structure declaration */
struct trx_sched;
/*! \brief One scheduler instance */
struct trx_sched {
/*! \brief Clock state */
uint8_t state;
/*! \brief Local clock source */
struct timeval clock;
/*! \brief Count of processed frames */
uint32_t fn_counter_proc;
/*! \brief Local frame counter advance */
uint32_t fn_counter_advance;
/*! \brief Frame counter */
uint32_t fn_counter_lost;
/*! \brief Frame callback timer */
struct osmo_timer_list clock_timer;
/*! \brief Frame callback */
void (*clock_cb)(struct trx_sched *sched);
/*! \brief Private data (e.g. pointer to trx instance) */
void *data;
};
int sched_clck_handle(struct trx_sched *sched, uint32_t fn);
void sched_clck_reset(struct trx_sched *sched);

701
src/host/trxcon/trx_if.c Normal file
View File

@ -0,0 +1,701 @@
/*
* OsmocomBB <-> SDR connection bridge
* Transceiver interface handlers
*
* Copyright (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
* Copyright (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.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 <stdio.h>
#include <errno.h>
#include <stdint.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/select.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/bits.h>
#include <osmocom/core/fsm.h>
#include <osmocom/gsm/gsm_utils.h>
#include "l1ctl.h"
#include "trxcon.h"
#include "trx_if.h"
#include "logging.h"
#include "scheduler.h"
static struct value_string trx_evt_names[] = {
{ 0, NULL } /* no events? */
};
static struct osmo_fsm_state trx_fsm_states[] = {
[TRX_STATE_OFFLINE] = {
.out_state_mask = (
GEN_MASK(TRX_STATE_IDLE) |
GEN_MASK(TRX_STATE_RSP_WAIT)),
.name = "OFFLINE",
},
[TRX_STATE_IDLE] = {
.out_state_mask = UINT32_MAX,
.name = "IDLE",
},
[TRX_STATE_ACTIVE] = {
.out_state_mask = (
GEN_MASK(TRX_STATE_IDLE) |
GEN_MASK(TRX_STATE_RSP_WAIT)),
.name = "ACTIVE",
},
[TRX_STATE_RSP_WAIT] = {
.out_state_mask = (
GEN_MASK(TRX_STATE_IDLE) |
GEN_MASK(TRX_STATE_ACTIVE) |
GEN_MASK(TRX_STATE_OFFLINE)),
.name = "RSP_WAIT",
},
};
static struct osmo_fsm trx_fsm = {
.name = "trx_interface_fsm",
.states = trx_fsm_states,
.num_states = ARRAY_SIZE(trx_fsm_states),
.log_subsys = DTRX,
.event_names = trx_evt_names,
};
static int trx_udp_open(void *priv, struct osmo_fd *ofd, const char *host_local,
uint16_t port_local, const char *host_remote, uint16_t port_remote,
int (*cb)(struct osmo_fd *fd, unsigned int what))
{
int rc;
ofd->data = priv;
ofd->fd = -1;
ofd->cb = cb;
/* Init UDP Connection */
rc = osmo_sock_init2_ofd(ofd, AF_UNSPEC, SOCK_DGRAM, 0, host_local, port_local,
host_remote, port_remote,
OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT);
return rc;
}
static void trx_udp_close(struct osmo_fd *ofd)
{
if (ofd->fd > 0) {
osmo_fd_unregister(ofd);
close(ofd->fd);
ofd->fd = -1;
}
}
/* ------------------------------------------------------------------------ */
/* Control (CTRL) interface handlers */
/* ------------------------------------------------------------------------ */
/* Commands on the Per-ARFCN Control Interface */
/* */
/* The per-ARFCN control interface uses a command-response protocol. */
/* Commands are NULL-terminated ASCII strings, one per UDP socket. */
/* Each command has a corresponding response. */
/* Every command is of the form: */
/* */
/* CMD <cmdtype> [params] */
/* */
/* The <cmdtype> is the actual command. */
/* Parameters are optional depending on the commands type. */
/* Every response is of the form: */
/* */
/* RSP <cmdtype> <status> [result] */
/* */
/* The <status> is 0 for success and a non-zero error code for failure. */
/* Successful responses may include results, depending on the command type. */
/* ------------------------------------------------------------------------ */
static void trx_ctrl_timer_cb(void *data);
/* Send first CTRL message and start timer */
static void trx_ctrl_send(struct trx_instance *trx)
{
struct trx_ctrl_msg *tcm;
if (llist_empty(&trx->trx_ctrl_list))
return;
tcm = llist_entry(trx->trx_ctrl_list.next, struct trx_ctrl_msg, list);
/* Send command */
LOGP(DTRX, LOGL_DEBUG, "Sending control '%s'\n", tcm->cmd);
send(trx->trx_ofd_ctrl.fd, tcm->cmd, strlen(tcm->cmd) + 1, 0);
/* Trigger state machine */
if (trx->fsm->state != TRX_STATE_RSP_WAIT) {
trx->prev_state = trx->fsm->state;
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_RSP_WAIT, 0, 0);
}
/* Start expire timer */
trx->trx_ctrl_timer.data = trx;
trx->trx_ctrl_timer.cb = trx_ctrl_timer_cb;
osmo_timer_schedule(&trx->trx_ctrl_timer, 2, 0);
}
static void trx_ctrl_timer_cb(void *data)
{
struct trx_instance *trx = (struct trx_instance *) data;
struct trx_ctrl_msg *tcm;
/* Queue may be cleaned at this moment */
if (llist_empty(&trx->trx_ctrl_list))
return;
LOGP(DTRX, LOGL_NOTICE, "No response from transceiver...\n");
tcm = llist_entry(trx->trx_ctrl_list.next, struct trx_ctrl_msg, list);
if (++tcm->retry_cnt > 3) {
LOGP(DTRX, LOGL_NOTICE, "Transceiver offline\n");
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_OFFLINE, 0, 0);
osmo_fsm_inst_dispatch(trxcon_fsm, TRX_EVENT_OFFLINE, trx);
return;
}
/* Attempt to send a command again */
trx_ctrl_send(trx);
}
/* Add a new CTRL command to the trx_ctrl_list */
static int trx_ctrl_cmd(struct trx_instance *trx, int critical,
const char *cmd, const char *fmt, ...)
{
struct trx_ctrl_msg *tcm;
int len, pending = 0;
va_list ap;
/* TODO: make sure that transceiver online */
if (!llist_empty(&trx->trx_ctrl_list))
pending = 1;
/* Allocate a message */
tcm = talloc_zero(trx, struct trx_ctrl_msg);
if (!tcm)
return -ENOMEM;
/* Fill in command arguments */
if (fmt && fmt[0]) {
len = snprintf(tcm->cmd, sizeof(tcm->cmd) - 1, "CMD %s ", cmd);
va_start(ap, fmt);
vsnprintf(tcm->cmd + len, sizeof(tcm->cmd) - len - 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, &trx->trx_ctrl_list);
LOGP(DTRX, LOGL_INFO, "Adding new control '%s'\n", tcm->cmd);
/* Send message, if no pending messages */
if (!pending)
trx_ctrl_send(trx);
return 0;
}
/*
* Power Control
*
* ECHO is used to check transceiver availability.
* CMD ECHO
* RSP ECHO <status>
*
* POWEROFF shuts off transmitter power and stops the demodulator.
* CMD POWEROFF
* RSP POWEROFF <status>
*
* POWERON starts the transmitter and starts the demodulator.
* Initial power level is very low.
* This command fails if the transmitter and receiver are not yet tuned.
* This command fails if the transmit or receive frequency creates a conflict
* with another ARFCN that is already running.
* If the transceiver is already on, it response with success to this command.
* CMD POWERON
* RSP POWERON <status>
*/
int trx_if_cmd_echo(struct trx_instance *trx)
{
return trx_ctrl_cmd(trx, 1, "ECHO", "");
}
int trx_if_cmd_poweroff(struct trx_instance *trx)
{
return trx_ctrl_cmd(trx, 1, "POWEROFF", "");
}
int trx_if_cmd_poweron(struct trx_instance *trx)
{
return trx_ctrl_cmd(trx, 1, "POWERON", "");
}
/*
* SETPOWER sets output power in dB wrt full scale.
* This command fails if the transmitter and receiver are not running.
* CMD SETPOWER <dB>
* RSP SETPOWER <status> <dB>
*/
int trx_if_cmd_setpower(struct trx_instance *trx, int db)
{
return trx_ctrl_cmd(trx, 0, "SETPOWER", "%d", db);
}
/*
* ADJPOWER adjusts power by the given dB step.
* Response returns resulting power level wrt full scale.
* This command fails if the transmitter and receiver are not running.
* CMD ADJPOWER <dBStep>
* RSP ADJPOWER <status> <dBLevel>
*/
int trx_if_cmd_adjpower(struct trx_instance *trx, int db)
{
return trx_ctrl_cmd(trx, 0, "ADJPOWER", "%d", db);
}
/*
* Timeslot Control
*
* SETSLOT sets the format of the uplink timeslots in the ARFCN.
* The <timeslot> indicates the timeslot of interest.
* The <chantype> indicates the type of channel that occupies the timeslot.
* A chantype of zero indicates the timeslot is off.
* CMD SETSLOT <timeslot> <chantype>
* RSP SETSLOT <status> <timeslot> <chantype>
*/
int trx_if_cmd_setslot(struct trx_instance *trx, uint8_t tn, uint8_t type)
{
return trx_ctrl_cmd(trx, 1, "SETSLOT", "%d %d", tn, type);
}
/*
* Tuning Control
*
* (RX/TX)TUNE tunes the receiver to a given frequency in kHz.
* This command fails if the receiver is already running.
* (To re-tune you stop the radio, re-tune, and restart.)
* This command fails if the transmit or receive frequency
* creates a conflict with another ARFCN that is already running.
* CMD (RX/TX)TUNE <kHz>
* RSP (RX/TX)TUNE <status> <kHz>
*/
int trx_if_cmd_rxtune(struct trx_instance *trx, uint16_t arfcn)
{
uint16_t freq10;
/* RX is downlink on MS side */
freq10 = gsm_arfcn2freq10(arfcn, 0);
if (freq10 == 0xffff) {
LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", arfcn);
return -ENOTSUP;
}
return trx_ctrl_cmd(trx, 1, "RXTUNE", "%d", freq10 * 100);
}
int trx_if_cmd_txtune(struct trx_instance *trx, uint16_t arfcn)
{
uint16_t freq10;
/* TX is uplink on MS side */
freq10 = gsm_arfcn2freq10(arfcn, 1);
if (freq10 == 0xffff) {
LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", arfcn);
return -ENOTSUP;
}
return trx_ctrl_cmd(trx, 1, "TXTUNE", "%d", freq10 * 100);
}
/*
* Power measurement
*
* MEASURE instructs the transceiver to perform a power
* measurement on specified frequency. After receiving this
* request, transceiver should quickly re-tune to requested
* frequency, measure power level and re-tune back to the
* previous frequency.
* CMD MEASURE <kHz>
* RSP MEASURE <status> <kHz> <dB>
*/
int trx_if_cmd_measure(struct trx_instance *trx,
uint16_t arfcn_start, uint16_t arfcn_stop)
{
uint16_t freq10;
/* Update ARFCN range for measurement */
trx->pm_arfcn_start = arfcn_start;
trx->pm_arfcn_stop = arfcn_stop;
/* Calculate a frequency for current ARFCN (DL) */
freq10 = gsm_arfcn2freq10(arfcn_start, 0);
if (freq10 == 0xffff) {
LOGP(DTRX, LOGL_ERROR, "ARFCN %d not defined\n", arfcn_start);
return -ENOTSUP;
}
return trx_ctrl_cmd(trx, 1, "MEASURE", "%d", freq10 * 100);
}
static void trx_if_measure_rsp_cb(struct trx_instance *trx, char *resp)
{
unsigned int freq10;
uint16_t arfcn;
int dbm;
/* Parse freq. and power level */
sscanf(resp, "%u %d", &freq10, &dbm);
freq10 /= 100;
/* Check received ARFCN against expected */
arfcn = gsm_freq102arfcn((uint16_t) freq10, 0);
if (arfcn != trx->pm_arfcn_start) {
LOGP(DTRX, LOGL_ERROR, "Power measurement error: "
"response ARFCN=%u doesn't match expected ARFCN=%u\n",
arfcn &~ ARFCN_FLAG_MASK,
trx->pm_arfcn_start &~ ARFCN_FLAG_MASK);
return;
}
/* Send L1CTL_PM_CONF */
l1ctl_tx_pm_conf(trx->l1l, arfcn, dbm,
arfcn == trx->pm_arfcn_stop);
/* Schedule a next measurement */
if (arfcn != trx->pm_arfcn_stop)
trx_if_cmd_measure(trx, ++arfcn, trx->pm_arfcn_stop);
}
/*
* Timing Advance control
*
* SETTA instructs the transceiver to transmit bursts in
* advance calculated from requested TA value. This value is
* normally between 0 and 63, with each step representing
* an advance of one bit period (about 3.69 microseconds).
* CMD SETTA <0-63>
* RSP SETTA <status> <TA>
*/
int trx_if_cmd_setta(struct trx_instance *trx, int8_t ta)
{
/* Do nothing, if requested TA value matches the current */
if (trx->ta == ta)
return 0;
/* Make sure that TA value is in valid range */
if (ta < 0 || ta > 63) {
LOGP(DTRX, LOGL_ERROR, "TA value %d is out of allowed range\n", ta);
return -ENOTSUP;
}
return trx_ctrl_cmd(trx, 0, "SETTA", "%d", ta);
}
/* Get response from CTRL socket */
static int trx_ctrl_read_cb(struct osmo_fd *ofd, unsigned int what)
{
struct trx_instance *trx = ofd->data;
struct trx_ctrl_msg *tcm;
int len, resp, rsp_len;
char buf[1500], *p;
len = recv(ofd->fd, buf, sizeof(buf) - 1, 0);
if (len <= 0)
return len;
buf[len] = '\0';
if (!!strncmp(buf, "RSP ", 4)) {
LOGP(DTRX, LOGL_NOTICE, "Unknown message on CTRL port: %s\n", buf);
return 0;
}
/* Calculate the length of response item */
p = strchr(buf + 4, ' ');
rsp_len = p ? p - buf - 4 : strlen(buf) - 4;
LOGP(DTRX, LOGL_INFO, "Response message: '%s'\n", buf);
/* Abort expire timer */
if (osmo_timer_pending(&trx->trx_ctrl_timer))
osmo_timer_del(&trx->trx_ctrl_timer);
/* Get command for response message */
if (llist_empty(&trx->trx_ctrl_list)) {
LOGP(DTRX, LOGL_NOTICE, "Response message without command\n");
return -EINVAL;
}
tcm = llist_entry(trx->trx_ctrl_list.next,
struct trx_ctrl_msg, list);
/* Check if response matches command */
if (!!strncmp(buf + 4, tcm->cmd + 4, rsp_len)) {
LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR,
"Response message '%s' does not match command "
"message '%s'\n", buf, tcm->cmd);
goto rsp_error;
}
/* Check for response code */
sscanf(p + 1, "%d", &resp);
if (resp) {
LOGP(DTRX, (tcm->critical) ? LOGL_FATAL : LOGL_ERROR,
"Transceiver rejected TRX command with "
"response: '%s'\n", buf);
if (tcm->critical)
goto rsp_error;
}
/* Trigger state machine */
if (!strncmp(tcm->cmd + 4, "POWERON", 7))
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_ACTIVE, 0, 0);
else if (!strncmp(tcm->cmd + 4, "POWEROFF", 8))
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
else if (!strncmp(tcm->cmd + 4, "MEASURE", 7))
trx_if_measure_rsp_cb(trx, buf + 14);
else if (!strncmp(tcm->cmd + 4, "ECHO", 4))
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
else
osmo_fsm_inst_state_chg(trx->fsm, trx->prev_state, 0, 0);
/* Remove command from list */
llist_del(&tcm->list);
talloc_free(tcm);
/* Send next message, if any */
trx_ctrl_send(trx);
return 0;
rsp_error:
/* Notify higher layers about the problem */
osmo_fsm_inst_dispatch(trxcon_fsm, TRX_EVENT_RSP_ERROR, trx);
return -EIO;
}
/* ------------------------------------------------------------------------ */
/* Data interface handlers */
/* ------------------------------------------------------------------------ */
/* DATA interface */
/* */
/* Messages on the data interface carry one radio burst per UDP message. */
/* */
/* Received Data Burst: */
/* 1 byte timeslot index */
/* 4 bytes GSM frame number, BE */
/* 1 byte RSSI in -dBm */
/* 2 bytes correlator timing offset in 1/256 symbol steps, 2's-comp, BE */
/* 148 bytes soft symbol estimates, 0 -> definite "0", 255 -> definite "1" */
/* 2 bytes are not used, but being sent by OsmoTRX */
/* */
/* Transmit Data Burst: */
/* 1 byte timeslot index */
/* 4 bytes GSM frame number, BE */
/* 1 byte transmit level wrt ARFCN max, -dB (attenuation) */
/* 148 bytes output symbol values, 0 & 1 */
/* ------------------------------------------------------------------------ */
static int trx_data_rx_cb(struct osmo_fd *ofd, unsigned int what)
{
struct trx_instance *trx = ofd->data;
uint8_t buf[256];
sbit_t bits[148];
int8_t rssi, tn;
int16_t toa256;
uint32_t fn;
int len;
len = recv(ofd->fd, buf, sizeof(buf), 0);
if (len <= 0)
return len;
if (len != 158) {
LOGP(DTRXD, LOGL_ERROR, "Got data message with invalid "
"length '%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];
toa256 = ((int16_t) (buf[6] << 8) | buf[7]);
/* Copy and convert bits {254..0} to sbits {-127..127} */
osmo_ubit2sbit(bits, buf + 8, 148);
if (tn >= 8) {
LOGP(DTRXD, LOGL_ERROR, "Illegal TS %d\n", tn);
return -EINVAL;
}
if (fn >= 2715648) {
LOGP(DTRXD, LOGL_ERROR, "Illegal FN %u\n", fn);
return -EINVAL;
}
LOGP(DTRXD, LOGL_DEBUG, "RX burst tn=%u fn=%u rssi=%d toa=%d\n",
tn, fn, rssi, toa256);
/* Poke scheduler */
sched_trx_handle_rx_burst(trx, tn, fn, bits, 148, rssi, toa256);
/* Correct local clock counter */
if (fn % 51 == 0)
sched_clck_handle(&trx->sched, fn);
return 0;
}
int trx_if_tx_burst(struct trx_instance *trx, uint8_t tn, uint32_t fn,
uint8_t pwr, const ubit_t *bits)
{
uint8_t buf[256];
/**
* We must be sure that we have clock,
* and we have sent all control data
*
* TODO: should we wait in TRX_STATE_RSP_WAIT state?
*/
if (trx->fsm->state != TRX_STATE_ACTIVE) {
LOGP(DTRXD, LOGL_DEBUG, "Ignoring TX data, "
"transceiver isn't ready\n");
return -EAGAIN;
}
LOGP(DTRXD, 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);
/* Send data to transceiver */
send(trx->trx_ofd_data.fd, buf, 154, 0);
return 0;
}
/*
* Open/close OsmoTRX connection
*/
int trx_if_open(struct trx_instance **trx, const char *local_host,
const char *remote_host, uint16_t port)
{
struct trx_instance *trx_new;
int rc;
LOGP(DTRX, LOGL_NOTICE, "Init transceiver interface\n");
/* Try to allocate memory */
trx_new = talloc_zero(tall_trx_ctx, struct trx_instance);
if (!trx_new) {
LOGP(DTRX, LOGL_ERROR, "Failed to allocate memory\n");
return -ENOMEM;
}
/* Initialize CTRL queue */
INIT_LLIST_HEAD(&trx_new->trx_ctrl_list);
/* Open sockets */
rc = trx_udp_open(trx_new, &trx_new->trx_ofd_ctrl, local_host,
port + 101, remote_host, port + 1, trx_ctrl_read_cb);
if (rc < 0)
goto error;
rc = trx_udp_open(trx_new, &trx_new->trx_ofd_data, local_host,
port + 102, remote_host, port + 2, trx_data_rx_cb);
if (rc < 0)
goto error;
/* Allocate a new dedicated state machine */
osmo_fsm_register(&trx_fsm);
trx_new->fsm = osmo_fsm_inst_alloc(&trx_fsm, trx_new,
NULL, LOGL_DEBUG, "trx_interface");
*trx = trx_new;
return 0;
error:
LOGP(DTRX, LOGL_ERROR, "Couldn't establish UDP connection\n");
talloc_free(trx_new);
return rc;
}
/* Flush pending control messages */
void trx_if_flush_ctrl(struct trx_instance *trx)
{
struct trx_ctrl_msg *tcm;
/* Reset state machine */
osmo_fsm_inst_state_chg(trx->fsm, TRX_STATE_IDLE, 0, 0);
/* Clear command queue */
while (!llist_empty(&trx->trx_ctrl_list)) {
tcm = llist_entry(trx->trx_ctrl_list.next,
struct trx_ctrl_msg, list);
llist_del(&tcm->list);
talloc_free(tcm);
}
}
void trx_if_close(struct trx_instance *trx)
{
/* May be unallocated due to init error */
if (!trx)
return;
LOGP(DTRX, LOGL_NOTICE, "Shutdown transceiver interface\n");
/* Flush CTRL message list */
trx_if_flush_ctrl(trx);
/* Close sockets */
trx_udp_close(&trx->trx_ofd_ctrl);
trx_udp_close(&trx->trx_ofd_data);
/* Free memory */
osmo_fsm_inst_free(trx->fsm);
talloc_free(trx);
}

78
src/host/trxcon/trx_if.h Normal file
View File

@ -0,0 +1,78 @@
#pragma once
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/select.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/fsm.h>
#include "scheduler.h"
#include "sched_trx.h"
/* Forward declaration to avoid mutual include */
struct l1ctl_link;
enum trx_fsm_states {
TRX_STATE_OFFLINE = 0,
TRX_STATE_IDLE,
TRX_STATE_ACTIVE,
TRX_STATE_RSP_WAIT,
};
struct trx_instance {
struct osmo_fd trx_ofd_ctrl;
struct osmo_fd trx_ofd_data;
struct osmo_timer_list trx_ctrl_timer;
struct llist_head trx_ctrl_list;
struct osmo_fsm_inst *fsm;
uint32_t prev_state;
/* GSM L1 specific */
uint16_t pm_arfcn_start;
uint16_t pm_arfcn_stop;
uint16_t band_arfcn;
uint8_t tx_power;
uint8_t bsic;
uint8_t tsc;
int8_t ta;
/* Scheduler stuff */
struct trx_sched sched;
struct trx_ts *ts_list[TRX_TS_COUNT];
/* Bind L1CTL link */
struct l1ctl_link *l1l;
};
struct trx_ctrl_msg {
struct llist_head list;
char cmd[128];
int retry_cnt;
int critical;
int cmd_len;
};
int trx_if_open(struct trx_instance **trx, const char *local_host,
const char *remote_host, uint16_t port);
void trx_if_flush_ctrl(struct trx_instance *trx);
void trx_if_close(struct trx_instance *trx);
int trx_if_cmd_poweron(struct trx_instance *trx);
int trx_if_cmd_poweroff(struct trx_instance *trx);
int trx_if_cmd_echo(struct trx_instance *trx);
int trx_if_cmd_setpower(struct trx_instance *trx, int db);
int trx_if_cmd_adjpower(struct trx_instance *trx, int db);
int trx_if_cmd_setta(struct trx_instance *trx, int8_t ta);
int trx_if_cmd_rxtune(struct trx_instance *trx, uint16_t arfcn);
int trx_if_cmd_txtune(struct trx_instance *trx, uint16_t arfcn);
int trx_if_cmd_setslot(struct trx_instance *trx, uint8_t tn, uint8_t type);
int trx_if_cmd_measure(struct trx_instance *trx,
uint16_t arfcn_start, uint16_t arfcn_stop);
int trx_if_tx_burst(struct trx_instance *trx, uint8_t tn, uint32_t fn,
uint8_t pwr, const ubit_t *bits);

317
src/host/trxcon/trxcon.c Normal file
View File

@ -0,0 +1,317 @@
/*
* OsmocomBB <-> SDR connection bridge
*
* (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
*
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 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 General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <getopt.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <arpa/inet.h>
#include <osmocom/core/fsm.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/signal.h>
#include <osmocom/core/select.h>
#include <osmocom/core/application.h>
#include <osmocom/gsm/gsm_utils.h>
#include "trxcon.h"
#include "trx_if.h"
#include "logging.h"
#include "l1ctl.h"
#include "l1ctl_link.h"
#include "l1ctl_proto.h"
#include "scheduler.h"
#include "sched_trx.h"
#define COPYRIGHT \
"Copyright (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>\n" \
"License GPLv2+: GNU GPL version 2 or later " \
"<http://gnu.org/licenses/gpl.html>\n" \
"This is free software: you are free to change and redistribute it.\n" \
"There is NO WARRANTY, to the extent permitted by law.\n\n"
static struct {
const char *debug_mask;
int daemonize;
int quit;
/* L1CTL specific */
struct l1ctl_link *l1l;
const char *bind_socket;
/* TRX specific */
struct trx_instance *trx;
const char *trx_ip;
uint16_t trx_base_port;
uint32_t trx_fn_advance;
} app_data;
void *tall_trx_ctx = NULL;
struct osmo_fsm_inst *trxcon_fsm;
static void trxcon_fsm_idle_action(struct osmo_fsm_inst *fi,
uint32_t event, void *data)
{
if (event == L1CTL_EVENT_CONNECT)
osmo_fsm_inst_state_chg(trxcon_fsm, TRXCON_STATE_MANAGED, 0, 0);
}
static void trxcon_fsm_managed_action(struct osmo_fsm_inst *fi,
uint32_t event, void *data)
{
switch (event) {
case L1CTL_EVENT_DISCONNECT:
osmo_fsm_inst_state_chg(trxcon_fsm, TRXCON_STATE_IDLE, 0, 0);
if (app_data.trx->fsm->state != TRX_STATE_OFFLINE) {
/* Reset scheduler and clock counter */
sched_trx_reset(app_data.trx, 1);
/* TODO: implement trx_if_reset() */
trx_if_cmd_poweroff(app_data.trx);
trx_if_cmd_echo(app_data.trx);
}
break;
case TRX_EVENT_RSP_ERROR:
case TRX_EVENT_OFFLINE:
/* TODO: notify L2 & L3 about that */
break;
default:
LOGPFSML(fi, LOGL_ERROR, "Unhandled event %u\n", event);
}
}
static struct osmo_fsm_state trxcon_fsm_states[] = {
[TRXCON_STATE_IDLE] = {
.in_event_mask = GEN_MASK(L1CTL_EVENT_CONNECT),
.out_state_mask = GEN_MASK(TRXCON_STATE_MANAGED),
.name = "IDLE",
.action = trxcon_fsm_idle_action,
},
[TRXCON_STATE_MANAGED] = {
.in_event_mask = (
GEN_MASK(L1CTL_EVENT_DISCONNECT) |
GEN_MASK(TRX_EVENT_RSP_ERROR) |
GEN_MASK(TRX_EVENT_OFFLINE)),
.out_state_mask = GEN_MASK(TRXCON_STATE_IDLE),
.name = "MANAGED",
.action = trxcon_fsm_managed_action,
},
};
static const struct value_string app_evt_names[] = {
OSMO_VALUE_STRING(L1CTL_EVENT_CONNECT),
OSMO_VALUE_STRING(L1CTL_EVENT_DISCONNECT),
OSMO_VALUE_STRING(TRX_EVENT_OFFLINE),
OSMO_VALUE_STRING(TRX_EVENT_RSP_ERROR),
{ 0, NULL }
};
static struct osmo_fsm trxcon_fsm_def = {
.name = "trxcon_app_fsm",
.states = trxcon_fsm_states,
.num_states = ARRAY_SIZE(trxcon_fsm_states),
.log_subsys = DAPP,
.event_names = app_evt_names,
};
static void print_usage(const char *app)
{
printf("Usage: %s\n", app);
}
static void print_help(void)
{
printf(" Some help...\n");
printf(" -h --help this text\n");
printf(" -d --debug Change debug flags. Default: %s\n", DEBUG_DEFAULT);
printf(" -i --trx-ip IP address of host runing TRX (default 127.0.0.1)\n");
printf(" -p --trx-port Base port of TRX instance (default 6700)\n");
printf(" -f --trx-advance Scheduler clock advance (default 20)\n");
printf(" -s --socket Listening socket for layer23 (default /tmp/osmocom_l2)\n");
printf(" -D --daemonize Run as daemon\n");
}
static void handle_options(int argc, char **argv)
{
while (1) {
int option_index = 0, c;
static struct option long_options[] = {
{"help", 0, 0, 'h'},
{"debug", 1, 0, 'd'},
{"socket", 1, 0, 's'},
{"trx-ip", 1, 0, 'i'},
{"trx-port", 1, 0, 'p'},
{"trx-advance", 1, 0, 'f'},
{"daemonize", 0, 0, 'D'},
{0, 0, 0, 0}
};
c = getopt_long(argc, argv, "d:i:p:f:s:Dh",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'h':
print_usage(argv[0]);
print_help();
exit(0);
break;
case 'd':
app_data.debug_mask = optarg;
break;
case 'i':
app_data.trx_ip = optarg;
break;
case 'p':
app_data.trx_base_port = atoi(optarg);
break;
case 'f':
app_data.trx_fn_advance = atoi(optarg);
break;
case 's':
app_data.bind_socket = optarg;
break;
case 'D':
app_data.daemonize = 1;
break;
default:
break;
}
}
}
static void init_defaults(void)
{
app_data.bind_socket = "/tmp/osmocom_l2";
app_data.trx_ip = "127.0.0.1";
app_data.trx_base_port = 6700;
app_data.trx_fn_advance = 20;
app_data.debug_mask = NULL;
app_data.daemonize = 0;
app_data.quit = 0;
}
static void signal_handler(int signal)
{
fprintf(stderr, "signal %u received\n", signal);
switch (signal) {
case SIGINT:
app_data.quit++;
break;
case SIGABRT:
case SIGUSR1:
case SIGUSR2:
talloc_report_full(tall_trx_ctx, stderr);
break;
default:
break;
}
}
int main(int argc, char **argv)
{
int rc = 0;
printf("%s", COPYRIGHT);
init_defaults();
handle_options(argc, argv);
/* Init talloc memory management system */
tall_trx_ctx = talloc_init("trxcon context");
msgb_talloc_ctx_init(tall_trx_ctx, 0);
/* Setup signal handlers */
signal(SIGINT, &signal_handler);
signal(SIGUSR1, &signal_handler);
signal(SIGUSR2, &signal_handler);
osmo_init_ignore_signals();
/* Init logging system */
trx_log_init(app_data.debug_mask);
/* Allocate the application state machine */
osmo_fsm_register(&trxcon_fsm_def);
trxcon_fsm = osmo_fsm_inst_alloc(&trxcon_fsm_def, tall_trx_ctx,
NULL, LOGL_DEBUG, "main");
/* Init L1CTL server */
rc = l1ctl_link_init(&app_data.l1l, app_data.bind_socket);
if (rc)
goto exit;
/* Init transceiver interface */
rc = trx_if_open(&app_data.trx, "0.0.0.0", app_data.trx_ip, app_data.trx_base_port);
if (rc)
goto exit;
/* Bind L1CTL with TRX and vice versa */
app_data.l1l->trx = app_data.trx;
app_data.trx->l1l = app_data.l1l;
/* Init scheduler */
rc = sched_trx_init(app_data.trx, app_data.trx_fn_advance);
if (rc)
goto exit;
LOGP(DAPP, LOGL_NOTICE, "Init complete\n");
if (app_data.daemonize) {
rc = osmo_daemonize();
if (rc < 0) {
perror("Error during daemonize");
goto exit;
}
}
/* Initialize pseudo-random generator */
srand(time(NULL));
while (!app_data.quit)
osmo_select_main(0);
exit:
/* Close active connections */
l1ctl_link_shutdown(app_data.l1l);
sched_trx_shutdown(app_data.trx);
trx_if_close(app_data.trx);
/* Shutdown main state machine */
osmo_fsm_inst_free(trxcon_fsm);
/* Make Valgrind happy */
log_fini();
talloc_free(tall_trx_ctx);
return rc;
}

21
src/host/trxcon/trxcon.h Normal file
View File

@ -0,0 +1,21 @@
#pragma once
#define GEN_MASK(state) (0x01 << state)
extern struct osmo_fsm_inst *trxcon_fsm;
extern void *tall_trx_ctx;
enum trxcon_fsm_states {
TRXCON_STATE_IDLE = 0,
TRXCON_STATE_MANAGED,
};
enum trxcon_fsm_events {
/* L1CTL specific events */
L1CTL_EVENT_CONNECT,
L1CTL_EVENT_DISCONNECT,
/* TRX specific events */
TRX_EVENT_RSP_ERROR,
TRX_EVENT_OFFLINE,
};

4
src/target/trx_toolkit/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

View File

@ -0,0 +1,34 @@
TRX toolkit is a set of tools intended for hacking and debugging
a TRX interface between both transceiver and L1 software, and
emulating a virtual Um-interface between OsmocomBB and OsmoBTS.
Brief description of available applications:
- fake_trx.py - main application, that allows to connect both
OsmocomBB and OsmoBTS without actual RF hardware. Currently
only a single MS may work with a single BTS.
- clck_gen.py - a peripheral tool aimed to emulate TDMA frame
clock generator. Could be used for testing and clock
synchronization of multiple applications. It should be noted,
that one relays on generic system timer (via Python), so
a random clock jitter takes place.
- ctrl_cmd.py - another peripheral tool, which could be used
for sending CTRL commands directly in manual mode, and also
for application fuzzing.
- burst_gen.py - a tool for sending GSM bursts either to L1
(OsmoBTS or OsmocomBB) or to TRX (OsmoTRX and GR-GSM TRX).
Currently it is only possible to generate random bursts of
different types: NB, FB, SB, AB.
- burst_send.py - a tool for sending existing bursts from a
capture file either to L1 (OsmoBTS or OsmocomBB) or to
TRX (e.g. OsmoTRX or GR-GSM TRX).
- trx_sniff.py - Scapy-based TRX protocol sniffer. Allows one
to observe a single connection between TRX and L1, and vice
versa. Also provides some capabilities for filtering bursts
by direction, frame and timeslot numbers, and for recording
captured messages to a binary file.

View File

@ -0,0 +1,216 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# TRX Toolkit
# BTS <-> BB burst forwarding
#
# (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com>
#
# All Rights Reserved
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import random
from data_msg import *
class BurstForwarder:
# Timeslot filter (drop everything by default)
ts_pass = None
# Freq. filter
bts_freq = None
bb_freq = None
# Randomization of RSSI
randomize_dl_rssi = False
randomize_ul_rssi = False
# Randomization of ToA
randomize_dl_toa256 = False
randomize_ul_toa256 = False
# Timing Advance value indicated by MS (0 by default)
# Valid range: 0..63, where each unit means
# one GSM symbol advance.
ta = 0
# Timing of Arrival values indicated by transceiver
# in units of 1/256 of GSM symbol periods. A pair of
# base and threshold values defines a range of ToA value
# randomization: from (base - threshold) to (base + threshold).
toa256_dl_base = 0
toa256_ul_base = 0
toa256_dl_threshold = 128
toa256_ul_threshold = 128
# RSSI values indicated by transceiver in dBm.
# A pair of base and threshold values defines a range of RSSI
# randomization: from (base - threshold) to (base + threshold).
rssi_dl_base = -60
rssi_ul_base = -70
rssi_dl_threshold = 10
rssi_ul_threshold = 5
def __init__(self, bts_link, bb_link):
self.bts_link = bts_link
self.bb_link = bb_link
# Converts TA value from symbols to
# units of 1/256 of GSM symbol periods
def calc_ta256(self):
return self.ta * 256
# Calculates a random ToA value for Downlink bursts
def calc_dl_toa256(self):
# Check if randomization is required
if not self.randomize_dl_toa256:
return self.toa256_dl_base
# Calculate a range for randomization
toa256_min = self.toa256_dl_base - self.toa256_dl_threshold
toa256_max = self.toa256_dl_base + self.toa256_dl_threshold
# Generate a random ToA value
toa256 = random.randint(toa256_min, toa256_max)
return toa256
# Calculates a random ToA value for Uplink bursts
def calc_ul_toa256(self):
# Check if randomization is required
if not self.randomize_ul_toa256:
return self.toa256_ul_base
# Calculate a range for randomization
toa256_min = self.toa256_ul_base - self.toa256_ul_threshold
toa256_max = self.toa256_ul_base + self.toa256_ul_threshold
# Generate a random ToA value
toa256 = random.randint(toa256_min, toa256_max)
return toa256
# Calculates a random RSSI value for Downlink bursts
def calc_dl_rssi(self):
# Check if randomization is required
if not self.randomize_dl_rssi:
return self.rssi_dl_base
# Calculate a range for randomization
rssi_min = self.rssi_dl_base - self.rssi_dl_threshold
rssi_max = self.rssi_dl_base + self.rssi_dl_threshold
# Generate a random RSSI value
return random.randint(rssi_min, rssi_max)
# Calculates a random RSSI value for Uplink bursts
def calc_ul_rssi(self):
# Check if randomization is required
if not self.randomize_ul_rssi:
return self.rssi_ul_base
# Calculate a range for randomization
rssi_min = self.rssi_ul_base - self.rssi_ul_threshold
rssi_max = self.rssi_ul_base + self.rssi_ul_threshold
# Generate a random RSSI value
return random.randint(rssi_min, rssi_max)
# Converts a L12TRX message to TRX2L1 message
def transform_msg(self, msg_raw, dl = True):
# Attempt to parse a message
try:
msg_l12trx = DATAMSG_L12TRX()
msg_l12trx.parse_msg(bytearray(msg_raw))
except:
print("[!] Dropping unhandled DL message...")
return None
# Compose a new message for L1
msg_trx2l1 = msg_l12trx.gen_trx2l1()
# Randomize both RSSI and ToA values
if dl:
msg_trx2l1.toa256 = self.calc_dl_toa256()
msg_trx2l1.rssi = self.calc_dl_rssi()
else:
msg_trx2l1.toa256 = self.calc_ul_toa256()
msg_trx2l1.toa256 -= self.calc_ta256()
msg_trx2l1.rssi = self.calc_ul_rssi()
return msg_trx2l1
# Downlink handler: BTS -> BB
def bts2bb(self):
# Read data from socket
data, addr = self.bts_link.sock.recvfrom(512)
# BB is not connected / tuned
if self.bb_freq is None:
return None
# Freq. filter
if self.bb_freq != self.bts_freq:
return None
# Process a message
msg = self.transform_msg(data, dl = True)
if msg is None:
return None
# Timeslot filter
if msg.tn != self.ts_pass:
return None
# Validate and generate the payload
payload = msg.gen_msg()
# Append two unused bytes at the end
# in order to keep the compatibility
payload += bytearray(2)
# Send burst to BB
self.bb_link.send(payload)
# Uplink handler: BB -> BTS
def bb2bts(self):
# Read data from socket
data, addr = self.bb_link.sock.recvfrom(512)
# BTS is not connected / tuned
if self.bts_freq is None:
return None
# Freq. filter
if self.bb_freq != self.bts_freq:
return None
# Process a message
msg = self.transform_msg(data, dl = False)
if msg is None:
return None
# Validate and generate the payload
payload = msg.gen_msg()
# Append two unused bytes at the end
# in order to keep the compatibility
payload += bytearray(2)
# Send burst to BTS
self.bts_link.send(payload)

View File

@ -0,0 +1,248 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# TRX Toolkit
# Auxiliary tool to generate and send random bursts via TRX DATA
# interface, which may be useful for fuzzing and testing
#
# (C) 2017-2018 by Vadim Yanitskiy <axilirator@gmail.com>
#
# All Rights Reserved
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from copyright import print_copyright
CR_HOLDERS = [("2017-2018", "Vadim Yanitskiy <axilirator@gmail.com>")]
import signal
import getopt
import sys
from rand_burst_gen import RandBurstGen
from data_dump import DATADumpFile
from data_if import DATAInterface
from gsm_shared import *
from data_msg import *
class Application:
# Application variables
remote_addr = "127.0.0.1"
bind_addr = "0.0.0.0"
base_port = 5700
conn_mode = "TRX"
output_file = None
burst_type = None
burst_count = 1
# Common header fields
fn = None
tn = None
# Message specific header fields
toa256 = None
rssi = None
pwr = None
def __init__(self):
print_copyright(CR_HOLDERS)
self.parse_argv()
self.check_argv()
# Set up signal handlers
signal.signal(signal.SIGINT, self.sig_handler)
# Open requested capture file
if self.output_file is not None:
self.ddf = DATADumpFile(self.output_file)
def run(self):
# Init DATA interface with TRX or L1
if self.conn_mode == "TRX":
self.data_if = DATAInterface(self.remote_addr, self.base_port + 2,
self.bind_addr, self.base_port + 102)
elif self.conn_mode == "L1":
self.data_if = DATAInterface(self.remote_addr, self.base_port + 102,
self.bind_addr, self.base_port + 2)
# Init random burst generator
burst_gen = RandBurstGen()
# Init an empty DATA message
if self.conn_mode == "TRX":
msg = DATAMSG_L12TRX()
elif self.conn_mode == "L1":
msg = DATAMSG_TRX2L1()
# Generate a random frame number or use provided one
fn_init = msg.rand_fn() if self.fn is None else self.fn
# Send as much bursts as required
for i in range(self.burst_count):
# Randomize the message header
msg.rand_hdr()
# Increase and set frame number
msg.fn = (fn_init + i) % GSM_HYPERFRAME
# Set timeslot number
if self.tn is not None:
msg.tn = self.tn
# Set transmit power level
if self.pwr is not None:
msg.pwr = self.pwr
# Set time of arrival
if self.toa256 is not None:
msg.toa256 = self.toa256
# Set RSSI
if self.rssi is not None:
msg.rssi = self.rssi
# Generate a random burst
if self.burst_type == "NB":
burst = burst_gen.gen_nb()
elif self.burst_type == "FB":
burst = burst_gen.gen_fb()
elif self.burst_type == "SB":
burst = burst_gen.gen_sb()
elif self.burst_type == "AB":
burst = burst_gen.gen_ab()
# Convert to soft-bits in case of TRX -> L1 message
if self.conn_mode == "L1":
burst = msg.ubit2sbit(burst)
# Set burst
msg.burst = burst
print("[i] Sending %d/%d %s burst %s to %s..."
% (i + 1, self.burst_count, self.burst_type,
msg.desc_hdr(), self.conn_mode))
# Send message
self.data_if.send_msg(msg)
# Append a new message to the capture
if self.output_file is not None:
self.ddf.append_msg(msg)
def print_help(self, msg = None):
s = " Usage: " + sys.argv[0] + " [options]\n\n" \
" Some help...\n" \
" -h --help this text\n\n"
s += " TRX interface specific\n" \
" -o --output-file Write bursts to a capture file\n" \
" -m --conn-mode Send bursts to: TRX (default) / L1\n" \
" -r --remote-addr Set remote address (default %s)\n" \
" -b --bind-addr Set local address (default %s)\n" \
" -p --base-port Set base port number (default %d)\n\n"
s += " Burst generation\n" \
" -b --burst-type Random burst type (NB, FB, SB, AB)\n" \
" -c --burst-count How much bursts to send (default 1)\n" \
" -f --frame-number Set frame number (default random)\n" \
" -t --timeslot Set timeslot index (default random)\n" \
" --pwr Set power level (default random)\n" \
" --rssi Set RSSI (default random)\n" \
" --toa Set ToA in symbols (default random)\n" \
" --toa256 Set ToA in 1/256 symbol periods\n"
print(s % (self.remote_addr, self.bind_addr, self.base_port))
if msg is not None:
print(msg)
def parse_argv(self):
try:
opts, args = getopt.getopt(sys.argv[1:],
"o:m:r:b:p:b:c:f:t:h",
[
"help",
"output-file="
"conn-mode=",
"remote-addr=",
"bind-addr=",
"base-port=",
"burst-type=",
"burst-count=",
"frame-number=",
"timeslot=",
"rssi=",
"toa=",
"toa256=",
"pwr=",
])
except getopt.GetoptError as err:
self.print_help("[!] " + str(err))
sys.exit(2)
for o, v in opts:
if o in ("-h", "--help"):
self.print_help()
sys.exit(2)
elif o in ("-o", "--output-file"):
self.output_file = v
elif o in ("-m", "--conn-mode"):
self.conn_mode = v
elif o in ("-r", "--remote-addr"):
self.remote_addr = v
elif o in ("-b", "--bind-addr"):
self.bind_addr = v
elif o in ("-p", "--base-port"):
self.base_port = int(v)
elif o in ("-b", "--burst-type"):
self.burst_type = v
elif o in ("-c", "--burst-count"):
self.burst_count = int(v)
elif o in ("-f", "--frame-number"):
self.fn = int(v)
elif o in ("-t", "--timeslot"):
self.tn = int(v)
# Message specific header fields
elif o == "--pwr":
self.pwr = int(v)
elif o == "--rssi":
self.rssi = int(v)
elif o == "--toa256":
self.toa256 = int(v)
elif o == "--toa":
self.toa256 = int(float(v) * 256.0 + 0.5)
def check_argv(self):
# Check connection mode
if self.conn_mode not in ("TRX", "L1"):
self.print_help("[!] Unknown connection type")
sys.exit(2)
# Check connection mode
if self.burst_type not in ("NB", "FB", "SB", "AB"):
self.print_help("[!] Unknown burst type")
sys.exit(2)
def sig_handler(self, signum, frame):
print("Signal %d received" % signum)
if signum is signal.SIGINT:
sys.exit(0)
if __name__ == '__main__':
app = Application()
app.run()

View File

@ -0,0 +1,218 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# TRX Toolkit
# Auxiliary tool to send existing bursts via TRX DATA interface
#
# (C) 2017-2018 by Vadim Yanitskiy <axilirator@gmail.com>
#
# All Rights Reserved
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from copyright import print_copyright
CR_HOLDERS = [("2017-2018", "Vadim Yanitskiy <axilirator@gmail.com>")]
import signal
import getopt
import sys
from data_dump import DATADumpFile
from data_if import DATAInterface
from gsm_shared import *
from data_msg import *
class Application:
# Application variables
remote_addr = "127.0.0.1"
bind_addr = "0.0.0.0"
base_port = 5700
conn_mode = "TRX"
# Burst source
capture_file = None
# Count limitations
msg_skip = None
msg_count = None
# Pass filtering
pf_fn_lt = None
pf_fn_gt = None
pf_tn = None
def __init__(self):
print_copyright(CR_HOLDERS)
self.parse_argv()
# Set up signal handlers
signal.signal(signal.SIGINT, self.sig_handler)
# Open requested capture file
self.ddf = DATADumpFile(self.capture_file)
def run(self):
# Init DATA interface with TRX or L1
if self.conn_mode == "TRX":
self.data_if = DATAInterface(self.remote_addr, self.base_port + 2,
self.bind_addr, self.base_port + 102)
l12trx = True
elif self.conn_mode == "L1":
self.data_if = DATAInterface(self.remote_addr, self.base_port + 102,
self.bind_addr, self.base_port + 2)
l12trx = False
else:
self.print_help("[!] Unknown connection type")
sys.exit(2)
# Read messages from the capture
messages = self.ddf.parse_all(
skip = self.msg_skip, count = self.msg_count)
if messages is False:
pass # FIXME!!!
for msg in messages:
# Pass filter
if not self.msg_pass_filter(l12trx, msg):
continue
print("[i] Sending a burst %s to %s..."
% (msg.desc_hdr(), self.conn_mode))
# Send message
self.data_if.send_msg(msg)
def msg_pass_filter(self, l12trx, msg):
# Direction filter
if isinstance(msg, DATAMSG_L12TRX) and not l12trx:
return False
elif isinstance(msg, DATAMSG_TRX2L1) and l12trx:
return False
# Timeslot filter
if self.pf_tn is not None:
if msg.tn != self.pf_tn:
return False
# Frame number filter
if self.pf_fn_lt is not None:
if msg.fn > self.pf_fn_lt:
return False
if self.pf_fn_gt is not None:
if msg.fn < self.pf_fn_gt:
return False
# Burst passed ;)
return True
def print_help(self, msg = None):
s = " Usage: " + sys.argv[0] + " [options]\n\n" \
" Some help...\n" \
" -h --help this text\n\n"
s += " TRX interface specific\n" \
" -m --conn-mode Send bursts to: TRX (default) / L1\n" \
" -r --remote-addr Set remote address (default %s)\n" \
" -b --bind-addr Set bind address (default %s)\n" \
" -p --base-port Set base port number (default %d)\n\n"
s += " Burst source\n" \
" -i --capture-file Read bursts from capture file\n\n" \
s += " Count limitations (disabled by default)\n" \
" --msg-skip NUM Skip NUM messages before sending\n" \
" --msg-count NUM Stop after sending NUM messages\n\n" \
s += " Filtering (disabled by default)\n" \
" --timeslot NUM TDMA timeslot number [0..7]\n" \
" --frame-num-lt NUM TDMA frame number lower than NUM\n" \
" --frame-num-gt NUM TDMA frame number greater than NUM\n"
print(s % (self.remote_addr, self.bind_addr, self.base_port))
if msg is not None:
print(msg)
def parse_argv(self):
try:
opts, args = getopt.getopt(sys.argv[1:],
"m:r:b:p:i:h",
[
"help",
"conn-mode=",
"remote-addr=",
"bind-addr=",
"base-port=",
"capture-file=",
"msg-skip=",
"msg-count=",
"timeslot=",
"frame-num-lt=",
"frame-num-gt=",
])
except getopt.GetoptError as err:
self.print_help("[!] " + str(err))
sys.exit(2)
for o, v in opts:
if o in ("-h", "--help"):
self.print_help()
sys.exit(2)
# Capture file
elif o in ("-i", "--capture-file"):
self.capture_file = v
# TRX interface specific
elif o in ("-m", "--conn-mode"):
self.conn_mode = v
elif o in ("-r", "--remote-addr"):
self.remote_addr = v
elif o in ("-b", "--bind-addr"):
self.bind_addr = v
elif o in ("-p", "--base-port"):
self.base_port = int(v)
# Count limitations
elif o == "--msg-skip":
self.msg_skip = int(v)
elif o == "--msg-count":
self.msg_count = int(v)
# Timeslot pass filter
elif o == "--timeslot":
self.pf_tn = int(v)
if self.pf_tn < 0 or self.pf_tn > 7:
self.print_help("[!] Wrong timeslot value")
sys.exit(2)
# Frame number pass filter
elif o == "--frame-num-lt":
self.pf_fn_lt = int(v)
elif o == "--frame-num-gt":
self.pf_fn_gt = int(v)
if self.capture_file is None:
self.print_help("[!] Please specify a capture file")
sys.exit(2)
def sig_handler(self, signum, frame):
print("Signal %d received" % signum)
if signum is signal.SIGINT:
sys.exit(0)
if __name__ == '__main__':
app = Application()
app.run()

View File

@ -0,0 +1,116 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# TRX Toolkit
# Simple TDMA frame clock generator
#
# (C) 2017-2018 by Vadim Yanitskiy <axilirator@gmail.com>
#
# All Rights Reserved
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from copyright import print_copyright
CR_HOLDERS = [("2017-2018", "Vadim Yanitskiy <axilirator@gmail.com>")]
import signal
import time
import sys
from threading import Timer
from udp_link import UDPLink
from gsm_shared import *
class CLCKGen:
# GSM TDMA definitions
SEC_DELAY_US = 1000 * 1000
GSM_FRAME_US = 4615.0
# Average loop back delay
LO_DELAY_US = 90.0
# State variables
timer = None
def __init__(self, clck_links, clck_start = 0, ind_period = 102):
self.clck_links = clck_links
self.ind_period = ind_period
self.clck_start = clck_start
self.clck_src = clck_start
# Calculate counter time
self.ctr_interval = self.GSM_FRAME_US - self.LO_DELAY_US
self.ctr_interval /= self.SEC_DELAY_US
self.ctr_interval *= self.ind_period
def start(self):
# Send the first indication
self.send_clck_ind()
def stop(self):
# Stop pending timer
if self.timer is not None:
self.timer.cancel()
self.timer = None
# Reset the clock source
self.clck_src = self.clck_start
def send_clck_ind(self):
# Keep clock cycle
if self.clck_src % GSM_HYPERFRAME >= 0:
self.clck_src %= GSM_HYPERFRAME
# We don't need to send so often
if self.clck_src % self.ind_period == 0:
# Create UDP payload
payload = "IND CLOCK %u\0" % self.clck_src
# Send indication to all UDP links
for link in self.clck_links:
link.send(payload)
# Debug print
print("[T] %s" % payload)
# Increase frame count
self.clck_src += self.ind_period
# Schedule a new indication
self.timer = Timer(self.ctr_interval, self.send_clck_ind)
self.timer.start()
# Just a wrapper for independent usage
class Application:
def __init__(self):
# Print copyright
print_copyright(CR_HOLDERS)
# Set up signal handlers
signal.signal(signal.SIGINT, self.sig_handler)
def run(self):
self.link = UDPLink("127.0.0.1", 5800, "0.0.0.0", 5700)
self.clck = CLCKGen([self.link], ind_period = 51)
self.clck.start()
def sig_handler(self, signum, frame):
print("Signal %d received" % signum)
if signum is signal.SIGINT:
self.clck.stop()
if __name__ == '__main__':
app = Application()
app.run()

View File

@ -0,0 +1,13 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
def print_copyright(holders = []):
# Print copyright holders if any
for date, author in holders:
print("Copyright (C) %s by %s" % (date, author))
# Print the license header itself
print("License GPLv2+: GNU GPL version 2 or later " \
"<http://gnu.org/licenses/gpl.html>\n" \
"This is free software: you are free to change and redistribute it.\n" \
"There is NO WARRANTY, to the extent permitted by law.\n")

View File

@ -0,0 +1,147 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# TRX Toolkit
# Auxiliary tool to send custom commands via TRX CTRL interface,
# which may be useful for testing and fuzzing
#
# (C) 2017-2018 by Vadim Yanitskiy <axilirator@gmail.com>
#
# All Rights Reserved
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from copyright import print_copyright
CR_HOLDERS = [("2017-2018", "Vadim Yanitskiy <axilirator@gmail.com>")]
import signal
import getopt
import select
import sys
from udp_link import UDPLink
class Application:
# Application variables
remote_addr = "127.0.0.1"
bind_addr = "0.0.0.0"
base_port = 5700
bind_port = 0
fuzzing = False
def __init__(self):
print_copyright(CR_HOLDERS)
self.parse_argv()
# Set up signal handlers
signal.signal(signal.SIGINT, self.sig_handler)
# Init UDP connection
self.ctrl_link = UDPLink(self.remote_addr, self.base_port + 1,
self.bind_addr, self.bind_port)
# Debug print
print("[i] Init CTRL interface (%s)" \
% self.ctrl_link.desc_link())
def print_help(self, msg = None):
s = " Usage: " + sys.argv[0] + " [options]\n\n" \
" Some help...\n" \
" -h --help this text\n\n"
s += " TRX interface specific\n" \
" -r --remote-addr Set remote address (default %s)\n" \
" -p --base-port Set base port number (default %d)\n" \
" -P --bind-port Set local port number (default: random)\n" \
" -b --bind-addr Set local address (default %s)\n" \
" -f --fuzzing Send raw payloads (without CMD)\n" \
print(s % (self.remote_addr, self.base_port, self.bind_addr))
if msg is not None:
print(msg)
def parse_argv(self):
try:
opts, args = getopt.getopt(sys.argv[1:],
"r:p:P:b:fh",
[
"help",
"fuzzing",
"base-port=",
"bind-port=",
"bind-addr=",
"remote-addr=",
])
except getopt.GetoptError as err:
self.print_help("[!] " + str(err))
sys.exit(2)
for o, v in opts:
if o in ("-h", "--help"):
self.print_help()
sys.exit(2)
elif o in ("-r", "--remote-addr"):
self.remote_addr = v
elif o in ("-b", "--bind-addr"):
self.bind_addr = v
elif o in ("-p", "--base-port"):
self.base_port = int(v)
elif o in ("-P", "--bind-port"):
self.bind_port = int(v)
elif o in ("-f", "--fuzzing"):
self.fuzzing = True
def run(self):
while True:
self.print_prompt()
# Wait until we get any data on any socket
socks = [sys.stdin, self.ctrl_link.sock]
r_event, w_event, x_event = select.select(socks, [], [])
# Check for incoming CTRL commands
if sys.stdin in r_event:
cmd = sys.stdin.readline()
self.handle_cmd(cmd)
if self.ctrl_link.sock in r_event:
data, addr = self.ctrl_link.sock.recvfrom(128)
sys.stdout.write("\r%s\n" % data.decode())
sys.stdout.flush()
def handle_cmd(self, cmd):
# Strip spaces, tabs, etc.
cmd = cmd.strip().strip("\0")
# Send a command
if self.fuzzing:
self.ctrl_link.send("%s" % cmd)
else:
self.ctrl_link.send("CMD %s\0" % cmd)
def print_prompt(self):
sys.stdout.write("CTRL# ")
sys.stdout.flush()
def sig_handler(self, signum, frame):
print("\n\nSignal %d received" % signum)
if signum is signal.SIGINT:
sys.exit(0)
if __name__ == '__main__':
app = Application()
app.run()

View File

@ -0,0 +1,79 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# TRX Toolkit
# CTRL interface implementation
#
# (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
#
# All Rights Reserved
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from udp_link import UDPLink
class CTRLInterface(UDPLink):
def handle_rx(self, data, remote):
if not self.verify_req(data):
print("[!] Wrong data on CTRL interface")
return
# Attempt to parse a command
request = self.prepare_req(data)
rc = self.parse_cmd(request)
if type(rc) is tuple:
self.send_response(request, remote, rc[0], rc[1])
else:
self.send_response(request, remote, rc)
def verify_req(self, data):
# Verify command signature
return data.startswith("CMD")
def prepare_req(self, data):
# Strip signature, paddings and \0
request = data[4:].strip().strip("\0")
# Split into a command and arguments
request = request.split(" ")
# Now we have something like ["TXTUNE", "941600"]
return request
def verify_cmd(self, request, cmd, argc):
# Check if requested command matches
if request[0] != cmd:
return False
# And has enough arguments
if len(request) - 1 != argc:
return False
return True
def send_response(self, request, remote, response_code, params = None):
# Include status code, for example ["TXTUNE", "0", "941600"]
request.insert(1, str(response_code))
# Optionally append command specific parameters
if params is not None:
request += params
# Add the response signature, and join back to string
response = "RSP " + " ".join(request) + "\0"
# Now we have something like "RSP TXTUNE 0 941600"
self.sendto(response, remote)
def parse_cmd(self, request):
raise NotImplementedError

View File

@ -0,0 +1,158 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# TRX Toolkit
# CTRL interface implementation (OsmocomBB specific)
#
# (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
#
# All Rights Reserved
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from ctrl_if import CTRLInterface
class CTRLInterfaceBB(CTRLInterface):
# Internal state variables
trx_started = False
burst_fwd = None
rx_freq = None
tx_freq = None
pm = None
def __init__(self, remote_addr, remote_port, bind_addr, bind_port):
CTRLInterface.__init__(self, remote_addr, remote_port, bind_addr, bind_port)
print("[i] Init CTRL interface for BB (%s)" % self.desc_link())
def parse_cmd(self, request):
# Power control
if self.verify_cmd(request, "POWERON", 0):
print("[i] Recv POWERON CMD")
# Ensure transceiver isn't working
if self.trx_started:
print("[!] Transceiver already started")
return -1
# Ensure RX / TX freq. are set
if (self.rx_freq is None) or (self.tx_freq is None):
print("[!] RX / TX freq. are not set")
return -1
print("[i] Starting transceiver...")
self.trx_started = True
return 0
elif self.verify_cmd(request, "POWEROFF", 0):
print("[i] Recv POWEROFF cmd")
print("[i] Stopping transceiver...")
self.trx_started = False
return 0
# Tuning Control
elif self.verify_cmd(request, "RXTUNE", 1):
print("[i] Recv RXTUNE cmd")
# TODO: check freq range
self.rx_freq = int(request[1]) * 1000
self.burst_fwd.bb_freq = self.rx_freq
return 0
elif self.verify_cmd(request, "TXTUNE", 1):
print("[i] Recv TXTUNE cmd")
# TODO: check freq range
self.tx_freq = int(request[1]) * 1000
return 0
# Power measurement
elif self.verify_cmd(request, "MEASURE", 1):
print("[i] Recv MEASURE cmd")
if self.pm is None:
return -1
# TODO: check freq range
meas_freq = int(request[1]) * 1000
meas_dbm = str(self.pm.measure(meas_freq))
return (0, [meas_dbm])
elif self.verify_cmd(request, "SETSLOT", 2):
print("[i] Recv SETSLOT cmd")
if self.burst_fwd is None:
return -1
# Obtain TS index
ts = int(request[1])
if ts not in range(0, 8):
print("[!] TS index should be in range: 0..7")
return -1
# Parse TS type
ts_type = int(request[2])
# TS activation / deactivation
# We don't care about ts_type
if ts_type == 0:
self.burst_fwd.ts_pass = None
else:
self.burst_fwd.ts_pass = ts
return 0
# Timing Advance
elif self.verify_cmd(request, "SETTA", 1):
print("[i] Recv SETTA cmd")
# Parse and check TA value
ta = int(request[1])
if ta < 0 or ta > 63:
print("[!] TA value should be in range: 0..63")
return -1
# Save to the BurstForwarder instance
self.burst_fwd.ta = ta
return 0
# Timing of Arrival simulation for Uplink
# Absolute form: CMD FAKE_TOA <BASE> <THRESH>
elif self.verify_cmd(request, "FAKE_TOA", 2):
print("[i] Recv FAKE_TOA cmd")
# Parse and apply both base and threshold
self.burst_fwd.toa256_ul_base = int(request[1])
self.burst_fwd.toa256_ul_threshold = int(request[2])
return 0
# Timing of Arrival simulation for Uplink
# Relative form: CMD FAKE_TOA <+-BASE_DELTA>
elif self.verify_cmd(request, "FAKE_TOA", 1):
print("[i] Recv FAKE_TOA cmd")
# Parse and apply delta
self.burst_fwd.toa256_ul_base += int(request[1])
return 0
# Wrong / unknown command
else:
# We don't care about other commands,
# so let's merely ignore them ;)
print("[i] Ignore CMD %s" % request[0])
return 0

View File

@ -0,0 +1,126 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# TRX Toolkit
# CTRL interface implementation (OsmoBTS specific)
#
# (C) 2016-2017 by Vadim Yanitskiy <axilirator@gmail.com>
#
# All Rights Reserved
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from ctrl_if import CTRLInterface
class CTRLInterfaceBTS(CTRLInterface):
# Internal state variables
trx_started = False
burst_fwd = None
clck_gen = None
rx_freq = None
tx_freq = None
pm = None
def __init__(self, remote_addr, remote_port, bind_addr, bind_port):
CTRLInterface.__init__(self, remote_addr, remote_port, bind_addr, bind_port)
print("[i] Init CTRL interface for BTS (%s)" % self.desc_link())
def parse_cmd(self, request):
# Power control
if self.verify_cmd(request, "POWERON", 0):
print("[i] Recv POWERON CMD")
# Ensure transceiver isn't working
if self.trx_started:
print("[!] Transceiver already started")
return -1
# Ensure RX / TX freq. are set
if (self.rx_freq is None) or (self.tx_freq is None):
print("[!] RX / TX freq. are not set")
return -1
print("[i] Starting transceiver...")
self.trx_started = True
# Power emulation
if self.pm is not None:
self.pm.add_bts_list([self.tx_freq])
# Start clock indications
if self.clck_gen is not None:
self.clck_gen.start()
return 0
elif self.verify_cmd(request, "POWEROFF", 0):
print("[i] Recv POWEROFF cmd")
print("[i] Stopping transceiver...")
self.trx_started = False
# Power emulation
if self.pm is not None:
self.pm.del_bts_list([self.tx_freq])
# Stop clock indications
if self.clck_gen is not None:
self.clck_gen.stop()
return 0
# Tuning Control
elif self.verify_cmd(request, "RXTUNE", 1):
print("[i] Recv RXTUNE cmd")
# TODO: check freq range
self.rx_freq = int(request[1]) * 1000
return 0
elif self.verify_cmd(request, "TXTUNE", 1):
print("[i] Recv TXTUNE cmd")
# TODO: check freq range
self.tx_freq = int(request[1]) * 1000
self.burst_fwd.bts_freq = self.tx_freq
return 0
# Timing of Arrival simulation for Downlink
# Absolute form: CMD FAKE_TOA <BASE> <THRESH>
elif self.verify_cmd(request, "FAKE_TOA", 2):
print("[i] Recv FAKE_TOA cmd")
# Parse and apply both base and threshold
self.burst_fwd.toa256_dl_base = int(request[1])
self.burst_fwd.toa256_dl_threshold = int(request[2])
return 0
# Timing of Arrival simulation for Downlink
# Relative form: CMD FAKE_TOA <+-BASE_DELTA>
elif self.verify_cmd(request, "FAKE_TOA", 1):
print("[i] Recv FAKE_TOA cmd")
# Parse and apply delta
self.burst_fwd.toa256_dl_base += int(request[1])
return 0
# Wrong / unknown command
else:
# We don't care about other commands,
# so let's merely ignore them ;)
print("[i] Ignore CMD %s" % request[0])
return 0

View File

@ -0,0 +1,360 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# TRX Toolkit
# Helpers for DATA capture management
#
# (C) 2018 by Vadim Yanitskiy <axilirator@gmail.com>
#
# All Rights Reserved
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import struct
from data_msg import *
class DATADump:
# Constants
TAG_L12TRX = b'\x01'
TAG_TRX2L1 = b'\x02'
HDR_LENGTH = 3
# Generates raw bytes from a DATA message
# Return value: raw message bytes
def dump_msg(self, msg):
# Determine a message type
if isinstance(msg, DATAMSG_L12TRX):
tag = self.TAG_L12TRX
elif isinstance(msg, DATAMSG_TRX2L1):
tag = self.TAG_TRX2L1
else:
raise ValueError("Unknown message type")
# Generate a message payload
msg_raw = msg.gen_msg()
# Calculate and pack the message length
msg_len = len(msg_raw)
# Pack to unsigned short (2 bytes, BE)
msg_len = struct.pack(">H", msg_len)
# Concatenate a message with header
return bytearray(tag + msg_len) + msg_raw
def parse_hdr(self, hdr):
# Extract the header info
msg_len = struct.unpack(">H", hdr[1:3])[0]
tag = hdr[:1]
# Check if tag is known
if tag == self.TAG_L12TRX:
# L1 -> TRX
msg = DATAMSG_L12TRX()
elif tag == self.TAG_TRX2L1:
# TRX -> L1
msg = DATAMSG_TRX2L1()
else:
# Unknown tag
return False
return (msg, msg_len)
class DATADumpFile(DATADump):
def __init__(self, capture):
# Check if capture file is already opened
if isinstance(capture, str):
print("[i] Opening capture file '%s'..." % capture)
self.f = open(capture, "a+b")
else:
self.f = capture
def __del__(self):
print("[i] Closing the capture file")
self.f.close()
# Moves the file descriptor before a specified message
# Return value:
# True in case of success,
# or False in case of EOF or header parsing error.
def _seek2msg(self, idx):
# Seek to the begining of the capture
self.f.seek(0)
# Read the capture in loop...
for i in range(idx):
# Attempt to read a message header
hdr_raw = self.f.read(self.HDR_LENGTH)
if len(hdr_raw) != self.HDR_LENGTH:
return False
# Attempt to parse it
rc = self.parse_hdr(hdr_raw)
if rc is False:
print("[!] Couldn't parse a message header")
return False
# Expand the header
(_, msg_len) = rc
# Skip a message
self.f.seek(msg_len, 1)
return True
# Parses a single message at the current descriptor position
# Return value:
# a parsed message in case of success,
# or None in case of EOF or header parsing error,
# or False in case of message parsing error.
def _parse_msg(self):
# Attempt to read a message header
hdr_raw = self.f.read(self.HDR_LENGTH)
if len(hdr_raw) != self.HDR_LENGTH:
return None
# Attempt to parse it
rc = self.parse_hdr(hdr_raw)
if rc is False:
print("[!] Couldn't parse a message header")
return None
# Expand the header
(msg, msg_len) = rc
# Attempt to read a message
msg_raw = self.f.read(msg_len)
if len(msg_raw) != msg_len:
print("[!] Message length mismatch")
return None
# Attempt to parse a message
try:
msg_raw = bytearray(msg_raw)
msg.parse_msg(msg_raw)
except:
print("[!] Couldn't parse a message, skipping...")
return False
# Success
return msg
# Parses a particular message defined by index idx
# Return value:
# a parsed message in case of success,
# or None in case of EOF or header parsing error,
# or False in case of message parsing error or out of range.
def parse_msg(self, idx):
# Move descriptor to the begining of requested message
rc = self._seek2msg(idx)
if not rc:
print("[!] Couldn't find requested message")
return False
# Attempt to parse a message
return self._parse_msg()
# Parses all messages from a given file
# Return value:
# list of parsed messages,
# or False in case of range error.
def parse_all(self, skip = None, count = None):
result = []
# Should we skip some messages?
if skip is None:
# Seek to the begining of the capture
self.f.seek(0)
else:
rc = self._seek2msg(skip)
if not rc:
print("[!] Couldn't find requested message")
return False
# Read the capture in loop...
while True:
# Attempt to parse a message
msg = self._parse_msg()
# EOF or broken header
if msg is None:
break
# Skip unparsed messages
if msg is False:
continue
# Success, append a message
result.append(msg)
# Count limitation
if count is not None:
if len(result) == count:
break
return result
# Writes a new message at the end of the capture
def append_msg(self, msg):
# Generate raw bytes and write
msg_raw = self.dump_msg(msg)
self.f.write(msg_raw)
# Writes a list of messages at the end of the capture
def append_all(self, msgs):
for msg in msgs:
self.append_msg(msg)
# Regression tests
if __name__ == '__main__':
from tempfile import TemporaryFile
from gsm_shared import *
import random
# Create a temporary file
tf = TemporaryFile()
# Create an instance of DATA dump manager
ddf = DATADumpFile(tf)
# Generate two random bursts
burst_l12trx = []
burst_trx2l1 = []
for i in range(0, GSM_BURST_LEN):
ubit = random.randint(0, 1)
burst_l12trx.append(ubit)
sbit = random.randint(-127, 127)
burst_trx2l1.append(sbit)
# Generate a basic list of random messages
print("[i] Generating the reference messages")
messages_ref = []
for i in range(100):
# Create a message
if i % 2:
msg = DATAMSG_L12TRX()
msg.burst = burst_l12trx
else:
msg = DATAMSG_TRX2L1()
msg.burst = burst_trx2l1
# Randomize the header
msg.rand_hdr()
# Append
messages_ref.append(msg)
print("[i] Adding the following messages to the capture:")
for msg in messages_ref[:3]:
print(" %s: burst_len=%d"
% (msg.desc_hdr(), len(msg.burst)))
# Check single message appending
ddf.append_msg(messages_ref[0])
ddf.append_msg(messages_ref[1])
ddf.append_msg(messages_ref[2])
# Read the written messages back
messages_check = ddf.parse_all()
print("[i] Read the following messages back:")
for msg in messages_check:
print(" %s: burst_len=%d"
% (msg.desc_hdr(), len(msg.burst)))
# Expecting three messages
assert(len(messages_check) == 3)
# Check the messages
for i in range(3):
# Compare common header parts and bursts
assert(messages_check[i].burst == messages_ref[i].burst)
assert(messages_check[i].fn == messages_ref[i].fn)
assert(messages_check[i].tn == messages_ref[i].tn)
# Validate a message
assert(messages_check[i].validate())
print("[?] Check append_msg(): OK")
# Append the pending reference messages
ddf.append_all(messages_ref[3:])
# Read the written messages back
messages_check = ddf.parse_all()
# Check the final amount
assert(len(messages_check) == len(messages_ref))
# Check the messages
for i in range(len(messages_check)):
# Compare common header parts and bursts
assert(messages_check[i].burst == messages_ref[i].burst)
assert(messages_check[i].fn == messages_ref[i].fn)
assert(messages_check[i].tn == messages_ref[i].tn)
# Validate a message
assert(messages_check[i].validate())
print("[?] Check append_all(): OK")
# Check parse_msg()
msg0 = ddf.parse_msg(0)
msg10 = ddf.parse_msg(10)
# Make sure parsing was successful
assert(msg0 and msg10)
# Compare common header parts and bursts
assert(msg0.burst == messages_ref[0].burst)
assert(msg0.fn == messages_ref[0].fn)
assert(msg0.tn == messages_ref[0].tn)
assert(msg10.burst == messages_ref[10].burst)
assert(msg10.fn == messages_ref[10].fn)
assert(msg10.tn == messages_ref[10].tn)
# Validate both messages
assert(msg0.validate())
assert(msg10.validate())
print("[?] Check parse_msg(): OK")
# Check parse_all() with range
messages_check = ddf.parse_all(skip = 10, count = 20)
# Make sure parsing was successful
assert(messages_check)
# Check the amount
assert(len(messages_check) == 20)
for i in range(20):
# Compare common header parts and bursts
assert(messages_check[i].burst == messages_ref[i + 10].burst)
assert(messages_check[i].fn == messages_ref[i + 10].fn)
assert(messages_check[i].tn == messages_ref[i + 10].tn)
# Validate a message
assert(messages_check[i].validate())
print("[?] Check parse_all(): OK")

View File

@ -0,0 +1,39 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# TRX Toolkit
# DATA interface implementation
#
# (C) 2017-2018 by Vadim Yanitskiy <axilirator@gmail.com>
#
# All Rights Reserved
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from udp_link import UDPLink
from data_msg import *
class DATAInterface(UDPLink):
def send_msg(self, msg):
# Validate a message
if not msg.validate():
raise ValueError("Message incomplete or incorrect")
# Generate TRX message
payload = msg.gen_msg()
# Send message
self.send(payload)

View File

@ -0,0 +1,545 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# TRX Toolkit
# DATA interface message definitions and helpers
#
# (C) 2018 by Vadim Yanitskiy <axilirator@gmail.com>
#
# All Rights Reserved
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import random
import struct
from gsm_shared import *
class DATAMSG:
# Common message fields
burst = None
fn = None
tn = None
# Common constructor
def __init__(self, fn = None, tn = None, burst = None):
self.burst = burst
self.fn = fn
self.tn = tn
# Generates message specific header
def gen_hdr(self):
raise NotImplementedError
# Parses message specific header
def parse_hdr(self, hdr):
raise NotImplementedError
# Generates message specific burst
def gen_burst(self):
raise NotImplementedError
# Parses message specific burst
def parse_burst(self, burst):
raise NotImplementedError
# Generates a random frame number
def rand_fn(self):
return random.randint(0, GSM_HYPERFRAME)
# Generates a random timeslot number
def rand_tn(self):
return random.randint(0, 7)
# Randomizes the message header
def rand_hdr(self):
self.fn = self.rand_fn()
self.tn = self.rand_tn()
# Generates human-readable header description
def desc_hdr(self):
result = ""
if self.fn is not None:
result += ("fn=%u " % self.fn)
if self.tn is not None:
result += ("tn=%u " % self.tn)
return result
# Converts unsigned soft-bits {254..0} to soft-bits {-127..127}
def usbit2sbit(self, bits):
buf = []
for bit in bits:
if bit == 0xff:
buf.append(-127)
else:
buf.append(127 - bit)
return buf
# Converts soft-bits {-127..127} to unsigned soft-bits {254..0}
def sbit2usbit(self, bits):
buf = []
for bit in bits:
buf.append(127 - bit)
return buf
# Converts soft-bits {-127..127} to bits {1..0}
def sbit2ubit(self, bits):
buf = []
for bit in bits:
buf.append(1 if bit < 0 else 0)
return buf
# Converts bits {1..0} to soft-bits {-127..127}
def ubit2sbit(self, bits):
buf = []
for bit in bits:
buf.append(-127 if bit else 127)
return buf
# Validates the message fields
def validate(self):
if self.burst is None:
return False
if len(self.burst) not in (GSM_BURST_LEN, EDGE_BURST_LEN):
return False
if self.fn is None:
return False
if self.fn < 0 or self.fn > GSM_HYPERFRAME:
return False
if self.tn is None:
return False
if self.tn < 0 or self.tn > 7:
return False
return True
# Generates frame number to bytes
def gen_fn(self, fn):
# Allocate an empty byte-array
buf = bytearray()
# Big endian, 4 bytes
buf.append((fn >> 24) & 0xff)
buf.append((fn >> 16) & 0xff)
buf.append((fn >> 8) & 0xff)
buf.append((fn >> 0) & 0xff)
return buf
# Parses frame number from bytes
def parse_fn(self, buf):
# Big endian, 4 bytes
return (buf[0] << 24) \
| (buf[1] << 16) \
| (buf[2] << 8) \
| (buf[3] << 0)
# Generates a TRX DATA message
def gen_msg(self):
# Validate all the fields
if not self.validate():
raise ValueError("Message incomplete or incorrect")
# Allocate an empty byte-array
buf = bytearray()
# Put timeslot index
buf.append(self.tn)
# Put frame number
fn = self.gen_fn(self.fn)
buf += fn
# Generate message specific header part
hdr = self.gen_hdr()
buf += hdr
# Generate burst
buf += self.gen_burst()
return buf
# Parses a TRX DATA message
def parse_msg(self, msg):
# Calculate message length
length = len(msg)
# Check length
if length < (self.HDR_LEN + GSM_BURST_LEN):
raise ValueError("Message is to short")
# Parse both fn and tn
self.fn = self.parse_fn(msg[1:])
self.tn = msg[0]
# Specific message part
self.parse_hdr(msg)
# Copy burst, skipping header
msg_burst = msg[self.HDR_LEN:]
self.parse_burst(msg_burst)
class DATAMSG_L12TRX(DATAMSG):
# Constants
HDR_LEN = 6
PWR_MIN = 0x00
PWR_MAX = 0xff
# Specific message fields
pwr = None
# Validates the message fields
def validate(self):
# Validate common fields
if not DATAMSG.validate(self):
return False
if self.pwr is None:
return False
if self.pwr < self.PWR_MIN or self.pwr > self.PWR_MAX:
return False
return True
# Generates a random power level
def rand_pwr(self, min = None, max = None):
if min is None:
min = self.PWR_MIN
if max is None:
max = self.PWR_MAX
return random.randint(min, max)
# Randomizes message specific header
def rand_hdr(self):
DATAMSG.rand_hdr(self)
self.pwr = self.rand_pwr()
# Generates human-readable header description
def desc_hdr(self):
# Describe the common part
result = DATAMSG.desc_hdr(self)
if self.pwr is not None:
result += ("pwr=%u " % self.pwr)
# Strip useless whitespace and return
return result.strip()
# Generates message specific header part
def gen_hdr(self):
# Allocate an empty byte-array
buf = bytearray()
# Put power
buf.append(self.pwr)
return buf
# Parses message specific header part
def parse_hdr(self, hdr):
# Parse power level
self.pwr = hdr[5]
# Generates message specific burst
def gen_burst(self):
# Copy burst 'as is'
return bytearray(self.burst)
# Parses message specific burst
def parse_burst(self, burst):
length = len(burst)
# Distinguish between GSM and EDGE
if length >= EDGE_BURST_LEN:
self.burst = list(burst[:EDGE_BURST_LEN])
else:
self.burst = list(burst[:GSM_BURST_LEN])
# Transforms this message to TRX2L1 message
def gen_trx2l1(self):
# Allocate a new message
msg = DATAMSG_TRX2L1(fn = self.fn, tn = self.tn)
# Convert burst bits
if self.burst is not None:
msg.burst = self.ubit2sbit(self.burst)
return msg
class DATAMSG_TRX2L1(DATAMSG):
# Constants
HDR_LEN = 8
RSSI_MIN = -120
RSSI_MAX = -50
# TODO: verify this range
TOA256_MIN = -256 * 200
TOA256_MAX = 256 * 200
# Specific message fields
rssi = None
toa256 = None
# Validates the message fields
def validate(self):
# Validate common fields
if not DATAMSG.validate(self):
return False
if self.rssi is None:
return False
if self.rssi < self.RSSI_MIN or self.rssi > self.RSSI_MAX:
return False
if self.toa256 is None:
return False
if self.toa256 < self.TOA256_MIN or self.toa256 > self.TOA256_MAX:
return False
return True
# Generates a random RSSI value
def rand_rssi(self, min = None, max = None):
if min is None:
min = self.RSSI_MIN
if max is None:
max = self.RSSI_MAX
return random.randint(min, max)
# Generates a ToA (Time of Arrival) value
def rand_toa256(self, min = None, max = None):
if min is None:
min = self.TOA256_MIN
if max is None:
max = self.TOA256_MAX
return random.randint(min, max)
# Randomizes message specific header
def rand_hdr(self):
DATAMSG.rand_hdr(self)
self.rssi = self.rand_rssi()
self.toa256 = self.rand_toa256()
# Generates human-readable header description
def desc_hdr(self):
# Describe the common part
result = DATAMSG.desc_hdr(self)
if self.rssi is not None:
result += ("rssi=%d " % self.rssi)
if self.toa256 is not None:
result += ("toa256=%d " % self.toa256)
# Strip useless whitespace and return
return result.strip()
# Generates message specific header part
def gen_hdr(self):
# Allocate an empty byte-array
buf = bytearray()
# Put RSSI
buf.append(-self.rssi)
# Encode ToA (Time of Arrival)
# Big endian, 2 bytes (int32_t)
buf.append((self.toa256 >> 8) & 0xff)
buf.append(self.toa256 & 0xff)
return buf
# Parses message specific header part
def parse_hdr(self, hdr):
# Parse RSSI
self.rssi = -(hdr[5])
# Parse ToA (Time of Arrival)
self.toa256 = struct.unpack(">h", hdr[6:8])[0]
# Generates message specific burst
def gen_burst(self):
# Convert soft-bits to unsigned soft-bits
burst_usbits = self.sbit2usbit(self.burst)
# Encode to bytes
return bytearray(burst_usbits)
# Parses message specific burst
def parse_burst(self, burst):
length = len(burst)
# Distinguish between GSM and EDGE
if length >= EDGE_BURST_LEN:
burst_usbits = list(burst[:EDGE_BURST_LEN])
else:
burst_usbits = list(burst[:GSM_BURST_LEN])
# Convert unsigned soft-bits to soft-bits
burst_sbits = self.usbit2sbit(burst_usbits)
# Save
self.burst = burst_sbits
# Transforms this message to L12TRX message
def gen_l12trx(self):
# Allocate a new message
msg = DATAMSG_L12TRX(fn = self.fn, tn = self.tn)
# Convert burst bits
if self.burst is not None:
msg.burst = self.sbit2ubit(self.burst)
return msg
# Regression test
if __name__ == '__main__':
# Common reference data
fn = 1024
tn = 0
# Generate two random bursts
burst_l12trx_ref = []
burst_trx2l1_ref = []
for i in range(0, GSM_BURST_LEN):
ubit = random.randint(0, 1)
burst_l12trx_ref.append(ubit)
sbit = random.randint(-127, 127)
burst_trx2l1_ref.append(sbit)
print("[i] Generating the reference messages")
# Create messages of both types
msg_l12trx_ref = DATAMSG_L12TRX(fn = fn, tn = tn)
msg_trx2l1_ref = DATAMSG_TRX2L1(fn = fn, tn = tn)
# Fill in message specific fields
msg_trx2l1_ref.rssi = -88
msg_l12trx_ref.pwr = 0x33
msg_trx2l1_ref.toa256 = -256
# Specify the reference bursts
msg_l12trx_ref.burst = burst_l12trx_ref
msg_trx2l1_ref.burst = burst_trx2l1_ref
print("[i] Encoding the reference messages")
# Encode DATA messages
l12trx_raw = msg_l12trx_ref.gen_msg()
trx2l1_raw = msg_trx2l1_ref.gen_msg()
print("[i] Parsing generated messages back")
# Parse generated DATA messages
msg_l12trx_dec = DATAMSG_L12TRX()
msg_trx2l1_dec = DATAMSG_TRX2L1()
msg_l12trx_dec.parse_msg(l12trx_raw)
msg_trx2l1_dec.parse_msg(trx2l1_raw)
print("[i] Comparing decoded messages with the reference")
# Compare bursts
assert(msg_l12trx_dec.burst == burst_l12trx_ref)
assert(msg_trx2l1_dec.burst == burst_trx2l1_ref)
print("[?] Compare bursts: OK")
# Compare both parsed messages with the reference data
assert(msg_l12trx_dec.fn == fn)
assert(msg_trx2l1_dec.fn == fn)
assert(msg_l12trx_dec.tn == tn)
assert(msg_trx2l1_dec.tn == tn)
print("[?] Compare FN / TN: OK")
# Compare message specific parts
assert(msg_trx2l1_dec.rssi == msg_trx2l1_ref.rssi)
assert(msg_l12trx_dec.pwr == msg_l12trx_ref.pwr)
assert(msg_trx2l1_dec.toa256 == msg_trx2l1_ref.toa256)
print("[?] Compare message specific data: OK")
# Validate header randomization
for i in range(0, 100):
msg_l12trx_ref.rand_hdr()
msg_trx2l1_ref.rand_hdr()
assert(msg_l12trx_ref.validate())
assert(msg_trx2l1_ref.validate())
print("[?] Validate header randomization: OK")
# Bit conversation test
usbits_ref = list(range(0, 256))
sbits_ref = list(range(-127, 128))
# Test both usbit2sbit() and sbit2usbit()
sbits = msg_trx2l1_ref.usbit2sbit(usbits_ref)
usbits = msg_trx2l1_ref.sbit2usbit(sbits)
assert(usbits[:255] == usbits_ref[:255])
assert(usbits[255] == 254)
print("[?] Check both usbit2sbit() and sbit2usbit(): OK")
# Test both sbit2ubit() and ubit2sbit()
ubits = msg_trx2l1_ref.sbit2ubit(sbits_ref)
assert(ubits == ([1] * 127 + [0] * 128))
sbits = msg_trx2l1_ref.ubit2sbit(ubits)
assert(sbits == ([-127] * 127 + [127] * 128))
print("[?] Check both sbit2ubit() and ubit2sbit(): OK")
# Test message transformation
msg_l12trx_dec = msg_trx2l1_ref.gen_l12trx()
msg_trx2l1_dec = msg_l12trx_ref.gen_trx2l1()
assert(msg_l12trx_dec.fn == msg_trx2l1_ref.fn)
assert(msg_l12trx_dec.tn == msg_trx2l1_ref.tn)
assert(msg_trx2l1_dec.fn == msg_l12trx_ref.fn)
assert(msg_trx2l1_dec.tn == msg_l12trx_ref.tn)
assert(msg_l12trx_dec.burst == msg_l12trx_dec.sbit2ubit(burst_trx2l1_ref))
assert(msg_trx2l1_dec.burst == msg_trx2l1_dec.ubit2sbit(burst_l12trx_ref))
print("[?] Check L12TRX <-> TRX2L1 type transformations: OK")

View File

@ -0,0 +1,53 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# TRX Toolkit
# Power measurement emulation for BB
#
# (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com>
#
# All Rights Reserved
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from random import randint
class FakePM:
# Freq. list for good power level
bts_list = []
def __init__(self, noise_min, noise_max, bts_min, bts_max):
# Save power level ranges
self.noise_min = noise_min
self.noise_max = noise_max
self.bts_min = bts_min
self.bts_max = bts_max
def measure(self, bts):
if bts in self.bts_list:
return randint(self.bts_min, self.bts_max)
else:
return randint(self.noise_min, self.noise_max)
def update_bts_list(self, new_list):
self.bts_list = new_list
def add_bts_list(self, add_list):
self.bts_list += add_list
def del_bts_list(self, del_list):
for item in del_list:
if item in self.bts_list:
self.bts_list.remove(item)

View File

@ -0,0 +1,236 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# TRX Toolkit
# Virtual Um-interface (fake transceiver)
#
# (C) 2017-2018 by Vadim Yanitskiy <axilirator@gmail.com>
#
# All Rights Reserved
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from copyright import print_copyright
CR_HOLDERS = [("2017-2018", "Vadim Yanitskiy <axilirator@gmail.com>")]
import signal
import getopt
import select
import sys
from ctrl_if_bts import CTRLInterfaceBTS
from ctrl_if_bb import CTRLInterfaceBB
from burst_fwd import BurstForwarder
from fake_pm import FakePM
from udp_link import UDPLink
from clck_gen import CLCKGen
class Application:
# Application variables
bts_addr = "127.0.0.1"
bb_addr = "127.0.0.1"
trx_bind_addr = "0.0.0.0"
bts_base_port = 5700
bb_base_port = 6700
# BurstForwarder field randomization
randomize_dl_toa256 = False
randomize_ul_toa256 = False
randomize_dl_rssi = False
randomize_ul_rssi = False
def __init__(self):
print_copyright(CR_HOLDERS)
self.parse_argv()
# Set up signal handlers
signal.signal(signal.SIGINT, self.sig_handler)
def run(self):
# Init TRX CTRL interface for BTS
self.bts_ctrl = CTRLInterfaceBTS(self.bts_addr, self.bts_base_port + 101,
self.trx_bind_addr, self.bts_base_port + 1)
# Init TRX CTRL interface for BB
self.bb_ctrl = CTRLInterfaceBB(self.bb_addr, self.bb_base_port + 101,
self.trx_bind_addr, self.bb_base_port + 1)
# Power measurement emulation
# Noise: -120 .. -105
# BTS: -75 .. -50
self.pm = FakePM(-120, -105, -75, -50)
# Share a FakePM instance between both BTS and BB
self.bts_ctrl.pm = self.pm
self.bb_ctrl.pm = self.pm
# Init DATA links
self.bts_data = UDPLink(self.bts_addr, self.bts_base_port + 102,
self.trx_bind_addr, self.bts_base_port + 2)
self.bb_data = UDPLink(self.bb_addr, self.bb_base_port + 102,
self.trx_bind_addr, self.bb_base_port + 2)
# BTS <-> BB burst forwarding
self.burst_fwd = BurstForwarder(self.bts_data, self.bb_data)
self.burst_fwd.randomize_dl_toa256 = self.randomize_dl_toa256
self.burst_fwd.randomize_ul_toa256 = self.randomize_ul_toa256
self.burst_fwd.randomize_dl_rssi = self.randomize_dl_rssi
self.burst_fwd.randomize_ul_rssi = self.randomize_ul_rssi
# Share a BurstForwarder instance between BTS and BB
self.bts_ctrl.burst_fwd = self.burst_fwd
self.bb_ctrl.burst_fwd = self.burst_fwd
# Provide clock to BTS
self.bts_clck = UDPLink(self.bts_addr, self.bts_base_port + 100,
self.trx_bind_addr, self.bts_base_port)
self.clck_gen = CLCKGen([self.bts_clck])
self.bts_ctrl.clck_gen = self.clck_gen
print("[i] Init complete")
# Enter main loop
while True:
socks = [self.bts_ctrl.sock, self.bb_ctrl.sock,
self.bts_data.sock, self.bb_data.sock]
# Wait until we get any data on any socket
r_event, w_event, x_event = select.select(socks, [], [])
# Downlink: BTS -> BB
if self.bts_data.sock in r_event:
self.burst_fwd.bts2bb()
# Uplink: BB -> BTS
if self.bb_data.sock in r_event:
self.burst_fwd.bb2bts()
# CTRL commands from BTS
if self.bts_ctrl.sock in r_event:
data, addr = self.bts_ctrl.sock.recvfrom(128)
self.bts_ctrl.handle_rx(data.decode(), addr)
# CTRL commands from BB
if self.bb_ctrl.sock in r_event:
data, addr = self.bb_ctrl.sock.recvfrom(128)
self.bb_ctrl.handle_rx(data.decode(), addr)
def shutdown(self):
print("[i] Shutting down...")
# Stop clock generator
self.clck_gen.stop()
def print_help(self, msg = None):
s = " Usage: " + sys.argv[0] + " [options]\n\n" \
" Some help...\n" \
" -h --help this text\n\n"
s += " TRX interface specific\n" \
" -R --bts-addr Set BTS remote address (default %s)\n" \
" -r --bb-addr Set BB remote address (default %s)\n" \
" -P --bts-base-port Set BTS base port number (default %d)\n" \
" -p --bb-base-port Set BB base port number (default %d)\n" \
" -b --trx-bind-addr Set TRX bind address (default %s)\n\n"
s += " Simulation\n" \
" --rand-dl-rssi Enable DL RSSI randomization\n" \
" --rand-ul-rssi Enable UL RSSI randomization\n" \
" --rand-dl-toa Enable DL ToA randomization\n" \
" --rand-ul-toa Enable UL ToA randomization\n"
print(s % (self.bts_addr, self.bb_addr,
self.bts_base_port, self.bb_base_port,
self.trx_bind_addr))
if msg is not None:
print(msg)
def parse_argv(self):
try:
opts, args = getopt.getopt(sys.argv[1:],
"R:r:P:p:b:h",
[
"help",
"bts-addr=", "bb-addr=",
"bts-base-port=", "bb-base-port=",
"trx-bind-addr=",
"rand-dl-rssi", "rand-ul-rssi",
"rand-dl-toa", "rand-ul-toa",
])
except getopt.GetoptError as err:
self.print_help("[!] " + str(err))
sys.exit(2)
for o, v in opts:
if o in ("-h", "--help"):
self.print_help()
sys.exit(2)
elif o in ("-R", "--bts-addr"):
self.bts_addr = v
elif o in ("-r", "--bb-addr"):
self.bb_addr = v
elif o in ("-P", "--bts-base-port"):
self.bts_base_port = int(v)
elif o in ("-p", "--bb-base-port"):
self.bb_base_port = int(v)
elif o in ("-b", "--trx-bind-addr"):
self.trx_bind_addr = v
# Message field randomization
elif o == "rand-dl-rssi":
self.randomize_dl_rssi = True
elif o == "rand-ul-rssi":
self.randomize_ul_rssi = True
elif o == "rand-dl-toa":
self.randomize_dl_toa256 = True
elif o == "rand-ul-toa":
self.randomize_ul_toa256 = True
# Ensure there is no overlap between ports
if self.bts_base_port == self.bb_base_port:
self.print_help("[!] BTS and BB base ports should be different")
sys.exit(2)
bts_ports = [
self.bts_base_port + 0, self.bts_base_port + 100,
self.bts_base_port + 1, self.bts_base_port + 101,
self.bts_base_port + 2, self.bts_base_port + 102,
]
bb_ports = [
self.bb_base_port + 0, self.bb_base_port + 100,
self.bb_base_port + 1, self.bb_base_port + 101,
self.bb_base_port + 2, self.bb_base_port + 102,
]
for p in bb_ports:
if p in bts_ports:
self.print_help("[!] BTS and BB ports overlap detected")
sys.exit(2)
def sig_handler(self, signum, frame):
print("Signal %d received" % signum)
if signum is signal.SIGINT:
self.shutdown()
sys.exit(0)
if __name__ == '__main__':
app = Application()
app.run()

View File

@ -0,0 +1,31 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# TRX Toolkit
# Common GSM constants
#
# (C) 2018 by Vadim Yanitskiy <axilirator@gmail.com>
#
# All Rights Reserved
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
# TDMA definitions
GSM_SUPERFRAME = 26 * 51
GSM_HYPERFRAME = 2048 * GSM_SUPERFRAME
# Burst length
GSM_BURST_LEN = 148
EDGE_BURST_LEN = GSM_BURST_LEN * 3

View File

@ -0,0 +1,177 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# TRX Toolkit
# Random burst (NB, FB, SB, AB) generator
#
# (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com>
#
# All Rights Reserved
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import random
from gsm_shared import *
class RandBurstGen:
# GSM 05.02 Chapter 5.2.3 Normal Burst
nb_tsc_list = [
[
0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0,
0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1,
],
[
0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1,
1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1,
],
[
0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1,
0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0,
],
[
0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0,
1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0,
],
[
0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0,
1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1,
],
[
0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0,
0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0,
],
[
1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1,
0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1,
],
[
1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0,
0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0,
],
]
# GSM 05.02 Chapter 5.2.5 SCH training sequence
sb_tsc = [
1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1,
0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1,
]
# GSM 05.02 Chapter 5.2.6 Dummy Burst
db_bits = [
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,
]
# GSM 05.02 Chapter 5.2.7 Access burst
ab_tsc = [
0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1,
1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0,
1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
]
# Generate a normal burst
def gen_nb(self, seq_idx = 0):
buf = []
# Tailing bits
buf += [0] * 3
# Random data 1 / 2
for i in range(0, 57):
buf.append(random.randint(0, 1))
# Steal flag 1 / 2
buf.append(random.randint(0, 1))
# Training sequence
buf += self.nb_tsc_list[seq_idx]
# Steal flag 2 / 2
buf.append(random.randint(0, 1))
# Random data 2 / 2
for i in range(0, 57):
buf.append(random.randint(0, 1))
# Tailing bits
buf += [0] * 3
return buf
# Generate a frequency correction burst
def gen_fb(self):
return [0] * GSM_BURST_LEN
# Generate a synchronization burst
def gen_sb(self):
buf = []
# Tailing bits
buf += [0] * 3
# Random data 1 / 2
for i in range(0, 39):
buf.append(random.randint(0, 1))
# Training sequence
buf += self.sb_tsc
# Random data 2 / 2
for i in range(0, 39):
buf.append(random.randint(0, 1))
# Tailing bits
buf += [0] * 3
return buf
# Generate a dummy burst
def gen_db(self):
return self.db_bits
# Generate an access burst
def gen_ab(self):
buf = []
# Tailing bits
buf += [0] * 8
# Training sequence
buf += self.ab_tsc
# Random data
for i in range(0, 36):
buf.append(random.randint(0, 1))
# Tailing bits
buf += [0] * 3
# Guard period
buf += [0] * 60
return buf

View File

@ -0,0 +1,286 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# TRX Toolkit
# Scapy-based TRX interface sniffer
#
# (C) 2018 by Vadim Yanitskiy <axilirator@gmail.com>
#
# All Rights Reserved
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from copyright import print_copyright
CR_HOLDERS = [("2018", "Vadim Yanitskiy <axilirator@gmail.com>")]
import signal
import getopt
import sys
import scapy.all
from data_dump import DATADumpFile
from data_msg import *
class Application:
# Application variables
sniff_interface = "lo"
sniff_base_port = 5700
print_bursts = False
output_file = None
# Counters
cnt_burst_dropped_num = 0
cnt_burst_break = None
cnt_burst_num = 0
cnt_frame_break = None
cnt_frame_last = None
cnt_frame_num = 0
# Burst direction fliter
bf_dir_l12trx = None
# Timeslot number filter
bf_tn_val = None
# Frame number fliter
bf_fn_lt = None
bf_fn_gt = None
# Internal variables
lo_trigger = False
def __init__(self):
print_copyright(CR_HOLDERS)
self.parse_argv()
# Open requested capture file
if self.output_file is not None:
self.ddf = DATADumpFile(self.output_file)
def run(self):
# Compose a packet filter
pkt_filter = "udp and (port %d or port %d)" \
% (self.sniff_base_port + 2, self.sniff_base_port + 102)
print("[i] Listening on interface '%s'..." % self.sniff_interface)
# Start sniffing...
scapy.all.sniff(iface = self.sniff_interface, store = 1,
filter = pkt_filter, prn = self.pkt_handler)
# Scapy registers its own signal handler
self.shutdown()
def pkt_handler(self, ether):
# Prevent loopback packet duplication
if self.sniff_interface == "lo":
self.lo_trigger = not self.lo_trigger
if not self.lo_trigger:
return
# Extract a TRX payload
ip = ether.payload
udp = ip.payload
trx = udp.payload
# Convert to bytearray
msg_raw = bytearray(str(trx))
# Determine a burst direction (L1 <-> TRX)
l12trx = udp.sport > udp.dport
# Create an empty DATA message
msg = DATAMSG_L12TRX() if l12trx else DATAMSG_TRX2L1()
# Attempt to parse the payload as a DATA message
try:
msg.parse_msg(msg_raw)
except:
print("[!] Failed to parse message, dropping...")
self.cnt_burst_dropped_num += 1
return
# Poke burst pass filter
rc = self.burst_pass_filter(l12trx, msg.fn, msg.tn)
if rc is False:
self.cnt_burst_dropped_num += 1
return
# Debug print
print("[i] %s burst: %s" \
% ("L1 -> TRX" if l12trx else "TRX -> L1", msg.desc_hdr()))
# Poke message handler
self.msg_handle(msg)
# Poke burst counter
rc = self.burst_count(msg.fn, msg.tn)
if rc is True:
self.shutdown()
def burst_pass_filter(self, l12trx, fn, tn):
# Direction filter
if self.bf_dir_l12trx is not None:
if l12trx != self.bf_dir_l12trx:
return False
# Timeslot filter
if self.bf_tn_val is not None:
if tn != self.bf_tn_val:
return False
# Frame number filter
if self.bf_fn_lt is not None:
if fn > self.bf_fn_lt:
return False
if self.bf_fn_gt is not None:
if fn < self.bf_fn_gt:
return False
# Burst passed ;)
return True
def msg_handle(self, msg):
if self.print_bursts:
print(msg.burst)
# Append a new message to the capture
if self.output_file is not None:
self.ddf.append_msg(msg)
def burst_count(self, fn, tn):
# Update frame counter
if self.cnt_frame_last is None:
self.cnt_frame_last = fn
self.cnt_frame_num += 1
else:
if fn != self.cnt_frame_last:
self.cnt_frame_num += 1
# Update burst counter
self.cnt_burst_num += 1
# Stop sniffing after N bursts
if self.cnt_burst_break is not None:
if self.cnt_burst_num == self.cnt_burst_break:
print("[i] Collected required amount of bursts")
return True
# Stop sniffing after N frames
if self.cnt_frame_break is not None:
if self.cnt_frame_num == self.cnt_frame_break:
print("[i] Collected required amount of frames")
return True
return False
def shutdown(self):
print("[i] Shutting down...")
# Print statistics
print("[i] %u bursts handled, %u dropped" \
% (self.cnt_burst_num, self.cnt_burst_dropped_num))
# Exit
sys.exit(0)
def print_help(self, msg = None):
s = " Usage: " + sys.argv[0] + " [options]\n\n" \
" Some help...\n" \
" -h --help this text\n\n"
s += " Sniffing options\n" \
" -i --sniff-interface Set network interface (default '%s')\n" \
" -p --sniff-base-port Set base port number (default %d)\n\n"
s += " Processing (no processing by default)\n" \
" -o --output-file Write bursts to file\n" \
" -v --print-bits Print burst bits to stdout\n\n" \
s += " Count limitations (disabled by default)\n" \
" --frame-count NUM Stop after sniffing NUM frames\n" \
" --burst-count NUM Stop after sniffing NUM bursts\n\n"
s += " Filtering (disabled by default)\n" \
" --direction DIR Burst direction: L12TRX or TRX2L1\n" \
" --timeslot NUM TDMA timeslot number [0..7]\n" \
" --frame-num-lt NUM TDMA frame number lower than NUM\n" \
" --burst-num-gt NUM TDMA frame number greater than NUM\n"
print(s % (self.sniff_interface, self.sniff_base_port))
if msg is not None:
print(msg)
def parse_argv(self):
try:
opts, args = getopt.getopt(sys.argv[1:],
"i:p:o:v:h", ["help", "sniff-interface=", "sniff-base-port=",
"frame-count=", "burst-count=", "direction=",
"timeslot=", "frame-num-lt=", "frame-num-gt=",
"output-file=", "print-bits"])
except getopt.GetoptError as err:
self.print_help("[!] " + str(err))
sys.exit(2)
for o, v in opts:
if o in ("-h", "--help"):
self.print_help()
sys.exit(2)
elif o in ("-i", "--sniff-interface"):
self.sniff_interface = v
elif o in ("-p", "--sniff-base-port"):
self.sniff_base_port = int(v)
elif o in ("-o", "--output-file"):
self.output_file = v
elif o in ("-v", "--print-bits"):
self.print_bursts = True
# Break counters
elif o == "--frame-count":
self.cnt_frame_break = int(v)
elif o == "--burst-count":
self.cnt_burst_break = int(v)
# Direction filter
elif o == "--direction":
if v == "L12TRX":
self.bf_dir_l12trx = True
elif v == "TRX2L1":
self.bf_dir_l12trx = False
else:
self.print_help("[!] Wrong direction argument")
sys.exit(2)
# Timeslot pass filter
elif o == "--timeslot":
self.bf_tn_val = int(v)
if self.bf_tn_val < 0 or self.bf_tn_val > 7:
self.print_help("[!] Wrong timeslot value")
sys.exit(2)
# Frame number pass filter
elif o == "--frame-num-lt":
self.bf_fn_lt = int(v)
elif o == "--frame-num-gt":
self.bf_fn_gt = int(v)
if __name__ == '__main__':
app = Application()
app.run()

View File

@ -0,0 +1,57 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# TRX Toolkit
# UDP link implementation
#
# (C) 2017 by Vadim Yanitskiy <axilirator@gmail.com>
#
# All Rights Reserved
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import socket
class UDPLink:
def __init__(self, remote_addr, remote_port, bind_addr = '0.0.0.0', bind_port = 0):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.bind((bind_addr, bind_port))
self.sock.setblocking(0)
# Save remote info
self.remote_addr = remote_addr
self.remote_port = remote_port
def __del__(self):
self.sock.close()
def desc_link(self):
(bind_addr, bind_port) = self.sock.getsockname()
return "L:%s:%u <-> R:%s:%u" \
% (bind_addr, bind_port, self.remote_addr, self.remote_port)
def send(self, data):
if type(data) not in [bytearray, bytes]:
data = data.encode()
self.sock.sendto(data, (self.remote_addr, self.remote_port))
def sendto(self, data, remote):
if type(data) not in [bytearray, bytes]:
data = data.encode()
self.sock.sendto(data, remote)