summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHarald Welte <laforge@gnumonks.org>2018-04-07 19:34:19 +0200
committerHarald Welte <laforge@gnumonks.org>2018-04-07 19:35:24 +0200
commit05d95a46fdcdaa53afe45f0afa704ed349a3ff57 (patch)
tree4c44e40eb9d69faa8ebf6e58621cb2aef8f40a78
parentf9ac7eb36ebc97c68a98c700f801015c0fc9c8ad (diff)
parent00bfb39d6c0f561a53aa7642e5f005c061c668ad (diff)
Merge 'fixeria/trx' into master
-rw-r--r--src/Makefile17
-rw-r--r--src/host/trxcon/.gitignore27
-rw-r--r--src/host/trxcon/Makefile.am50
-rw-r--r--src/host/trxcon/configure.ac35
-rw-r--r--src/host/trxcon/l1ctl.c809
-rw-r--r--src/host/trxcon/l1ctl.h25
-rw-r--r--src/host/trxcon/l1ctl_link.c310
-rw-r--r--src/host/trxcon/l1ctl_link.h48
l---------src/host/trxcon/l1ctl_proto.h1
-rw-r--r--src/host/trxcon/logging.c88
-rw-r--r--src/host/trxcon/logging.h17
-rw-r--r--src/host/trxcon/sched_clck.c215
-rw-r--r--src/host/trxcon/sched_lchan_common.c169
-rw-r--r--src/host/trxcon/sched_lchan_desc.c298
-rw-r--r--src/host/trxcon/sched_lchan_rach.c105
-rw-r--r--src/host/trxcon/sched_lchan_sch.c134
-rw-r--r--src/host/trxcon/sched_lchan_tchf.c298
-rw-r--r--src/host/trxcon/sched_lchan_xcch.c200
-rw-r--r--src/host/trxcon/sched_mframe.c1814
-rw-r--r--src/host/trxcon/sched_prim.c332
-rw-r--r--src/host/trxcon/sched_trx.c689
-rw-r--r--src/host/trxcon/sched_trx.h325
-rw-r--r--src/host/trxcon/scheduler.h40
-rw-r--r--src/host/trxcon/trx_if.c701
-rw-r--r--src/host/trxcon/trx_if.h78
-rw-r--r--src/host/trxcon/trxcon.c317
-rw-r--r--src/host/trxcon/trxcon.h21
-rw-r--r--src/target/trx_toolkit/.gitignore4
-rw-r--r--src/target/trx_toolkit/README34
-rw-r--r--src/target/trx_toolkit/burst_fwd.py216
-rwxr-xr-xsrc/target/trx_toolkit/burst_gen.py248
-rwxr-xr-xsrc/target/trx_toolkit/burst_send.py218
-rwxr-xr-xsrc/target/trx_toolkit/clck_gen.py116
-rw-r--r--src/target/trx_toolkit/copyright.py13
-rwxr-xr-xsrc/target/trx_toolkit/ctrl_cmd.py147
-rw-r--r--src/target/trx_toolkit/ctrl_if.py79
-rw-r--r--src/target/trx_toolkit/ctrl_if_bb.py158
-rw-r--r--src/target/trx_toolkit/ctrl_if_bts.py126
-rw-r--r--src/target/trx_toolkit/data_dump.py360
-rw-r--r--src/target/trx_toolkit/data_if.py39
-rw-r--r--src/target/trx_toolkit/data_msg.py545
-rw-r--r--src/target/trx_toolkit/fake_pm.py53
-rwxr-xr-xsrc/target/trx_toolkit/fake_trx.py236
-rw-r--r--src/target/trx_toolkit/gsm_shared.py31
-rw-r--r--src/target/trx_toolkit/rand_burst_gen.py177
-rwxr-xr-xsrc/target/trx_toolkit/trx_sniff.py286
-rw-r--r--src/target/trx_toolkit/udp_link.py57
47 files changed, 10305 insertions, 1 deletions
diff --git a/src/Makefile b/src/Makefile
index bb83d354..d92acbc1 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -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 $@
diff --git a/src/host/trxcon/.gitignore b/src/host/trxcon/.gitignore
new file mode 100644
index 00000000..fe90e43c
--- /dev/null
+++ b/src/host/trxcon/.gitignore
@@ -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
diff --git a/src/host/trxcon/Makefile.am b/src/host/trxcon/Makefile.am
new file mode 100644
index 00000000..c9cc170a
--- /dev/null
+++ b/src/host/trxcon/Makefile.am
@@ -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)
diff --git a/src/host/trxcon/configure.ac b/src/host/trxcon/configure.ac
new file mode 100644
index 00000000..1f24260d
--- /dev/null
+++ b/src/host/trxcon/configure.ac
@@ -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)
diff --git a/src/host/trxcon/l1ctl.c b/src/host/trxcon/l1ctl.c
new file mode 100644
index 00000000..74400be8
--- /dev/null
+++ b/src/host/trxcon/l1ctl.c
@@ -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);
+}
diff --git a/src/host/trxcon/l1ctl.h b/src/host/trxcon/l1ctl.h
new file mode 100644
index 00000000..ca8c0be6
--- /dev/null
+++ b/src/host/trxcon/l1ctl.h
@@ -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);
diff --git a/src/host/trxcon/l1ctl_link.c b/src/host/trxcon/l1ctl_link.c
new file mode 100644
index 00000000..20cb70ce
--- /dev/null
+++ b/src/host/trxcon/l1ctl_link.c
@@ -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);
+}
diff --git a/src/host/trxcon/l1ctl_link.h b/src/host/trxcon/l1ctl_link.h
new file mode 100644
index 00000000..01103dcc
--- /dev/null
+++ b/src/host/trxcon/l1ctl_link.h
@@ -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);
diff --git a/src/host/trxcon/l1ctl_proto.h b/src/host/trxcon/l1ctl_proto.h
new file mode 120000
index 00000000..75862bae
--- /dev/null
+++ b/src/host/trxcon/l1ctl_proto.h
@@ -0,0 +1 @@
+../../../include/l1ctl_proto.h \ No newline at end of file
diff --git a/src/host/trxcon/logging.c b/src/host/trxcon/logging.c
new file mode 100644
index 00000000..a76b4d97
--- /dev/null
+++ b/src/host/trxcon/logging.c
@@ -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;
+}
diff --git a/src/host/trxcon/logging.h b/src/host/trxcon/logging.h
new file mode 100644
index 00000000..0206362a
--- /dev/null
+++ b/src/host/trxcon/logging.h
@@ -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);
diff --git a/src/host/trxcon/sched_clck.c b/src/host/trxcon/sched_clck.c
new file mode 100644
index 00000000..59cffb24
--- /dev/null
+++ b/src/host/trxcon/sched_clck.c
@@ -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;
+}
diff --git a/src/host/trxcon/sched_lchan_common.c b/src/host/trxcon/sched_lchan_common.c
new file mode 100644
index 00000000..47b01621
--- /dev/null
+++ b/src/host/trxcon/sched_lchan_common.c
@@ -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;
+ }
+}
diff --git a/src/host/trxcon/sched_lchan_desc.c b/src/host/trxcon/sched_lchan_desc.c
new file mode 100644
index 00000000..37d12730
--- /dev/null
+++ b/src/host/trxcon/sched_lchan_desc.c
@@ -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,
+ },
+};
diff --git a/src/host/trxcon/sched_lchan_rach.c b/src/host/trxcon/sched_lchan_rach.c
new file mode 100644
index 00000000..2a09a37d
--- /dev/null
+++ b/src/host/trxcon/sched_lchan_rach.c
@@ -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;
+}
diff --git a/src/host/trxcon/sched_lchan_sch.c b/src/host/trxcon/sched_lchan_sch.c
new file mode 100644
index 00000000..1b241a08
--- /dev/null
+++ b/src/host/trxcon/sched_lchan_sch.c
@@ -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;
+}
diff --git a/src/host/trxcon/sched_lchan_tchf.c b/src/host/trxcon/sched_lchan_tchf.c
new file mode 100644
index 00000000..80e4d52f
--- /dev/null
+++ b/src/host/trxcon/sched_lchan_tchf.c
@@ -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;
+}
diff --git a/src/host/trxcon/sched_lchan_xcch.c b/src/host/trxcon/sched_lchan_xcch.c
new file mode 100644
index 00000000..04c9f4ec
--- /dev/null
+++ b/src/host/trxcon/sched_lchan_xcch.c
@@ -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;
+}
diff --git a/src/host/trxcon/sched_mframe.c b/src/host/trxcon/sched_mframe.c
new file mode 100644
index 00000000..25e7c29d
--- /dev/null
+++ b/src/host/trxcon/sched_mframe.c
@@ -0,0 +1,1814 @@
+/*
+ * OsmocomBB <-> SDR connection bridge
+ * TDMA scheduler: channel combinations, burst mapping
+ *
+ * (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 <osmocom/gsm/gsm_utils.h>
+
+#include "sched_trx.h"
+
+/* Non-combined CCCH */
+static const struct trx_frame frame_bcch[51] = {
+ /* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_BCCH, 0, TRXC_RACH, 0 },
+ { TRXC_BCCH, 1, TRXC_RACH, 0 },
+ { TRXC_BCCH, 2, TRXC_RACH, 0 },
+ { TRXC_BCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 1, TRXC_RACH, 0 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 1, TRXC_RACH, 0 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 1, TRXC_RACH, 0 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 1, TRXC_RACH, 0 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 1, TRXC_RACH, 0 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 1, TRXC_RACH, 0 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 1, TRXC_RACH, 0 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 1, TRXC_RACH, 0 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 1, TRXC_RACH, 0 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_IDLE, 0, TRXC_RACH, 0 },
+};
+
+/* Combined CCCH+SDCCH4 */
+static const struct trx_frame frame_bcch_sdcch4[102] = {
+ /* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_FCCH, 0, TRXC_SDCCH4_3, 0 },
+ { TRXC_SCH, 0, TRXC_SDCCH4_3, 1 },
+ { TRXC_BCCH, 0, TRXC_SDCCH4_3, 2 },
+ { TRXC_BCCH, 1, TRXC_SDCCH4_3, 3 },
+ { TRXC_BCCH, 2, TRXC_RACH, 0 },
+ { TRXC_BCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_SACCH4_2, 0 },
+ { TRXC_CCCH, 1, TRXC_SACCH4_2, 1 },
+ { TRXC_CCCH, 2, TRXC_SACCH4_2, 2 },
+ { TRXC_CCCH, 3, TRXC_SACCH4_2, 3 },
+ { TRXC_FCCH, 0, TRXC_SACCH4_3, 0 },
+ { TRXC_SCH, 0, TRXC_SACCH4_3, 1 },
+ { TRXC_CCCH, 0, TRXC_SACCH4_3, 2 },
+ { TRXC_CCCH, 1, TRXC_SACCH4_3, 3 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 1, TRXC_RACH, 0 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 1, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 2, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 3, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 1, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 2, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_2, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_2, 1, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_2, 2, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_2, 3, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_3, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_3, 1, TRXC_SDCCH4_0, 0 },
+ { TRXC_SDCCH4_3, 2, TRXC_SDCCH4_0, 1 },
+ { TRXC_SDCCH4_3, 3, TRXC_SDCCH4_0, 2 },
+ { TRXC_FCCH, 0, TRXC_SDCCH4_0, 3 },
+ { TRXC_SCH, 0, TRXC_SDCCH4_1, 0 },
+ { TRXC_SACCH4_0, 0, TRXC_SDCCH4_1, 1 },
+ { TRXC_SACCH4_0, 1, TRXC_SDCCH4_1, 2 },
+ { TRXC_SACCH4_0, 2, TRXC_SDCCH4_1, 3 },
+ { TRXC_SACCH4_0, 3, TRXC_RACH, 0 },
+ { TRXC_SACCH4_1, 0, TRXC_RACH, 0 },
+ { TRXC_SACCH4_1, 1, TRXC_SDCCH4_2, 0 },
+ { TRXC_SACCH4_1, 2, TRXC_SDCCH4_2, 1 },
+ { TRXC_SACCH4_1, 3, TRXC_SDCCH4_2, 2 },
+ { TRXC_IDLE, 0, TRXC_SDCCH4_2, 3 },
+
+ { TRXC_FCCH, 0, TRXC_SDCCH4_3, 0 },
+ { TRXC_SCH, 0, TRXC_SDCCH4_3, 1 },
+ { TRXC_BCCH, 0, TRXC_SDCCH4_3, 2 },
+ { TRXC_BCCH, 1, TRXC_SDCCH4_3, 3 },
+ { TRXC_BCCH, 2, TRXC_RACH, 0 },
+ { TRXC_BCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_SACCH4_0, 0 },
+ { TRXC_CCCH, 1, TRXC_SACCH4_0, 1 },
+ { TRXC_CCCH, 2, TRXC_SACCH4_0, 2 },
+ { TRXC_CCCH, 3, TRXC_SACCH4_0, 3 },
+ { TRXC_FCCH, 0, TRXC_SACCH4_1, 0 },
+ { TRXC_SCH, 0, TRXC_SACCH4_1, 1 },
+ { TRXC_CCCH, 0, TRXC_SACCH4_1, 2 },
+ { TRXC_CCCH, 1, TRXC_SACCH4_1, 3 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_CCCH, 0, TRXC_RACH, 0 },
+ { TRXC_CCCH, 1, TRXC_RACH, 0 },
+ { TRXC_CCCH, 2, TRXC_RACH, 0 },
+ { TRXC_CCCH, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 1, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 2, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_0, 3, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 1, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 2, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_1, 3, TRXC_RACH, 0 },
+ { TRXC_FCCH, 0, TRXC_RACH, 0 },
+ { TRXC_SCH, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_2, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_2, 1, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_2, 2, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_2, 3, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_3, 0, TRXC_RACH, 0 },
+ { TRXC_SDCCH4_3, 1, TRXC_SDCCH4_0, 0 },
+ { TRXC_SDCCH4_3, 2, TRXC_SDCCH4_0, 1 },
+ { TRXC_SDCCH4_3, 3, TRXC_SDCCH4_0, 2 },
+ { TRXC_FCCH, 0, TRXC_SDCCH4_0, 3 },
+ { TRXC_SCH, 0, TRXC_SDCCH4_1, 0 },
+ { TRXC_SACCH4_2, 0, TRXC_SDCCH4_1, 1 },
+ { TRXC_SACCH4_2, 1, TRXC_SDCCH4_1, 2 },
+ { TRXC_SACCH4_2, 2, TRXC_SDCCH4_1, 3 },
+ { TRXC_SACCH4_2, 3, TRXC_RACH, 0 },
+ { TRXC_SACCH4_3, 0, TRXC_RACH, 0 },
+ { TRXC_SACCH4_3, 1, TRXC_SDCCH4_2, 0 },
+ { TRXC_SACCH4_3, 2, TRXC_SDCCH4_2, 1 },
+ { TRXC_SACCH4_3, 3, TRXC_SDCCH4_2, 2 },
+ { TRXC_IDLE, 0, TRXC_SDCCH4_2, 3 },
+};
+
+static const struct trx_frame frame_sdcch8[102] = {
+ /* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_SDCCH8_0, 0, TRXC_SACCH8_5, 0 },
+ { TRXC_SDCCH8_0, 1, TRXC_SACCH8_5, 1 },
+ { TRXC_SDCCH8_0, 2, TRXC_SACCH8_5, 2 },
+ { TRXC_SDCCH8_0, 3, TRXC_SACCH8_5, 3 },
+ { TRXC_SDCCH8_1, 0, TRXC_SACCH8_6, 0 },
+ { TRXC_SDCCH8_1, 1, TRXC_SACCH8_6, 1 },
+ { TRXC_SDCCH8_1, 2, TRXC_SACCH8_6, 2 },
+ { TRXC_SDCCH8_1, 3, TRXC_SACCH8_6, 3 },
+ { TRXC_SDCCH8_2, 0, TRXC_SACCH8_7, 0 },
+ { TRXC_SDCCH8_2, 1, TRXC_SACCH8_7, 1 },
+ { TRXC_SDCCH8_2, 2, TRXC_SACCH8_7, 2 },
+ { TRXC_SDCCH8_2, 3, TRXC_SACCH8_7, 3 },
+ { TRXC_SDCCH8_3, 0, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 1, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 2, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 3, TRXC_SDCCH8_0, 0 },
+ { TRXC_SDCCH8_4, 0, TRXC_SDCCH8_0, 1 },
+ { TRXC_SDCCH8_4, 1, TRXC_SDCCH8_0, 2 },
+ { TRXC_SDCCH8_4, 2, TRXC_SDCCH8_0, 3 },
+ { TRXC_SDCCH8_4, 3, TRXC_SDCCH8_1, 0 },
+ { TRXC_SDCCH8_5, 0, TRXC_SDCCH8_1, 1 },
+ { TRXC_SDCCH8_5, 1, TRXC_SDCCH8_1, 2 },
+ { TRXC_SDCCH8_5, 2, TRXC_SDCCH8_1, 3 },
+ { TRXC_SDCCH8_5, 3, TRXC_SDCCH8_2, 0 },
+ { TRXC_SDCCH8_6, 0, TRXC_SDCCH8_2, 1 },
+ { TRXC_SDCCH8_6, 1, TRXC_SDCCH8_2, 2 },
+ { TRXC_SDCCH8_6, 2, TRXC_SDCCH8_2, 3 },
+ { TRXC_SDCCH8_6, 3, TRXC_SDCCH8_3, 0 },
+ { TRXC_SDCCH8_7, 0, TRXC_SDCCH8_3, 1 },
+ { TRXC_SDCCH8_7, 1, TRXC_SDCCH8_3, 2 },
+ { TRXC_SDCCH8_7, 2, TRXC_SDCCH8_3, 3 },
+ { TRXC_SDCCH8_7, 3, TRXC_SDCCH8_4, 0 },
+ { TRXC_SACCH8_0, 0, TRXC_SDCCH8_4, 1 },
+ { TRXC_SACCH8_0, 1, TRXC_SDCCH8_4, 2 },
+ { TRXC_SACCH8_0, 2, TRXC_SDCCH8_4, 3 },
+ { TRXC_SACCH8_0, 3, TRXC_SDCCH8_5, 0 },
+ { TRXC_SACCH8_1, 0, TRXC_SDCCH8_5, 1 },
+ { TRXC_SACCH8_1, 1, TRXC_SDCCH8_5, 2 },
+ { TRXC_SACCH8_1, 2, TRXC_SDCCH8_5, 3 },
+ { TRXC_SACCH8_1, 3, TRXC_SDCCH8_6, 0 },
+ { TRXC_SACCH8_2, 0, TRXC_SDCCH8_6, 1 },
+ { TRXC_SACCH8_2, 1, TRXC_SDCCH8_6, 2 },
+ { TRXC_SACCH8_2, 2, TRXC_SDCCH8_6, 3 },
+ { TRXC_SACCH8_2, 3, TRXC_SDCCH8_7, 0 },
+ { TRXC_SACCH8_3, 0, TRXC_SDCCH8_7, 1 },
+ { TRXC_SACCH8_3, 1, TRXC_SDCCH8_7, 2 },
+ { TRXC_SACCH8_3, 2, TRXC_SDCCH8_7, 3 },
+ { TRXC_SACCH8_3, 3, TRXC_SACCH8_0, 0 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_0, 1 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_0, 2 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_0, 3 },
+
+ { TRXC_SDCCH8_0, 0, TRXC_SACCH8_1, 0 },
+ { TRXC_SDCCH8_0, 1, TRXC_SACCH8_1, 1 },
+ { TRXC_SDCCH8_0, 2, TRXC_SACCH8_1, 2 },
+ { TRXC_SDCCH8_0, 3, TRXC_SACCH8_1, 3 },
+ { TRXC_SDCCH8_1, 0, TRXC_SACCH8_2, 0 },
+ { TRXC_SDCCH8_1, 1, TRXC_SACCH8_2, 1 },
+ { TRXC_SDCCH8_1, 2, TRXC_SACCH8_2, 2 },
+ { TRXC_SDCCH8_1, 3, TRXC_SACCH8_2, 3 },
+ { TRXC_SDCCH8_2, 0, TRXC_SACCH8_3, 0 },
+ { TRXC_SDCCH8_2, 1, TRXC_SACCH8_3, 1 },
+ { TRXC_SDCCH8_2, 2, TRXC_SACCH8_3, 2 },
+ { TRXC_SDCCH8_2, 3, TRXC_SACCH8_3, 3 },
+ { TRXC_SDCCH8_3, 0, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 1, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 2, TRXC_IDLE, 0 },
+ { TRXC_SDCCH8_3, 3, TRXC_SDCCH8_0, 0 },
+ { TRXC_SDCCH8_4, 0, TRXC_SDCCH8_0, 1 },
+ { TRXC_SDCCH8_4, 1, TRXC_SDCCH8_0, 2 },
+ { TRXC_SDCCH8_4, 2, TRXC_SDCCH8_0, 3 },
+ { TRXC_SDCCH8_4, 3, TRXC_SDCCH8_1, 0 },
+ { TRXC_SDCCH8_5, 0, TRXC_SDCCH8_1, 1 },
+ { TRXC_SDCCH8_5, 1, TRXC_SDCCH8_1, 2 },
+ { TRXC_SDCCH8_5, 2, TRXC_SDCCH8_1, 3 },
+ { TRXC_SDCCH8_5, 3, TRXC_SDCCH8_2, 0 },
+ { TRXC_SDCCH8_6, 0, TRXC_SDCCH8_2, 1 },
+ { TRXC_SDCCH8_6, 1, TRXC_SDCCH8_2, 2 },
+ { TRXC_SDCCH8_6, 2, TRXC_SDCCH8_2, 3 },
+ { TRXC_SDCCH8_6, 3, TRXC_SDCCH8_3, 0 },
+ { TRXC_SDCCH8_7, 0, TRXC_SDCCH8_3, 1 },
+ { TRXC_SDCCH8_7, 1, TRXC_SDCCH8_3, 2 },
+ { TRXC_SDCCH8_7, 2, TRXC_SDCCH8_3, 3 },
+ { TRXC_SDCCH8_7, 3, TRXC_SDCCH8_4, 0 },
+ { TRXC_SACCH8_4, 0, TRXC_SDCCH8_4, 1 },
+ { TRXC_SACCH8_4, 1, TRXC_SDCCH8_4, 2 },
+ { TRXC_SACCH8_4, 2, TRXC_SDCCH8_4, 3 },
+ { TRXC_SACCH8_4, 3, TRXC_SDCCH8_5, 0 },
+ { TRXC_SACCH8_5, 0, TRXC_SDCCH8_5, 1 },
+ { TRXC_SACCH8_5, 1, TRXC_SDCCH8_5, 2 },
+ { TRXC_SACCH8_5, 2, TRXC_SDCCH8_5, 3 },
+ { TRXC_SACCH8_5, 3, TRXC_SDCCH8_6, 0 },
+ { TRXC_SACCH8_6, 0, TRXC_SDCCH8_6, 1 },
+ { TRXC_SACCH8_6, 1, TRXC_SDCCH8_6, 2 },
+ { TRXC_SACCH8_6, 2, TRXC_SDCCH8_6, 3 },
+ { TRXC_SACCH8_6, 3, TRXC_SDCCH8_7, 0 },
+ { TRXC_SACCH8_7, 0, TRXC_SDCCH8_7, 1 },
+ { TRXC_SACCH8_7, 1, TRXC_SDCCH8_7, 2 },
+ { TRXC_SACCH8_7, 2, TRXC_SDCCH8_7, 3 },
+ { TRXC_SACCH8_7, 3, TRXC_SACCH8_4, 0 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_4, 1 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_4, 2 },
+ { TRXC_IDLE, 0, TRXC_SACCH8_4, 3 },
+};
+
+static const struct trx_frame frame_tchf_ts0[104] = {
+ /* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+};
+
+static const struct trx_frame frame_tchf_ts1[104] = {
+ /* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 },
+};
+
+static const struct trx_frame frame_tchf_ts2[104] = {
+ /* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+};
+
+static const struct trx_frame frame_tchf_ts3[104] = {
+ /* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 },
+};
+
+static const struct trx_frame frame_tchf_ts4[104] = {
+ /* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+};
+
+static const struct trx_frame frame_tchf_ts5[104] = {
+ /* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 },
+};
+
+static const struct trx_frame frame_tchf_ts6[104] = {
+ /* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+};
+
+static const struct trx_frame frame_tchf_ts7[104] = {
+ /* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 1, TRXC_SACCHTF, 1 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 2, TRXC_SACCHTF, 2 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 3, TRXC_SACCHTF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_TCHF, 0, TRXC_TCHF, 0 },
+ { TRXC_TCHF, 1, TRXC_TCHF, 1 },
+ { TRXC_TCHF, 2, TRXC_TCHF, 2 },
+ { TRXC_TCHF, 3, TRXC_TCHF, 3 },
+ { TRXC_SACCHTF, 0, TRXC_SACCHTF, 0 },
+};
+
+static const struct trx_frame frame_tchh_ts01[104] = {
+ /* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 },
+};
+
+static const struct trx_frame frame_tchh_ts23[104] = {
+ /* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 },
+};
+
+static const struct trx_frame frame_tchh_ts45[104] = {
+ /* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 },
+};
+
+static const struct trx_frame frame_tchh_ts67[104] = {
+ /* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 1, TRXC_SACCHTH_0, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 1, TRXC_SACCHTH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 2, TRXC_SACCHTH_0, 2 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 2, TRXC_SACCHTH_1, 2 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 3, TRXC_SACCHTH_0, 3 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 3, TRXC_SACCHTH_1, 3 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_0, 0, TRXC_SACCHTH_0, 0 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_TCHH_0, 0, TRXC_TCHH_0, 0 },
+ { TRXC_TCHH_1, 0, TRXC_TCHH_1, 0 },
+ { TRXC_TCHH_0, 1, TRXC_TCHH_0, 1 },
+ { TRXC_TCHH_1, 1, TRXC_TCHH_1, 1 },
+ { TRXC_SACCHTH_1, 0, TRXC_SACCHTH_1, 0 },
+};
+
+static const struct trx_frame frame_pdch[104] = {
+ /* dl_chan dl_bid ul_chan ul_bid */
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 },
+ { TRXC_PDTCH, 1, TRXC_PDTCH, 1 },
+ { TRXC_PDTCH, 2, TRXC_PDTCH, 2 },
+ { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 },
+ { TRXC_PDTCH, 1, TRXC_PDTCH, 1 },
+ { TRXC_PDTCH, 2, TRXC_PDTCH, 2 },
+ { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 },
+ { TRXC_PDTCH, 1, TRXC_PDTCH, 1 },
+ { TRXC_PDTCH, 2, TRXC_PDTCH, 2 },
+ { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PTCCH, 0, TRXC_PTCCH, 0 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 },
+ { TRXC_PDTCH, 1, TRXC_PDTCH, 1 },
+ { TRXC_PDTCH, 2, TRXC_PDTCH, 2 },
+ { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 },
+ { TRXC_PDTCH, 1, TRXC_PDTCH, 1 },
+ { TRXC_PDTCH, 2, TRXC_PDTCH, 2 },
+ { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 },
+ { TRXC_PDTCH, 1, TRXC_PDTCH, 1 },
+ { TRXC_PDTCH, 2, TRXC_PDTCH, 2 },
+ { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 },
+ { TRXC_PDTCH, 1, TRXC_PDTCH, 1 },
+ { TRXC_PDTCH, 2, TRXC_PDTCH, 2 },
+ { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 },
+ { TRXC_PDTCH, 1, TRXC_PDTCH, 1 },
+ { TRXC_PDTCH, 2, TRXC_PDTCH, 2 },
+ { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 },
+ { TRXC_PDTCH, 1, TRXC_PDTCH, 1 },
+ { TRXC_PDTCH, 2, TRXC_PDTCH, 2 },
+ { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PTCCH, 1, TRXC_PTCCH, 1 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 },
+ { TRXC_PDTCH, 1, TRXC_PDTCH, 1 },
+ { TRXC_PDTCH, 2, TRXC_PDTCH, 2 },
+ { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 },
+ { TRXC_PDTCH, 1, TRXC_PDTCH, 1 },
+ { TRXC_PDTCH, 2, TRXC_PDTCH, 2 },
+ { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 },
+ { TRXC_PDTCH, 1, TRXC_PDTCH, 1 },
+ { TRXC_PDTCH, 2, TRXC_PDTCH, 2 },
+ { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 },
+ { TRXC_PDTCH, 1, TRXC_PDTCH, 1 },
+ { TRXC_PDTCH, 2, TRXC_PDTCH, 2 },
+ { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 },
+ { TRXC_PDTCH, 1, TRXC_PDTCH, 1 },
+ { TRXC_PDTCH, 2, TRXC_PDTCH, 2 },
+ { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 },
+ { TRXC_PDTCH, 1, TRXC_PDTCH, 1 },
+ { TRXC_PDTCH, 2, TRXC_PDTCH, 2 },
+ { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PTCCH, 2, TRXC_PTCCH, 2 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 },
+ { TRXC_PDTCH, 1, TRXC_PDTCH, 1 },
+ { TRXC_PDTCH, 2, TRXC_PDTCH, 2 },
+ { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 },
+ { TRXC_PDTCH, 1, TRXC_PDTCH, 1 },
+ { TRXC_PDTCH, 2, TRXC_PDTCH, 2 },
+ { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 },
+ { TRXC_PDTCH, 1, TRXC_PDTCH, 1 },
+ { TRXC_PDTCH, 2, TRXC_PDTCH, 2 },
+ { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 },
+ { TRXC_PDTCH, 1, TRXC_PDTCH, 1 },
+ { TRXC_PDTCH, 2, TRXC_PDTCH, 2 },
+ { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 },
+ { TRXC_PDTCH, 1, TRXC_PDTCH, 1 },
+ { TRXC_PDTCH, 2, TRXC_PDTCH, 2 },
+ { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 },
+ { TRXC_PDTCH, 1, TRXC_PDTCH, 1 },
+ { TRXC_PDTCH, 2, TRXC_PDTCH, 2 },
+ { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PTCCH, 3, TRXC_PTCCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 },
+ { TRXC_PDTCH, 1, TRXC_PDTCH, 1 },
+ { TRXC_PDTCH, 2, TRXC_PDTCH, 2 },
+ { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 },
+ { TRXC_PDTCH, 1, TRXC_PDTCH, 1 },
+ { TRXC_PDTCH, 2, TRXC_PDTCH, 2 },
+ { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_PDTCH, 0, TRXC_PDTCH, 0 },
+ { TRXC_PDTCH, 1, TRXC_PDTCH, 1 },
+ { TRXC_PDTCH, 2, TRXC_PDTCH, 2 },
+ { TRXC_PDTCH, 3, TRXC_PDTCH, 3 },
+ { TRXC_IDLE, 0, TRXC_IDLE, 0 },
+};
+
+/**
+ * A few notes about frame count:
+ *
+ * 26 frame multiframe - traffic multiframe
+ * 51 frame multiframe - control multiframe
+ *
+ * 102 = 2 x 51 frame multiframe
+ * 104 = 4 x 26 frame multiframe
+ */
+static const struct trx_multiframe layouts[] = {
+ {
+ GSM_PCHAN_NONE, "NONE",
+ 0, 0xff, (uint64_t) 0x00,
+ NULL
+ },
+ {
+ GSM_PCHAN_CCCH, "BCCH+CCCH",
+ 51, 0xff, (uint64_t) 0x3e,
+ frame_bcch
+ },
+ {
+ GSM_PCHAN_CCCH_SDCCH4, "BCCH+CCCH+SDCCH/4+SACCH/4",
+ 102, 0xff, (uint64_t) 0xf001e3e,
+ frame_bcch_sdcch4
+ },
+ {
+ GSM_PCHAN_SDCCH8_SACCH8C, "SDCCH/8+SACCH/8",
+ 102, 0xff, (uint64_t) 0xff01fe000,
+ frame_sdcch8
+ },
+ {
+ GSM_PCHAN_TCH_F, "TCH/F+SACCH",
+ 104, 0x01, (uint64_t) 0x200040,
+ frame_tchf_ts0
+ },
+ {
+ GSM_PCHAN_TCH_F, "TCH/F+SACCH",
+ 104, 0x02, (uint64_t) 0x200040,
+ frame_tchf_ts1
+ },
+ {
+ GSM_PCHAN_TCH_F, "TCH/F+SACCH",
+ 104, 0x04, (uint64_t) 0x200040,
+ frame_tchf_ts2
+ },
+ {
+ GSM_PCHAN_TCH_F, "TCH/F+SACCH",
+ 104, 0x08, (uint64_t) 0x200040,
+ frame_tchf_ts3
+ },
+ {
+ GSM_PCHAN_TCH_F, "TCH/F+SACCH",
+ 104, 0x10, (uint64_t) 0x200040,
+ frame_tchf_ts4
+ },
+ {
+ GSM_PCHAN_TCH_F, "TCH/F+SACCH",
+ 104, 0x20, (uint64_t) 0x200040,
+ frame_tchf_ts5
+ },
+ {
+ GSM_PCHAN_TCH_F, "TCH/F+SACCH",
+ 104, 0x40, (uint64_t) 0x200040,
+ frame_tchf_ts6
+ },
+ {
+ GSM_PCHAN_TCH_F, "TCH/F+SACCH",
+ 104, 0x80, (uint64_t) 0x200040,
+ frame_tchf_ts7
+ },
+ {
+ GSM_PCHAN_TCH_H, "TCH/H+SACCH",
+ 104, 0x03, (uint64_t) 0xc00180,
+ frame_tchh_ts01
+ },
+ {
+ GSM_PCHAN_TCH_H, "TCH/H+SACCH",
+ 104, 0x0c, (uint64_t) 0xc00180,
+ frame_tchh_ts23
+ },
+ {
+ GSM_PCHAN_TCH_H, "TCH/H+SACCH",
+ 104, 0x30, (uint64_t) 0xc00180,
+ frame_tchh_ts45
+ },
+ {
+ GSM_PCHAN_TCH_H, "TCH/H+SACCH",
+ 104, 0xc0, (uint64_t) 0xc00180,
+ frame_tchh_ts67
+ },
+ {
+ GSM_PCHAN_PDCH, "PDCH",
+ 104, 0xff, (uint64_t) 0x3000000000,
+ frame_pdch
+ },
+};
+
+const struct trx_multiframe *sched_mframe_layout(
+ enum gsm_phys_chan_config config, int tn)
+{
+ int i, ts_allowed;
+
+ for (i = 0; i < ARRAY_SIZE(layouts); i++) {
+ ts_allowed = layouts[i].slotmask & (0x01 << tn);
+ if (layouts[i].chan_config == config && ts_allowed)
+ return &layouts[i];
+ }
+
+ return NULL;
+}
diff --git a/src/host/trxcon/sched_prim.c b/src/host/trxcon/sched_prim.c
new file mode 100644
index 00000000..e1c87bbf
--- /dev/null
+++ b/src/host/trxcon/sched_prim.c
@@ -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);
+ }
+}
diff --git a/src/host/trxcon/sched_trx.c b/src/host/trxcon/sched_trx.c
new file mode 100644
index 00000000..c263ce7f
--- /dev/null
+++ b/src/host/trxcon/sched_trx.c
@@ -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;
+}
diff --git a/src/host/trxcon/sched_trx.h b/src/host/trxcon/sched_trx.h
new file mode 100644
index 00000000..17e30bff
--- /dev/null
+++ b/src/host/trxcon/sched_trx.h
@@ -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);
diff --git a/src/host/trxcon/scheduler.h b/src/host/trxcon/scheduler.h
new file mode 100644
index 00000000..f36c3b2b
--- /dev/null
+++ b/src/host/trxcon/scheduler.h
@@ -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);
diff --git a/src/host/trxcon/trx_if.c b/src/host/trxcon/trx_if.c
new file mode 100644
index 00000000..cab5a9bd
--- /dev/null
+++ b/src/host/trxcon/trx_if.c
@@ -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);
+}
diff --git a/src/host/trxcon/trx_if.h b/src/host/trxcon/trx_if.h
new file mode 100644
index 00000000..6080dcee
--- /dev/null
+++ b/src/host/trxcon/trx_if.h
@@ -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);
diff --git a/src/host/trxcon/trxcon.c b/src/host/trxcon/trxcon.c
new file mode 100644
index 00000000..43c98a5c
--- /dev/null
+++ b/src/host/trxcon/trxcon.c
@@ -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;
+}
diff --git a/src/host/trxcon/trxcon.h b/src/host/trxcon/trxcon.h
new file mode 100644
index 00000000..65b5e85d
--- /dev/null
+++ b/src/host/trxcon/trxcon.h
@@ -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,
+};
diff --git a/src/target/trx_toolkit/.gitignore b/src/target/trx_toolkit/.gitignore
new file mode 100644
index 00000000..749ccdaf
--- /dev/null
+++ b/src/target/trx_toolkit/.gitignore
@@ -0,0 +1,4 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
diff --git a/src/target/trx_toolkit/README b/src/target/trx_toolkit/README
new file mode 100644
index 00000000..91b6099b
--- /dev/null
+++ b/src/target/trx_toolkit/README
@@ -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.
diff --git a/src/target/trx_toolkit/burst_fwd.py b/src/target/trx_toolkit/burst_fwd.py
new file mode 100644
index 00000000..144ae5f4
--- /dev/null
+++ b/src/target/trx_toolkit/burst_fwd.py
@@ -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)
diff --git a/src/target/trx_toolkit/burst_gen.py b/src/target/trx_toolkit/burst_gen.py
new file mode 100755
index 00000000..d83f1378
--- /dev/null
+++ b/src/target/trx_toolkit/burst_gen.py
@@ -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()
diff --git a/src/target/trx_toolkit/burst_send.py b/src/target/trx_toolkit/burst_send.py
new file mode 100755
index 00000000..f6c85ba0
--- /dev/null
+++ b/src/target/trx_toolkit/burst_send.py
@@ -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()
diff --git a/src/target/trx_toolkit/clck_gen.py b/src/target/trx_toolkit/clck_gen.py
new file mode 100755
index 00000000..b488770e
--- /dev/null
+++ b/src/target/trx_toolkit/clck_gen.py
@@ -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()
diff --git a/src/target/trx_toolkit/copyright.py b/src/target/trx_toolkit/copyright.py
new file mode 100644
index 00000000..3d3597fd
--- /dev/null
+++ b/src/target/trx_toolkit/copyright.py
@@ -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")
diff --git a/src/target/trx_toolkit/ctrl_cmd.py b/src/target/trx_toolkit/ctrl_cmd.py
new file mode 100755
index 00000000..e56105a8
--- /dev/null
+++ b/src/target/trx_toolkit/ctrl_cmd.py
@@ -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()
diff --git a/src/target/trx_toolkit/ctrl_if.py b/src/target/trx_toolkit/ctrl_if.py
new file mode 100644
index 00000000..1e569a60
--- /dev/null
+++ b/src/target/trx_toolkit/ctrl_if.py
@@ -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
diff --git a/src/target/trx_toolkit/ctrl_if_bb.py b/src/target/trx_toolkit/ctrl_if_bb.py
new file mode 100644
index 00000000..3de14ef7
--- /dev/null
+++ b/src/target/trx_toolkit/ctrl_if_bb.py
@@ -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
diff --git a/src/target/trx_toolkit/ctrl_if_bts.py b/src/target/trx_toolkit/ctrl_if_bts.py
new file mode 100644
index 00000000..14886178
--- /dev/null
+++ b/src/target/trx_toolkit/ctrl_if_bts.py
@@ -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
diff --git a/src/target/trx_toolkit/data_dump.py b/src/target/trx_toolkit/data_dump.py
new file mode 100644
index 00000000..1d7805e3
--- /dev/null
+++ b/src/target/trx_toolkit/data_dump.py
@@ -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")
diff --git a/src/target/trx_toolkit/data_if.py b/src/target/trx_toolkit/data_if.py
new file mode 100644
index 00000000..f4431a46
--- /dev/null
+++ b/src/target/trx_toolkit/data_if.py
@@ -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)
diff --git a/src/target/trx_toolkit/data_msg.py b/src/target/trx_toolkit/data_msg.py
new file mode 100644
index 00000000..ea415ab9
--- /dev/null
+++ b/src/target/trx_toolkit/data_msg.py
@@ -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")
diff --git a/src/target/trx_toolkit/fake_pm.py b/src/target/trx_toolkit/fake_pm.py
new file mode 100644
index 00000000..840b4e40
--- /dev/null
+++ b/src/target/trx_toolkit/fake_pm.py
@@ -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)
diff --git a/src/target/trx_toolkit/fake_trx.py b/src/target/trx_toolkit/fake_trx.py
new file mode 100755
index 00000000..b818b2a9
--- /dev/null
+++ b/src/target/trx_toolkit/fake_trx.py
@@ -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()
diff --git a/src/target/trx_toolkit/gsm_shared.py b/src/target/trx_toolkit/gsm_shared.py
new file mode 100644
index 00000000..d2f8278b
--- /dev/null
+++ b/src/target/trx_toolkit/gsm_shared.py
@@ -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
diff --git a/src/target/trx_toolkit/rand_burst_gen.py b/src/target/trx_toolkit/rand_burst_gen.py
new file mode 100644
index 00000000..46c1e090
--- /dev/null
+++ b/src/target/trx_toolkit/rand_burst_gen.py
@@ -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
diff --git a/src/target/trx_toolkit/trx_sniff.py b/src/target/trx_toolkit/trx_sniff.py
new file mode 100755
index 00000000..577e6f97
--- /dev/null
+++ b/src/target/trx_toolkit/trx_sniff.py
@@ -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()
diff --git a/src/target/trx_toolkit/udp_link.py b/src/target/trx_toolkit/udp_link.py
new file mode 100644
index 00000000..b378b635
--- /dev/null
+++ b/src/target/trx_toolkit/udp_link.py
@@ -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)