diff --git a/src/Makefile b/src/Makefile index bb83d354f..d92acbc1b 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 000000000..fe90e43c1 --- /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 000000000..c9cc170a0 --- /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 000000000..1f24260da --- /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 000000000..74400be84 --- /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 + * (C) 2016-2017 by Vadim Yanitskiy + * + * 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 +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#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 000000000..ca8c0be63 --- /dev/null +++ b/src/host/trxcon/l1ctl.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +#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 000000000..20cb70ce2 --- /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 + * (C) 2016-2017 by Vadim Yanitskiy + * + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#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 000000000..01103dccc --- /dev/null +++ b/src/host/trxcon/l1ctl_link.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + +#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 000000000..75862baeb --- /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 000000000..a76b4d97b --- /dev/null +++ b/src/host/trxcon/logging.c @@ -0,0 +1,88 @@ +/* + * OsmocomBB <-> SDR connection bridge + * + * (C) 2016-2017 by Vadim Yanitskiy + * + * 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 +#include +#include + +#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 000000000..0206362a5 --- /dev/null +++ b/src/host/trxcon/logging.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#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 000000000..59cffb243 --- /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 + * (C) 2015 by Alexander Chemeris + * (C) 2015 by Harald Welte + * + * 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 . + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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 000000000..47b01621b --- /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 + * + * 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 +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include +#include + +#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 000000000..37d12730e --- /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 + * (C) 2015 by Alexander Chemeris + * (C) 2015 by Harald Welte + * + * 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 . + * + */ + +#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 000000000..2a09a37da --- /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 + * + * 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 +#include +#include +#include + +#include +#include + +#include +#include + +#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 000000000..1b241a083 --- /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 + * + * 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 +#include +#include +#include + +#include + +#include +#include + +#include +#include + +#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 000000000..80e4d52fd --- /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 + * + * 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 +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#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 000000000..04c9f4ec2 --- /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 + * + * 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 +#include +#include + +#include +#include + +#include +#include +#include + +#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 000000000..25e7c29d6 --- /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 + * (C) 2015 by Alexander Chemeris + * (C) 2015 by Harald Welte + * + * 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 . + * + */ + +#include + +#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 000000000..e1c87bbf2 --- /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 + * + * 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 +#include +#include +#include + +#include +#include +#include + +#include +#include + +#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 000000000..c263ce7fd --- /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 + * + * 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 +#include +#include +#include + +#include +#include +#include +#include +#include + +#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 000000000..17e30bff4 --- /dev/null +++ b/src/host/trxcon/sched_trx.h @@ -0,0 +1,325 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +#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 000000000..f36c3b2b4 --- /dev/null +++ b/src/host/trxcon/scheduler.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +#include + +#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 000000000..cab5a9bde --- /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 + * Copyright (C) 2016-2017 by Vadim Yanitskiy + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#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 [params] */ +/* */ +/* The is the actual command. */ +/* Parameters are optional depending on the commands type. */ +/* Every response is of the form: */ +/* */ +/* RSP [result] */ +/* */ +/* The 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 + * + * POWEROFF shuts off transmitter power and stops the demodulator. + * CMD POWEROFF + * RSP POWEROFF + * + * 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 + */ + +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 + * RSP SETPOWER + */ + +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 + * RSP ADJPOWER +*/ + +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 indicates the timeslot of interest. + * The indicates the type of channel that occupies the timeslot. + * A chantype of zero indicates the timeslot is off. + * CMD SETSLOT + * RSP SETSLOT + */ + +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 + * RSP (RX/TX)TUNE + */ + +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 + * RSP MEASURE + */ + +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 + */ + +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 000000000..6080dceec --- /dev/null +++ b/src/host/trxcon/trx_if.h @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include +#include + +#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 000000000..43c98a5c0 --- /dev/null +++ b/src/host/trxcon/trxcon.c @@ -0,0 +1,317 @@ +/* + * OsmocomBB <-> SDR connection bridge + * + * (C) 2016-2017 by Vadim Yanitskiy + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#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 \n" \ + "License GPLv2+: GNU GPL version 2 or later " \ + "\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 000000000..65b5e85d7 --- /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 000000000..749ccdafd --- /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 000000000..91b6099b5 --- /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 000000000..144ae5f46 --- /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 +# +# 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 000000000..d83f1378f --- /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 +# +# 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 ")] + +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 000000000..f6c85ba05 --- /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 +# +# 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 ")] + +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 000000000..b488770e2 --- /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 +# +# 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 ")] + +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 000000000..3d3597fd5 --- /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 " \ + "\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 000000000..e56105a84 --- /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 +# +# 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 ")] + +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 000000000..1e569a608 --- /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 +# +# 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 000000000..3de14ef73 --- /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 +# +# 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 + 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 000000000..14886178f --- /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 +# +# 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 + 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 000000000..1d7805e30 --- /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 +# +# 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 000000000..f4431a46c --- /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 +# +# 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 000000000..ea415ab96 --- /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 +# +# 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 000000000..840b4e407 --- /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 +# +# 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 000000000..b818b2a9b --- /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 +# +# 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 ")] + +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 000000000..d2f8278b6 --- /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 +# +# 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 000000000..46c1e090b --- /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 +# +# 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 000000000..577e6f97e --- /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 +# +# 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 ")] + +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 000000000..b378b635f --- /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 +# +# 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)