Change in osmo-bts[master]: osmo-bts-trx: move logical channel handlers to separate files

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.

fixeria gerrit-no-reply at lists.osmocom.org
Sat Jun 13 17:24:57 UTC 2020


fixeria has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-bts/+/18820 )


Change subject: osmo-bts-trx: move logical channel handlers to separate files
......................................................................

osmo-bts-trx: move logical channel handlers to separate files

It's easier to maintain the logical channel handlers in separate
files, rather than in a huge one (scheduler_trx.c, ~2k lines).

Change-Id: Ie5663fd90596b4800a4546675a323250bbb24c80
---
M src/osmo-bts-trx/Makefile.am
A src/osmo-bts-trx/sched_lchan_fcch_sch.c
A src/osmo-bts-trx/sched_lchan_pdtch.c
A src/osmo-bts-trx/sched_lchan_rach.c
A src/osmo-bts-trx/sched_lchan_tchf.c
A src/osmo-bts-trx/sched_lchan_tchh.c
A src/osmo-bts-trx/sched_lchan_xcch.c
A src/osmo-bts-trx/sched_utils.h
M src/osmo-bts-trx/scheduler_trx.c
9 files changed, 1,816 insertions(+), 1,562 deletions(-)



  git pull ssh://gerrit.osmocom.org:29418/osmo-bts refs/changes/20/18820/1

diff --git a/src/osmo-bts-trx/Makefile.am b/src/osmo-bts-trx/Makefile.am
index b2d9b19..ae69000 100644
--- a/src/osmo-bts-trx/Makefile.am
+++ b/src/osmo-bts-trx/Makefile.am
@@ -28,6 +28,7 @@
 	$(NULL)
 
 noinst_HEADERS = \
+	sched_utils.h \
 	trx_if.h \
 	l1_if.h \
 	loops.h \
@@ -40,6 +41,12 @@
 	trx_if.c \
 	l1_if.c \
 	scheduler_trx.c \
+	sched_lchan_fcch_sch.c \
+	sched_lchan_rach.c \
+	sched_lchan_xcch.c \
+	sched_lchan_pdtch.c \
+	sched_lchan_tchf.c \
+	sched_lchan_tchh.c \
 	trx_vty.c \
 	loops.c \
 	$(NULL)
diff --git a/src/osmo-bts-trx/sched_lchan_fcch_sch.c b/src/osmo-bts-trx/sched_lchan_fcch_sch.c
new file mode 100644
index 0000000..63dd468
--- /dev/null
+++ b/src/osmo-bts-trx/sched_lchan_fcch_sch.c
@@ -0,0 +1,94 @@
+/*
+ * (C) 2013 by Andreas Eversberg <jolly at eversberg.eu>
+ * (C) 2015-2017 by Harald Welte <laforge at gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/coding/gsm0503_coding.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/scheduler.h>
+#include <osmo-bts/scheduler_backend.h>
+
+#include <sched_utils.h>
+
+/* obtain a to-be-transmitted FCCH (frequency correction channel) burst */
+ubit_t *tx_fcch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+		   enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+	LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting FCCH\n");
+
+	if (nbits)
+		*nbits = GSM_BURST_LEN;
+
+	/* BURST BYPASS */
+
+	return (ubit_t *) _sched_fcch_burst;
+}
+
+/* obtain a to-be-transmitted SCH (synchronization channel) burst */
+ubit_t *tx_sch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+		  enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+	static ubit_t bits[GSM_BURST_LEN], burst[78];
+	uint8_t sb_info[4];
+	struct	gsm_time t;
+	uint8_t t3p, bsic;
+
+	LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting SCH\n");
+
+	/* BURST BYPASS */
+
+	/* create SB info from GSM time and BSIC */
+	gsm_fn2gsmtime(&t, fn);
+	t3p = t.t3 / 10;
+	bsic = l1t->trx->bts->bsic;
+	sb_info[0] =
+		((bsic &  0x3f) << 2) |
+		((t.t1 & 0x600) >> 9);
+	sb_info[1] =
+		((t.t1 & 0x1fe) >> 1);
+	sb_info[2] =
+		((t.t1 & 0x001) << 7) |
+		((t.t2 &  0x1f) << 2) |
+		((t3p  &   0x6) >> 1);
+	sb_info[3] =
+		 (t3p  &   0x1);
+
+	/* encode bursts */
+	gsm0503_sch_encode(burst, sb_info);
+
+	/* compose burst */
+	memset(bits, 0, 3);
+	memcpy(bits + 3, burst, 39);
+	memcpy(bits + 42, _sched_sch_train, 64);
+	memcpy(bits + 106, burst + 39, 39);
+	memset(bits + 145, 0, 3);
+
+	if (nbits)
+		*nbits = GSM_BURST_LEN;
+
+	return bits;
+}
diff --git a/src/osmo-bts-trx/sched_lchan_pdtch.c b/src/osmo-bts-trx/sched_lchan_pdtch.c
new file mode 100644
index 0000000..8b39797
--- /dev/null
+++ b/src/osmo-bts-trx/sched_lchan_pdtch.c
@@ -0,0 +1,249 @@
+/*
+ * (C) 2013 by Andreas Eversberg <jolly at eversberg.eu>
+ * (C) 2015-2017 by Harald Welte <laforge at gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/coding/gsm0503_coding.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/scheduler.h>
+#include <osmo-bts/scheduler_backend.h>
+
+#include <sched_utils.h>
+
+/* Maximum size of a EGPRS message in bytes */
+#define EGPRS_0503_MAX_BYTES	155
+
+/*! \brief a single PDTCH burst was received by the PHY, process it */
+int rx_pdtch_fn(struct l1sched_trx *l1t, enum trx_chan_type chan,
+		uint8_t bid, const struct trx_ul_burst_ind *bi)
+{
+	struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, bi->tn);
+	struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
+	sbit_t *burst, **bursts_p = &chan_state->ul_bursts;
+	uint32_t *first_fn = &chan_state->ul_first_fn;
+	uint8_t *mask = &chan_state->ul_mask;
+	float *rssi_sum = &chan_state->rssi_sum;
+	uint8_t *rssi_num = &chan_state->rssi_num;
+	int32_t *toa256_sum = &chan_state->toa256_sum;
+	uint8_t *toa_num = &chan_state->toa_num;
+	int32_t *ci_cb_sum = &chan_state->ci_cb_sum;
+	uint8_t *ci_cb_num = &chan_state->ci_cb_num;
+	uint8_t l2[EGPRS_0503_MAX_BYTES];
+	int n_errors = 0;
+	int n_bursts_bits = 0;
+	int n_bits_total = 0;
+	int16_t lqual_cb;
+	uint16_t ber10k;
+	int rc;
+
+	LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
+		"Received PDTCH bid=%u\n", bid);
+
+	/* allocate burst memory, if not already */
+	if (!*bursts_p) {
+		*bursts_p = talloc_zero_size(tall_bts_ctx,
+					     GSM0503_EGPRS_BURSTS_NBITS);
+		if (!*bursts_p)
+			return -ENOMEM;
+	}
+
+	/* clear burst */
+	if (bid == 0) {
+		memset(*bursts_p, 0, GSM0503_EGPRS_BURSTS_NBITS);
+		*mask = 0x0;
+		*first_fn = bi->fn;
+		*rssi_sum = 0;
+		*rssi_num = 0;
+		*toa256_sum = 0;
+		*toa_num = 0;
+		*ci_cb_sum = 0;
+		*ci_cb_num = 0;
+	}
+
+	/* update mask + rssi */
+	*mask |= (1 << bid);
+	*rssi_sum += bi->rssi;
+	(*rssi_num)++;
+	*toa256_sum += bi->toa256;
+	(*toa_num)++;
+
+	/* C/I: Carrier-to-Interference ratio (in centiBels) */
+	if (bi->flags & TRX_BI_F_CI_CB) {
+		*ci_cb_sum += bi->ci_cb;
+		(*ci_cb_num)++;
+	}
+
+	/* copy burst to buffer of 4 bursts */
+	if (bi->burst_len == EGPRS_BURST_LEN) {
+		burst = *bursts_p + bid * 348;
+		memcpy(burst, bi->burst + 9, 174);
+		memcpy(burst + 174, bi->burst + 261, 174);
+		n_bursts_bits = GSM0503_EGPRS_BURSTS_NBITS;
+	} else {
+		burst = *bursts_p + bid * 116;
+		memcpy(burst, bi->burst + 3, 58);
+		memcpy(burst + 58, bi->burst + 87, 58);
+		n_bursts_bits = GSM0503_GPRS_BURSTS_NBITS;
+	}
+
+	/* wait until complete set of bursts */
+	if (bid != 3)
+		return 0;
+
+	/* check for complete set of bursts */
+	if ((*mask & 0xf) != 0xf) {
+		LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
+			"Received incomplete frame (%u/%u)\n",
+			bi->fn % l1ts->mf_period, l1ts->mf_period);
+	}
+	*mask = 0x0;
+
+	/*
+	 * Attempt to decode EGPRS bursts first. For 8-PSK EGPRS this is all we
+	 * do. Attempt GPRS decoding on EGPRS failure. If the burst is GPRS,
+	 * then we incur decoding overhead of 31 bits on the Type 3 EGPRS
+	 * header, which is tolerable.
+	 */
+	rc = gsm0503_pdtch_egprs_decode(l2, *bursts_p, n_bursts_bits,
+				NULL, &n_errors, &n_bits_total);
+
+	if ((bi->burst_len == GSM_BURST_LEN) && (rc < 0)) {
+		rc = gsm0503_pdtch_decode(l2, *bursts_p, NULL,
+				  &n_errors, &n_bits_total);
+	}
+
+	if (rc <= 0) {
+		LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
+			"Received bad PDTCH (%u/%u)\n",
+			bi->fn % l1ts->mf_period, l1ts->mf_period);
+		return 0;
+	}
+
+	lqual_cb = *ci_cb_num ? (*ci_cb_sum / *ci_cb_num) : 0;
+	ber10k = compute_ber10k(n_bits_total, n_errors);
+	return _sched_compose_ph_data_ind(l1t, bi->tn,
+					  *first_fn, chan, l2, rc,
+					  *rssi_sum / *rssi_num,
+					  *toa256_sum / *toa_num,
+					  lqual_cb, ber10k,
+					  PRES_INFO_BOTH);
+}
+
+/* obtain a to-be-transmitted PDTCH (packet data) burst */
+ubit_t *tx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+		    enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+	struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+	struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn];
+	struct msgb *msg = NULL; /* make GCC happy */
+	ubit_t *burst, **bursts_p = &l1ts->chan_state[chan].dl_bursts;
+	enum trx_burst_type *burst_type = &l1ts->chan_state[chan].dl_burst_type;
+	static ubit_t bits[EGPRS_BURST_LEN];
+	int rc = 0;
+
+	/* send burst, if we already got a frame */
+	if (bid > 0) {
+		if (!*bursts_p)
+			return NULL;
+		goto send_burst;
+	}
+
+	/* get mac block from queue */
+	msg = _sched_dequeue_prim(l1t, tn, fn, chan);
+	if (msg)
+		goto got_msg;
+
+	LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No prim for transmit.\n");
+
+no_msg:
+	/* free burst memory */
+	if (*bursts_p) {
+		talloc_free(*bursts_p);
+		*bursts_p = NULL;
+	}
+	return NULL;
+
+got_msg:
+	/* BURST BYPASS */
+
+	/* allocate burst memory, if not already */
+	if (!*bursts_p) {
+		*bursts_p = talloc_zero_size(tall_bts_ctx,
+					     GSM0503_EGPRS_BURSTS_NBITS);
+		if (!*bursts_p)
+			return NULL;
+	}
+
+	/* encode bursts */
+	rc = gsm0503_pdtch_egprs_encode(*bursts_p, msg->l2h, msg->tail - msg->l2h);
+	if (rc < 0)
+		rc = gsm0503_pdtch_encode(*bursts_p, msg->l2h, msg->tail - msg->l2h);
+
+	/* check validity of message */
+	if (rc < 0) {
+		LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim invalid length, please FIX! "
+			"(len=%ld)\n", (long)(msg->tail - msg->l2h));
+		/* free message */
+		msgb_free(msg);
+		goto no_msg;
+	} else if (rc == GSM0503_EGPRS_BURSTS_NBITS) {
+		*burst_type = TRX_BURST_8PSK;
+	} else {
+		*burst_type = TRX_BURST_GMSK;
+	}
+
+	/* free message */
+	msgb_free(msg);
+
+send_burst:
+	/* compose burst */
+	if (*burst_type == TRX_BURST_8PSK) {
+		burst = *bursts_p + bid * 348;
+		memset(bits, 1, 9);
+		memcpy(bits + 9, burst, 174);
+		memcpy(bits + 183, _sched_egprs_tsc[gsm_ts_tsc(ts)], 78);
+		memcpy(bits + 261, burst + 174, 174);
+		memset(bits + 435, 1, 9);
+
+		if (nbits)
+			*nbits = EGPRS_BURST_LEN;
+	} else {
+		burst = *bursts_p + bid * 116;
+		memset(bits, 0, 3);
+		memcpy(bits + 3, burst, 58);
+		memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26);
+		memcpy(bits + 87, burst + 58, 58);
+		memset(bits + 145, 0, 3);
+
+		if (nbits)
+			*nbits = GSM_BURST_LEN;
+	}
+
+	LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid);
+
+	return bits;
+}
diff --git a/src/osmo-bts-trx/sched_lchan_rach.c b/src/osmo-bts-trx/sched_lchan_rach.c
new file mode 100644
index 0000000..fa50f14
--- /dev/null
+++ b/src/osmo-bts-trx/sched_lchan_rach.c
@@ -0,0 +1,212 @@
+/*
+ * (C) 2013 by Andreas Eversberg <jolly at eversberg.eu>
+ * (C) 2015-2017 by Harald Welte <laforge at gnumonks.org>
+ * (C) 2019 by Vadim Yanitskiy <axilirator at gmail.com>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <limits.h>
+#include <errno.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/coding/gsm0503_coding.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/scheduler.h>
+#include <osmo-bts/scheduler_backend.h>
+
+#include <sched_utils.h>
+
+/* 3GPP TS 05.02, section 5.2.7 */
+#define RACH_EXT_TAIL_LEN	8
+#define RACH_SYNCH_SEQ_LEN	41
+
+enum rach_synch_seq_t {
+	RACH_SYNCH_SEQ_UNKNOWN = -1,
+	RACH_SYNCH_SEQ_TS0, /* GSM, GMSK (default) */
+	RACH_SYNCH_SEQ_TS1, /* EGPRS, 8-PSK */
+	RACH_SYNCH_SEQ_TS2, /* EGPRS, GMSK */
+	RACH_SYNCH_SEQ_NUM
+};
+
+static struct value_string rach_synch_seq_names[] = {
+	{ RACH_SYNCH_SEQ_UNKNOWN,	"UNKNOWN" },
+	{ RACH_SYNCH_SEQ_TS0,		"TS0: GSM, GMSK" },
+	{ RACH_SYNCH_SEQ_TS1,		"TS1: EGPRS, 8-PSK" },
+	{ RACH_SYNCH_SEQ_TS2,		"TS2: EGPRS, GMSK" },
+	{ 0, NULL },
+};
+
+static enum rach_synch_seq_t rach_get_synch_seq(sbit_t *bits, int *best_score)
+{
+	sbit_t *synch_seq_burst = bits + RACH_EXT_TAIL_LEN;
+	enum rach_synch_seq_t seq = RACH_SYNCH_SEQ_TS0;
+	int score[RACH_SYNCH_SEQ_NUM] = { 0 };
+	int max_score = INT_MIN;
+	int i, j;
+
+	/* 3GPP TS 05.02, section 5.2.7 "Access burst (AB)", synch. sequence bits */
+	static const char synch_seq_ref[RACH_SYNCH_SEQ_NUM][RACH_SYNCH_SEQ_LEN] = {
+		[RACH_SYNCH_SEQ_TS0] = "01001011011111111001100110101010001111000",
+		[RACH_SYNCH_SEQ_TS1] = "01010100111110001000011000101111001001101",
+		[RACH_SYNCH_SEQ_TS2] = "11101111001001110101011000001101101110111",
+	};
+
+	/* Get a multiplier for j-th bit of i-th synch. sequence */
+#define RACH_SYNCH_SEQ_MULT \
+	(synch_seq_ref[i][j] == '1' ? -1 : 1)
+
+	/* For each synch. sequence, count the bit match score. Since we deal with
+	 * soft-bits (-127...127), we sum the absolute values of matching ones,
+	 * and subtract the absolute values of different ones, so the resulting
+	 * score is more accurate than it could be with hard-bits. */
+	for (i = 0; i < RACH_SYNCH_SEQ_NUM; i++) {
+		for (j = 0; j < RACH_SYNCH_SEQ_LEN; j++)
+			score[i] += RACH_SYNCH_SEQ_MULT * synch_seq_burst[j];
+
+		/* Keep the maximum value updated */
+		if (score[i] > max_score) {
+			max_score = score[i];
+			seq = i;
+		}
+	}
+
+	/* Calculate an approximate level of our confidence */
+	if (best_score != NULL)
+		*best_score = max_score;
+
+	/* At least 1/3 of a synch. sequence shall match */
+	if (max_score < (127 * RACH_SYNCH_SEQ_LEN / 3))
+		return RACH_SYNCH_SEQ_UNKNOWN;
+
+	return seq;
+}
+
+int rx_rach_fn(struct l1sched_trx *l1t, enum trx_chan_type chan,
+	       uint8_t bid, const struct trx_ul_burst_ind *bi)
+{
+	struct osmo_phsap_prim l1sap;
+	int n_errors = 0;
+	int n_bits_total = 0;
+	uint16_t ra11;
+	uint8_t ra;
+	int rc;
+
+	/* TSC (Training Sequence Code) is an optional parameter of the UL burst
+	 * indication. We need this information in order to decide whether an
+	 * Access Burst is 11-bit encoded or not (see OS#1854). If this information
+	 * is absent, we try to correlate the received synch. sequence with the
+	 * known ones (3GPP TS 05.02, section 5.2.7), and fall-back to the default
+	 * TS0 if it fails. */
+	enum rach_synch_seq_t synch_seq = RACH_SYNCH_SEQ_TS0;
+	int best_score = 127 * RACH_SYNCH_SEQ_LEN;
+
+	/* If logical channel is not either of RACH, PDTCH or PTCCH, this is a
+	 * handover Access Burst, which is always encoded as 8-bit and shall
+	 * contain the generic training sequence (TS0). */
+	if (chan == TRXC_RACH || chan == TRXC_PDTCH || chan == TRXC_PTCCH) {
+		if (bi->flags & TRX_BI_F_TS_INFO)
+			synch_seq = (enum rach_synch_seq_t) bi->tsc;
+		else
+			synch_seq = rach_get_synch_seq((sbit_t *) bi->burst, &best_score);
+	}
+
+	LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
+	       "Received%s RACH (%s): rssi=%d toa256=%d",
+	       (chan != TRXC_RACH) ? " handover" : "",
+	       get_value_string(rach_synch_seq_names, synch_seq),
+	       bi->rssi, bi->toa256);
+	if (bi->flags & TRX_BI_F_CI_CB)
+		LOGPC(DL1P, LOGL_DEBUG, " C/I=%d cB", bi->ci_cb);
+	else
+		LOGPC(DL1P, LOGL_DEBUG, " match=%.1f%%",
+		      best_score * 100.0 / (127 * RACH_SYNCH_SEQ_LEN));
+	LOGPC(DL1P, LOGL_DEBUG, "\n");
+
+	/* Compose a new L1SAP primitive */
+	memset(&l1sap, 0x00, sizeof(l1sap));
+	osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, NULL);
+	l1sap.u.rach_ind.chan_nr = trx_chan_desc[chan].chan_nr | bi->tn;
+	l1sap.u.rach_ind.acc_delay = (bi->toa256 >= 0) ? bi->toa256 / 256 : 0;
+	l1sap.u.rach_ind.acc_delay_256bits = bi->toa256;
+	l1sap.u.rach_ind.rssi = bi->rssi;
+	l1sap.u.rach_ind.fn = bi->fn;
+
+	/* Link quality is defined by C/I (Carrier-to-Interference ratio),
+	 * which has optional presence. If it's absent, report the
+	 * minimum acceptable value to pass L1SAP checks. */
+	if (bi->flags & TRX_BI_F_CI_CB)
+		l1sap.u.rach_ind.lqual_cb = bi->ci_cb;
+	else
+		l1sap.u.rach_ind.lqual_cb = l1t->trx->bts->min_qual_rach;
+
+	/* Decode RACH depending on its synch. sequence */
+	switch (synch_seq) {
+	case RACH_SYNCH_SEQ_TS1:
+	case RACH_SYNCH_SEQ_TS2:
+		rc = gsm0503_rach_ext_decode_ber(&ra11, bi->burst + RACH_EXT_TAIL_LEN + RACH_SYNCH_SEQ_LEN,
+						 l1t->trx->bts->bsic, &n_errors, &n_bits_total);
+		if (rc) {
+			LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
+			       "Received bad Access Burst\n");
+			return 0;
+		}
+
+		if (synch_seq == RACH_SYNCH_SEQ_TS1)
+			l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_1;
+		else
+			l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_2;
+
+		l1sap.u.rach_ind.is_11bit = 1;
+		l1sap.u.rach_ind.ra = ra11;
+		break;
+
+	case RACH_SYNCH_SEQ_TS0:
+	default:
+		/* Fall-back to the default TS0 if needed */
+		if (synch_seq != RACH_SYNCH_SEQ_TS0) {
+			LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
+			       "Falling-back to the default TS0\n");
+			synch_seq = RACH_SYNCH_SEQ_TS0;
+		}
+
+		rc = gsm0503_rach_decode_ber(&ra, bi->burst + RACH_EXT_TAIL_LEN + RACH_SYNCH_SEQ_LEN,
+					     l1t->trx->bts->bsic, &n_errors, &n_bits_total);
+		if (rc) {
+			LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
+			       "Received bad Access Burst\n");
+			return 0;
+		}
+
+		l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_0;
+		l1sap.u.rach_ind.is_11bit = 0;
+		l1sap.u.rach_ind.ra = ra;
+		break;
+	}
+
+	l1sap.u.rach_ind.ber10k = compute_ber10k(n_bits_total, n_errors);
+
+	/* forward primitive */
+	l1sap_up(l1t->trx, &l1sap);
+
+	return 0;
+}
diff --git a/src/osmo-bts-trx/sched_lchan_tchf.c b/src/osmo-bts-trx/sched_lchan_tchf.c
new file mode 100644
index 0000000..fb11f45
--- /dev/null
+++ b/src/osmo-bts-trx/sched_lchan_tchf.c
@@ -0,0 +1,570 @@
+/*
+ * (C) 2013 by Andreas Eversberg <jolly at eversberg.eu>
+ * (C) 2015-2017 by Harald Welte <laforge at gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/gsm/gsm0502.h>
+
+#include <osmocom/codec/codec.h>
+#include <osmocom/codec/ecu.h>
+
+#include <osmocom/coding/gsm0503_coding.h>
+#include <osmocom/coding/gsm0503_amr_dtx.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/scheduler.h>
+#include <osmo-bts/scheduler_backend.h>
+#include <osmo-bts/msg_utils.h>
+
+#include <sched_utils.h>
+#include <loops.h>
+
+/*! \brief a single TCH/F burst was received by the PHY, process it */
+int rx_tchf_fn(struct l1sched_trx *l1t, enum trx_chan_type chan,
+	       uint8_t bid, const struct trx_ul_burst_ind *bi)
+{
+	struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, bi->tn);
+	struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
+	sbit_t *burst, **bursts_p = &chan_state->ul_bursts;
+	uint8_t *mask = &chan_state->ul_mask;
+	uint8_t rsl_cmode = chan_state->rsl_cmode;
+	uint8_t tch_mode = chan_state->tch_mode;
+	uint8_t tch_data[128]; /* just to be safe */
+	int rc, amr = 0;
+	int n_errors = 0;
+	int n_bits_total = 0;
+	bool bfi_flag = false;
+	struct gsm_lchan *lchan =
+		get_lchan_by_chan_nr(l1t->trx, trx_chan_desc[chan].chan_nr | bi->tn);
+	unsigned int fn_begin;
+	uint16_t ber10k;
+	uint8_t is_sub = 0;
+	uint8_t ft;
+
+	/* If handover RACH detection is turned on, treat this burst as an Access Burst.
+	 * Handle NOPE.ind as usually to ensure proper Uplink measurement reporting. */
+	if (chan_state->ho_rach_detect == 1 && ~bi->flags & TRX_BI_F_NOPE_IND)
+		return rx_rach_fn(l1t, chan, bid, bi);
+
+	LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
+	       "Received TCH/F, bid=%u\n", bid);
+
+	/* allocate burst memory, if not already */
+	if (!*bursts_p) {
+		*bursts_p = talloc_zero_size(tall_bts_ctx, 928);
+		if (!*bursts_p)
+			return -ENOMEM;
+	}
+
+	/* clear burst */
+	if (bid == 0) {
+		memset(*bursts_p + 464, 0, 464);
+		*mask = 0x0;
+	}
+
+	/* update mask */
+	*mask |= (1 << bid);
+
+	/* copy burst to end of buffer of 8 bursts */
+	burst = *bursts_p + bid * 116 + 464;
+	if (bi->burst_len > 0) {
+		memcpy(burst, bi->burst + 3, 58);
+		memcpy(burst + 58, bi->burst + 87, 58);
+	} else
+		memset(burst, 0, 116);
+
+	/* wait until complete set of bursts */
+	if (bid != 3)
+		return 0;
+
+	/* check for complete set of bursts */
+	if ((*mask & 0xf) != 0xf) {
+		LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
+			"Received incomplete frame (%u/%u)\n",
+			bi->fn % l1ts->mf_period, l1ts->mf_period);
+	}
+	*mask = 0x0;
+
+	/* decode
+	 * also shift buffer by 4 bursts for interleaving */
+	switch ((rsl_cmode != RSL_CMOD_SPD_SPEECH) ? GSM48_CMODE_SPEECH_V1
+								: tch_mode) {
+	case GSM48_CMODE_SPEECH_V1: /* FR */
+		rc = gsm0503_tch_fr_decode(tch_data, *bursts_p, 1, 0, &n_errors, &n_bits_total);
+		if (rc >= 0)
+			lchan_set_marker(osmo_fr_check_sid(tch_data, rc), lchan); /* DTXu */
+		break;
+	case GSM48_CMODE_SPEECH_EFR: /* EFR */
+		rc = gsm0503_tch_fr_decode(tch_data, *bursts_p, 1, 1, &n_errors, &n_bits_total);
+		break;
+	case GSM48_CMODE_SPEECH_AMR: /* AMR */
+		/* the first FN 0,8,17 defines that CMI is included in frame,
+		 * the first FN 4,13,21 defines that CMR is included in frame.
+		 * NOTE: A frame ends 7 FN after start.
+		 */
+
+		/* The AFS_ONSET frame itself does not result into an RTP frame
+		 * since it only contains a recognition pattern that marks the
+		 * end of the DTX interval. To mark the end of the DTX interval
+		 * in the RTP stream as well, the voice frame after the
+		 * AFS_ONSET frame is used. */
+		if (chan_state->amr_last_dtx == AFS_ONSET)
+			lchan_set_marker(false, lchan);
+
+		/* we store tch_data + 2 header bytes, the amr variable set to
+		 * 2 will allow us to skip the first 2 bytes in case we did
+		 * receive an FACCH frame instead of a voice frame (we do not
+		 * know this before we actually decode the frame) */
+		amr = 2;
+		rc = gsm0503_tch_afs_decode_dtx(tch_data + amr, *bursts_p,
+			(((bi->fn + 26 - 7) % 26) >> 2) & 1, chan_state->codec,
+			chan_state->codecs, &chan_state->ul_ft,
+			&chan_state->ul_cmr, &n_errors, &n_bits_total, &chan_state->amr_last_dtx);
+
+		/* Tag all frames that are not regular AMR voice frames as
+		 * SUB-Frames */
+		if (chan_state->amr_last_dtx != AMR_OTHER) {
+			LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
+			       "Received AMR SID frame: %s\n",
+			       gsm0503_amr_dtx_frame_name(chan_state->amr_last_dtx));
+			is_sub = 1;
+		}
+
+		/* The occurrence of the following frames indicates that we
+		 * are either at the beginning or in the middle of a talk
+		 * spurt. We update the SID status accordingly, but we do
+		 * not want the marker to be set, since this must only
+		 * happen when the talk spurt is over (see above) */
+		switch (chan_state->amr_last_dtx) {
+		case AFS_SID_FIRST:
+		case AFS_SID_UPDATE:
+		case AFS_SID_UPDATE_CN:
+			lchan_set_marker(true, lchan);
+			lchan->rtp_tx_marker = false;
+			break;
+		}
+
+		if (rc)
+			trx_loop_amr_input(l1t,
+				trx_chan_desc[chan].chan_nr | bi->tn, chan_state,
+				n_errors, n_bits_total);
+		/* only good speech frames get rtp header */
+		if (rc != GSM_MACBLOCK_LEN && rc >= 4) {
+			if (chan_state->amr_last_dtx == AMR_OTHER) {
+				ft = chan_state->codec[chan_state->ul_cmr];
+			} else {
+				/* SID frames will always get Frame Type Index 8 (AMR_SID) */
+				ft = AMR_SID;
+			}
+			rc = osmo_amr_rtp_enc(tch_data,
+				chan_state->codec[chan_state->ul_cmr],
+			        ft, AMR_GOOD);
+		}
+
+		break;
+	default:
+		LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn,
+			"TCH mode %u invalid, please fix!\n",
+			tch_mode);
+		return -EINVAL;
+	}
+	memcpy(*bursts_p, *bursts_p + 464, 464);
+
+	/* Check if the frame is bad */
+	if (rc < 0) {
+		LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
+			"Received bad data (%u/%u)\n",
+			bi->fn % l1ts->mf_period, l1ts->mf_period);
+		bfi_flag = true;
+	} else if (rc < 4) {
+		LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
+			"Received bad data (%u/%u) with invalid codec mode %d\n",
+			bi->fn % l1ts->mf_period, l1ts->mf_period, rc);
+		bfi_flag = true;
+	}
+
+	if (rc != GSM_MACBLOCK_LEN && lchan->ecu_state)
+		osmo_ecu_frame_in(lchan->ecu_state, bfi_flag, tch_data, rc);
+
+	ber10k = compute_ber10k(n_bits_total, n_errors);
+	if (bfi_flag)
+		goto bfi;
+
+	/* FACCH */
+	if (rc == GSM_MACBLOCK_LEN) {
+		fn_begin = gsm0502_fn_remap(bi->fn, FN_REMAP_FACCH_F);
+		_sched_compose_ph_data_ind(l1t, bi->tn, fn_begin, chan,
+			tch_data + amr, GSM_MACBLOCK_LEN,
+			/* FIXME: AVG RSSI and ToA256 */
+			bi->rssi, bi->toa256,
+			0 /* FIXME: AVG C/I */,
+			ber10k, PRES_INFO_UNKNOWN);
+bfi:
+		if (rsl_cmode == RSL_CMOD_SPD_SPEECH) {
+			/* indicate bad frame */
+			if (lchan->tch.dtx.ul_sid) {
+				/* DTXu: pause in progress. Push empty payload to upper layers */
+				rc = 0;
+				goto compose_l1sap;
+			}
+
+			/* If there is an ECU active on this channel, use its output */
+			if (lchan->ecu_state) {
+				rc = osmo_ecu_frame_out(lchan->ecu_state, tch_data);
+				if (rc >= 0) /* Otherwise we send a BFI */
+					goto compose_l1sap;
+			}
+
+			switch (tch_mode) {
+			case GSM48_CMODE_SPEECH_V1: /* FR */
+				memset(tch_data, 0, GSM_FR_BYTES);
+				tch_data[0] = 0xd0;
+				rc = GSM_FR_BYTES;
+				break;
+			case GSM48_CMODE_SPEECH_EFR: /* EFR */
+				memset(tch_data, 0, GSM_EFR_BYTES);
+				tch_data[0] = 0xc0;
+				rc = GSM_EFR_BYTES;
+				break;
+			case GSM48_CMODE_SPEECH_AMR: /* AMR */
+				rc = osmo_amr_rtp_enc(tch_data,
+					chan_state->codec[chan_state->dl_cmr],
+					chan_state->codec[chan_state->dl_ft],
+					AMR_BAD);
+				if (rc < 2) {
+					LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn,
+					       "Failed to encode AMR_BAD frame (rc=%d), "
+					       "not sending BFI\n", rc);
+					return -EINVAL;
+				}
+				memset(tch_data + 2, 0, rc - 2);
+				break;
+			default:
+				LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn,
+					"TCH mode %u invalid, please fix!\n", tch_mode);
+				return -EINVAL;
+			}
+		}
+	}
+
+	if (rsl_cmode != RSL_CMOD_SPD_SPEECH)
+		return 0;
+
+	/* TCH or BFI */
+compose_l1sap:
+	fn_begin = gsm0502_fn_remap(bi->fn, FN_REMAP_TCH_F);
+	return _sched_compose_tch_ind(l1t, bi->tn, fn_begin, chan,
+				      tch_data, rc, bi->toa256, ber10k, bi->rssi, is_sub);
+}
+
+/* common section for generation of TCH bursts (TCH/H and TCH/F).
+ * FIXME: this function is over-complicated, refactor / get rid of it. */
+void tx_tch_common(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+		   enum trx_chan_type chan, uint8_t bid,
+		   struct msgb **_msg_tch, struct msgb **_msg_facch)
+{
+	struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+	struct msgb *msg1, *msg2, *msg_tch = NULL, *msg_facch = NULL;
+	struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
+	uint8_t rsl_cmode = chan_state->rsl_cmode;
+	uint8_t tch_mode = chan_state->tch_mode;
+	struct osmo_phsap_prim *l1sap;
+	int32_t *toa256_sum = &chan_state->toa256_sum;
+	uint8_t *toa_num = &chan_state->toa_num;
+	int16_t toa256;
+
+	/* handle loss detection of received TCH frames */
+	if (rsl_cmode == RSL_CMOD_SPD_SPEECH
+	    && ++(chan_state->lost_frames) > 5) {
+		uint8_t tch_data[GSM_FR_BYTES];
+		int len;
+
+		LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn,
+			"Missing TCH bursts detected, sending BFI\n");
+
+		/* indicate bad frame */
+		switch (tch_mode) {
+		case GSM48_CMODE_SPEECH_V1: /* FR / HR */
+			if (chan != TRXC_TCHF) { /* HR */
+				tch_data[0] = 0x70; /* F = 0, FT = 111 */
+				memset(tch_data + 1, 0, 14);
+				len = 15;
+				break;
+			}
+			memset(tch_data, 0, GSM_FR_BYTES);
+			len = GSM_FR_BYTES;
+			break;
+		case GSM48_CMODE_SPEECH_EFR: /* EFR */
+			if (chan != TRXC_TCHF)
+				goto inval_mode1;
+			memset(tch_data, 0, GSM_EFR_BYTES);
+			len = GSM_EFR_BYTES;
+			break;
+		case GSM48_CMODE_SPEECH_AMR: /* AMR */
+			len = osmo_amr_rtp_enc(tch_data,
+				chan_state->codec[chan_state->dl_cmr],
+				chan_state->codec[chan_state->dl_ft], AMR_BAD);
+			if (len < 2) {
+				LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn,
+				       "Failed to encode AMR_BAD frame (rc=%d), "
+				       "not sending BFI\n", len);
+				return;
+			}
+			memset(tch_data + 2, 0, len - 2);
+			break;
+		default:
+inval_mode1:
+			LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode invalid, please fix!\n");
+			len = 0;
+		}
+
+		if (len) {
+			if (*toa_num == 0)
+				toa256 = 0;
+			else
+				toa256 = *toa256_sum / *toa_num;
+
+			/* Note: RSSI is set to 0 to indicate to the higher
+			 * layers that this is a faked tch_ind */
+			_sched_compose_tch_ind(l1t, tn, fn, chan, tch_data, len, toa256, 10000, 0, 0);
+		}
+	}
+
+	/* get frame and unlink from queue */
+	msg1 = _sched_dequeue_prim(l1t, tn, fn, chan);
+	msg2 = _sched_dequeue_prim(l1t, tn, fn, chan);
+	if (msg1) {
+		l1sap = msgb_l1sap_prim(msg1);
+		if (l1sap->oph.primitive == PRIM_TCH) {
+			msg_tch = msg1;
+			if (msg2) {
+				l1sap = msgb_l1sap_prim(msg2);
+				if (l1sap->oph.primitive == PRIM_TCH) {
+					LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn,
+						"TCH twice, please FIX!\n");
+					msgb_free(msg2);
+				} else
+					msg_facch = msg2;
+			}
+		} else {
+			msg_facch = msg1;
+			if (msg2) {
+				l1sap = msgb_l1sap_prim(msg2);
+				if (l1sap->oph.primitive != PRIM_TCH) {
+					LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn,
+						"FACCH twice, please FIX!\n");
+					msgb_free(msg2);
+				} else
+					msg_tch = msg2;
+			}
+		}
+	} else if (msg2) {
+		l1sap = msgb_l1sap_prim(msg2);
+		if (l1sap->oph.primitive == PRIM_TCH)
+			msg_tch = msg2;
+		else
+			msg_facch = msg2;
+	}
+
+	/* check validity of message */
+	if (msg_facch && msgb_l2len(msg_facch) != GSM_MACBLOCK_LEN) {
+		LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim not 23 bytes, please FIX! "
+			"(len=%d)\n", msgb_l2len(msg_facch));
+		/* free message */
+		msgb_free(msg_facch);
+		msg_facch = NULL;
+	}
+
+	/* check validity of message, get AMR ft and cmr */
+	if (!msg_facch && msg_tch) {
+		int len;
+		uint8_t cmr_codec;
+		int cmr, ft, i;
+		enum osmo_amr_type ft_codec;
+		enum osmo_amr_quality bfi;
+		int8_t sti, cmi;
+
+		if (rsl_cmode != RSL_CMOD_SPD_SPEECH) {
+			LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Dropping speech frame, "
+				"because we are not in speech mode\n");
+			goto free_bad_msg;
+		}
+
+		switch (tch_mode) {
+		case GSM48_CMODE_SPEECH_V1: /* FR / HR */
+			if (chan != TRXC_TCHF) /* HR */
+				len = 15;
+			else
+				len = GSM_FR_BYTES;
+			break;
+		case GSM48_CMODE_SPEECH_EFR: /* EFR */
+			if (chan != TRXC_TCHF)
+				goto inval_mode2;
+			len = GSM_EFR_BYTES;
+			break;
+		case GSM48_CMODE_SPEECH_AMR: /* AMR */
+			len = osmo_amr_rtp_dec(msg_tch->l2h, msgb_l2len(msg_tch),
+					       &cmr_codec, &cmi, &ft_codec,
+					       &bfi, &sti);
+			cmr = -1;
+			ft = -1;
+			for (i = 0; i < chan_state->codecs; i++) {
+				if (chan_state->codec[i] == cmr_codec)
+					cmr = i;
+				if (chan_state->codec[i] == ft_codec)
+					ft = i;
+			}
+			if (cmr >= 0) { /* new request */
+				chan_state->dl_cmr = cmr;
+				/* disable AMR loop */
+				trx_loop_amr_set(chan_state, 0);
+			} else {
+				/* enable AMR loop */
+				trx_loop_amr_set(chan_state, 1);
+			}
+			if (ft < 0) {
+				LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn,
+					"Codec (FT = %d) of RTP frame not in list\n", ft_codec);
+				goto free_bad_msg;
+			}
+			if (fn_is_codec_mode_request(fn) && chan_state->dl_ft != ft) {
+				LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Codec (FT = %d) "
+					" of RTP cannot be changed now, but in next frame\n", ft_codec);
+				goto free_bad_msg;
+			}
+			chan_state->dl_ft = ft;
+			if (bfi == AMR_BAD) {
+				LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn,
+					"Transmitting 'bad AMR frame'\n");
+				goto free_bad_msg;
+			}
+			break;
+		default:
+inval_mode2:
+			LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode invalid, please fix!\n");
+			goto free_bad_msg;
+		}
+		if (len < 0) {
+			LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot send invalid AMR payload\n");
+			goto free_bad_msg;
+		}
+		if (msgb_l2len(msg_tch) != len) {
+			LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot send payload with "
+				"invalid length! (expecting %d, received %d)\n",
+				len, msgb_l2len(msg_tch));
+free_bad_msg:
+			/* free message */
+			msgb_free(msg_tch);
+			msg_tch = NULL;
+			goto send_frame;
+		}
+	}
+
+send_frame:
+	*_msg_tch = msg_tch;
+	*_msg_facch = msg_facch;
+}
+
+/* obtain a to-be-transmitted TCH/F (Full Traffic Channel) burst */
+ubit_t *tx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+		   enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+	struct msgb *msg_tch = NULL, *msg_facch = NULL;
+	struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+	struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn];
+	struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
+	uint8_t tch_mode = chan_state->tch_mode;
+	ubit_t *burst, **bursts_p = &chan_state->dl_bursts;
+	static ubit_t bits[GSM_BURST_LEN];
+
+	/* send burst, if we already got a frame */
+	if (bid > 0) {
+		if (!*bursts_p)
+			return NULL;
+		goto send_burst;
+	}
+
+	tx_tch_common(l1t, tn, fn, chan, bid, &msg_tch, &msg_facch);
+
+	/* BURST BYPASS */
+
+	/* allocate burst memory, if not already,
+	 * otherwise shift buffer by 4 bursts for interleaving */
+	if (!*bursts_p) {
+		*bursts_p = talloc_zero_size(tall_bts_ctx, 928);
+		if (!*bursts_p)
+			return NULL;
+	} else {
+		memcpy(*bursts_p, *bursts_p + 464, 464);
+		memset(*bursts_p + 464, 0, 464);
+	}
+
+	/* no message at all */
+	if (!msg_tch && !msg_facch) {
+		LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No TCH or FACCH prim for transmit.\n");
+		goto send_burst;
+	}
+
+	/* encode bursts (prioritize FACCH) */
+	if (msg_facch)
+		gsm0503_tch_fr_encode(*bursts_p, msg_facch->l2h, msgb_l2len(msg_facch),
+			1);
+	else if (tch_mode == GSM48_CMODE_SPEECH_AMR)
+		/* the first FN 4,13,21 defines that CMI is included in frame,
+		 * the first FN 0,8,17 defines that CMR is included in frame.
+		 */
+		gsm0503_tch_afs_encode(*bursts_p, msg_tch->l2h + 2,
+			msgb_l2len(msg_tch) - 2, fn_is_codec_mode_request(fn),
+			chan_state->codec, chan_state->codecs,
+			chan_state->dl_ft,
+			chan_state->dl_cmr);
+	else
+		gsm0503_tch_fr_encode(*bursts_p, msg_tch->l2h, msgb_l2len(msg_tch), 1);
+
+	/* free message */
+	if (msg_tch)
+		msgb_free(msg_tch);
+	if (msg_facch)
+		msgb_free(msg_facch);
+
+send_burst:
+	/* compose burst */
+	burst = *bursts_p + bid * 116;
+	memset(bits, 0, 3);
+	memcpy(bits + 3, burst, 58);
+	memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26);
+	memcpy(bits + 87, burst + 58, 58);
+	memset(bits + 145, 0, 3);
+
+	if (nbits)
+		*nbits = GSM_BURST_LEN;
+
+	LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid);
+
+	return bits;
+}
diff --git a/src/osmo-bts-trx/sched_lchan_tchh.c b/src/osmo-bts-trx/sched_lchan_tchh.c
new file mode 100644
index 0000000..1895038
--- /dev/null
+++ b/src/osmo-bts-trx/sched_lchan_tchh.c
@@ -0,0 +1,408 @@
+/*
+ * (C) 2013 by Andreas Eversberg <jolly at eversberg.eu>
+ * (C) 2015-2017 by Harald Welte <laforge at gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/gsm/gsm0502.h>
+
+#include <osmocom/codec/codec.h>
+#include <osmocom/codec/ecu.h>
+
+#include <osmocom/coding/gsm0503_coding.h>
+#include <osmocom/coding/gsm0503_amr_dtx.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/scheduler.h>
+#include <osmo-bts/scheduler_backend.h>
+#include <osmo-bts/msg_utils.h>
+
+#include <sched_utils.h>
+#include <loops.h>
+
+/*! \brief a single TCH/H burst was received by the PHY, process it */
+int rx_tchh_fn(struct l1sched_trx *l1t, enum trx_chan_type chan,
+	       uint8_t bid, const struct trx_ul_burst_ind *bi)
+{
+	struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, bi->tn);
+	struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
+	sbit_t *burst, **bursts_p = &chan_state->ul_bursts;
+	uint8_t *mask = &chan_state->ul_mask;
+	uint8_t rsl_cmode = chan_state->rsl_cmode;
+	uint8_t tch_mode = chan_state->tch_mode;
+	uint8_t tch_data[128]; /* just to be safe */
+	int rc, amr = 0;
+	int n_errors = 0;
+	int n_bits_total = 0;
+	bool bfi_flag = false;
+	struct gsm_lchan *lchan =
+		get_lchan_by_chan_nr(l1t->trx, trx_chan_desc[chan].chan_nr | bi->tn);
+	/* Note on FN-10: If we are at FN 10, we decoded an even aligned
+	 * TCH/FACCH frame, because our burst buffer carries 6 bursts.
+	 * Even FN ending at: 10,11,19,20,2,3
+	 */
+	int fn_is_odd = (((bi->fn + 26 - 10) % 26) >> 2) & 1;
+	unsigned int fn_begin;
+	uint16_t ber10k;
+	uint8_t is_sub = 0;
+	uint8_t ft;
+
+	/* If handover RACH detection is turned on, treat this burst as an Access Burst.
+	 * Handle NOPE.ind as usually to ensure proper Uplink measurement reporting. */
+	if (chan_state->ho_rach_detect == 1 && ~bi->flags & TRX_BI_F_NOPE_IND)
+		return rx_rach_fn(l1t, chan, bid, bi);
+
+	LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
+		"Received TCH/H, bid=%u\n", bid);
+
+	/* allocate burst memory, if not already */
+	if (!*bursts_p) {
+		*bursts_p = talloc_zero_size(tall_bts_ctx, 696);
+		if (!*bursts_p)
+			return -ENOMEM;
+	}
+
+	/* clear burst */
+	if (bid == 0) {
+		memset(*bursts_p + 464, 0, 232);
+		*mask = 0x0;
+	}
+
+	/* update mask */
+	*mask |= (1 << bid);
+
+	/* copy burst to end of buffer of 6 bursts */
+	burst = *bursts_p + bid * 116 + 464;
+	if (bi->burst_len > 0) {
+		memcpy(burst, bi->burst + 3, 58);
+		memcpy(burst + 58, bi->burst + 87, 58);
+	} else
+		memset(burst, 0, 116);
+
+	/* wait until complete set of bursts */
+	if (bid != 1)
+		return 0;
+
+	/* check for complete set of bursts */
+	if ((*mask & 0x3) != 0x3) {
+		LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
+			"Received incomplete frame (%u/%u)\n",
+			bi->fn % l1ts->mf_period, l1ts->mf_period);
+	}
+	*mask = 0x0;
+
+	/* skip second of two TCH frames of FACCH was received */
+	if (chan_state->ul_ongoing_facch) {
+		chan_state->ul_ongoing_facch = 0;
+		memcpy(*bursts_p, *bursts_p + 232, 232);
+		memcpy(*bursts_p + 232, *bursts_p + 464, 232);
+		ber10k = 0;
+		goto bfi;
+	}
+
+	/* decode
+	 * also shift buffer by 4 bursts for interleaving */
+	switch ((rsl_cmode != RSL_CMOD_SPD_SPEECH) ? GSM48_CMODE_SPEECH_V1
+								: tch_mode) {
+	case GSM48_CMODE_SPEECH_V1: /* HR or signalling */
+		/* Note on FN-10: If we are at FN 10, we decoded an even aligned
+		 * TCH/FACCH frame, because our burst buffer carries 6 bursts.
+		 * Even FN ending at: 10,11,19,20,2,3
+		 */
+		rc = gsm0503_tch_hr_decode(tch_data, *bursts_p,
+			fn_is_odd, &n_errors, &n_bits_total);
+		if (rc >= 0) /* DTXu */
+			lchan_set_marker(osmo_hr_check_sid(tch_data, rc), lchan);
+		break;
+	case GSM48_CMODE_SPEECH_AMR: /* AMR */
+		/* the first FN 0,8,17 or 1,9,18 defines that CMI is included
+		 * in frame, the first FN 4,13,21 or 5,14,22 defines that CMR
+		 * is included in frame.
+		 */
+
+		/* See comment in function rx_tchf_fn() */
+		switch (chan_state->amr_last_dtx) {
+		case AHS_ONSET:
+		case AHS_SID_FIRST_INH:
+		case AHS_SID_UPDATE_INH:
+			lchan_set_marker(false, lchan);
+			break;
+		}
+
+		/* See comment in function rx_tchf_fn() */
+		amr = 2;
+		rc = gsm0503_tch_ahs_decode_dtx(tch_data + amr, *bursts_p,
+			fn_is_odd, fn_is_odd, chan_state->codec,
+			chan_state->codecs, &chan_state->ul_ft,
+			&chan_state->ul_cmr, &n_errors, &n_bits_total, &chan_state->amr_last_dtx);
+
+		/* Tag all frames that are not regular AMR voice frames
+		   as SUB-Frames */
+		if (chan_state->amr_last_dtx != AMR_OTHER) {
+			LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
+			       "Received AMR SID frame: %s\n",
+			       gsm0503_amr_dtx_frame_name(chan_state->amr_last_dtx));
+			is_sub = 1;
+		}
+
+		/* See comment in function rx_tchf_fn() */
+		switch (chan_state->amr_last_dtx) {
+		case AHS_SID_FIRST_P1:
+		case AHS_SID_FIRST_P2:
+		case AHS_SID_UPDATE:
+		case AHS_SID_UPDATE_CN:
+			lchan_set_marker(true, lchan);
+			lchan->rtp_tx_marker = false;
+			break;
+		}
+
+		if (rc)
+			trx_loop_amr_input(l1t,
+				trx_chan_desc[chan].chan_nr | bi->tn, chan_state,
+				n_errors, n_bits_total);
+
+		/* only good speech frames get rtp header */
+		if (rc != GSM_MACBLOCK_LEN && rc >= 4) {
+			if (chan_state->amr_last_dtx == AMR_OTHER) {
+				ft = chan_state->codec[chan_state->ul_cmr];
+			} else {
+				/* SID frames will always get Frame Type Index 8 (AMR_SID) */
+				ft = AMR_SID;
+			}
+			rc = osmo_amr_rtp_enc(tch_data,
+				chan_state->codec[chan_state->ul_cmr],
+			        ft, AMR_GOOD);
+		}
+
+		break;
+	default:
+		LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn,
+			"TCH mode %u invalid, please fix!\n",
+			tch_mode);
+		return -EINVAL;
+	}
+	memcpy(*bursts_p, *bursts_p + 232, 232);
+	memcpy(*bursts_p + 232, *bursts_p + 464, 232);
+	ber10k = compute_ber10k(n_bits_total, n_errors);
+
+
+	/* Check if the frame is bad */
+	if (rc < 0) {
+		LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
+			"Received bad data (%u/%u)\n",
+			bi->fn % l1ts->mf_period, l1ts->mf_period);
+		bfi_flag = true;
+	} else if (rc < 4) {
+		LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
+			"Received bad data (%u/%u) with invalid codec mode %d\n",
+			bi->fn % l1ts->mf_period, l1ts->mf_period, rc);
+		bfi_flag = true;
+	}
+
+	if (rc != GSM_MACBLOCK_LEN && lchan->ecu_state)
+		osmo_ecu_frame_in(lchan->ecu_state, bfi_flag, tch_data, rc);
+
+	if (bfi_flag)
+		goto bfi;
+
+	/* FACCH */
+	if (rc == GSM_MACBLOCK_LEN) {
+		chan_state->ul_ongoing_facch = 1;
+		uint16_t ber10k = compute_ber10k(n_bits_total, n_errors);
+		if (lchan->nr == 0)
+			fn_begin = gsm0502_fn_remap(bi->fn, FN_REMAP_FACCH_H0);
+		else
+			fn_begin = gsm0502_fn_remap(bi->fn, FN_REMAP_FACCH_H1);
+		_sched_compose_ph_data_ind(l1t, bi->tn, fn_begin, chan,
+			tch_data + amr, GSM_MACBLOCK_LEN,
+			/* FIXME: AVG both RSSI and ToA */
+			bi->rssi, bi->toa256,
+			0 /* FIXME: AVG C/I */,
+			ber10k, PRES_INFO_UNKNOWN);
+bfi:
+		/* FIXME: a FACCH/H frame replaces two speech frames,
+		 * so we actually need to send two bad frame indications! */
+		if (rsl_cmode == RSL_CMOD_SPD_SPEECH) {
+			/* indicate bad frame */
+			if (lchan->tch.dtx.ul_sid) {
+				/* DTXu: pause in progress. Push empty payload to upper layers */
+				rc = 0;
+				goto compose_l1sap;
+			}
+
+			/* If there is an ECU active on this channel, use its output */
+			if (lchan->ecu_state) {
+				rc = osmo_ecu_frame_out(lchan->ecu_state, tch_data);
+				if (rc >= 0) /* Otherwise we send a BFI */
+					goto compose_l1sap;
+			}
+
+			switch (tch_mode) {
+			case GSM48_CMODE_SPEECH_V1: /* HR */
+				tch_data[0] = 0x70; /* F = 0, FT = 111 */
+				memset(tch_data + 1, 0, 14);
+				rc = 15;
+				break;
+			case GSM48_CMODE_SPEECH_AMR: /* AMR */
+				rc = osmo_amr_rtp_enc(tch_data,
+					chan_state->codec[chan_state->dl_cmr],
+					chan_state->codec[chan_state->dl_ft],
+					AMR_BAD);
+				if (rc < 2) {
+					LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn,
+					       "Failed to encode AMR_BAD frame (rc=%d), "
+					       "not sending BFI\n", rc);
+					return -EINVAL;
+				}
+				memset(tch_data + 2, 0, rc - 2);
+				break;
+			default:
+				LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn,
+					"TCH mode %u invalid, please fix!\n", tch_mode);
+				return -EINVAL;
+			}
+		}
+	}
+
+	if (rsl_cmode != RSL_CMOD_SPD_SPEECH)
+		return 0;
+
+compose_l1sap:
+	/* TCH or BFI */
+	/* Note on FN 19 or 20: If we received the last burst of a frame,
+	 * it actually starts at FN 8 or 9. A burst starting there, overlaps
+	 * with the slot 12, so an extra FN must be subtracted to get correct
+	 * start of frame.
+	 */
+	if (lchan->nr == 0)
+		fn_begin = gsm0502_fn_remap(bi->fn, FN_REMAP_TCH_H0);
+	else
+		fn_begin = gsm0502_fn_remap(bi->fn, FN_REMAP_TCH_H1);
+	return _sched_compose_tch_ind(l1t, bi->tn, fn_begin, chan,
+				      tch_data, rc, bi->toa256, ber10k, bi->rssi, is_sub);
+}
+
+/* common section for generation of TCH bursts (TCH/H and TCH/F).
+ * FIXME: this function is over-complicated, refactor / get rid of it. */
+extern void tx_tch_common(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+			  enum trx_chan_type chan, uint8_t bid,
+			  struct msgb **_msg_tch, struct msgb **_msg_facch);
+
+/* obtain a to-be-transmitted TCH/H (Half Traffic Channel) burst */
+ubit_t *tx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+		   enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+	struct msgb *msg_tch = NULL, *msg_facch = NULL;
+	struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+	struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn];
+	struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
+	uint8_t tch_mode = chan_state->tch_mode;
+	ubit_t *burst, **bursts_p = &chan_state->dl_bursts;
+	static ubit_t bits[GSM_BURST_LEN];
+
+	/* send burst, if we already got a frame */
+	if (bid > 0) {
+		if (!*bursts_p)
+			return NULL;
+		goto send_burst;
+	}
+
+	/* get TCH and/or FACCH */
+	tx_tch_common(l1t, tn, fn, chan, bid, &msg_tch, &msg_facch);
+
+	/* check for FACCH alignment */
+	if (msg_facch && ((((fn + 4) % 26) >> 2) & 1)) {
+		LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot transmit FACCH starting on "
+			"even frames, please fix RTS!\n");
+		msgb_free(msg_facch);
+		msg_facch = NULL;
+	}
+
+	/* BURST BYPASS */
+
+	/* allocate burst memory, if not already,
+	 * otherwise shift buffer by 2 bursts for interleaving */
+	if (!*bursts_p) {
+		*bursts_p = talloc_zero_size(tall_bts_ctx, 696);
+		if (!*bursts_p)
+			return NULL;
+	} else {
+		memcpy(*bursts_p, *bursts_p + 232, 232);
+		if (chan_state->dl_ongoing_facch) {
+			memcpy(*bursts_p + 232, *bursts_p + 464, 232);
+			memset(*bursts_p + 464, 0, 232);
+		} else {
+			memset(*bursts_p + 232, 0, 232);
+		}
+	}
+
+	/* no message at all */
+	if (!msg_tch && !msg_facch && !chan_state->dl_ongoing_facch) {
+		LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No TCH or FACCH prim for transmit.\n");
+		goto send_burst;
+	}
+
+	/* encode bursts (prioritize FACCH) */
+	if (msg_facch) {
+		gsm0503_tch_hr_encode(*bursts_p, msg_facch->l2h, msgb_l2len(msg_facch));
+		chan_state->dl_ongoing_facch = 1; /* first of two TCH frames */
+	} else if (chan_state->dl_ongoing_facch) /* second of two TCH frames */
+		chan_state->dl_ongoing_facch = 0; /* we are done with FACCH */
+	else if (tch_mode == GSM48_CMODE_SPEECH_AMR)
+		/* the first FN 4,13,21 or 5,14,22 defines that CMI is included
+		 * in frame, the first FN 0,8,17 or 1,9,18 defines that CMR is
+		 * included in frame. */
+		gsm0503_tch_ahs_encode(*bursts_p, msg_tch->l2h + 2,
+			msgb_l2len(msg_tch) - 2, fn_is_codec_mode_request(fn),
+			chan_state->codec, chan_state->codecs,
+			chan_state->dl_ft,
+			chan_state->dl_cmr);
+	else
+		gsm0503_tch_hr_encode(*bursts_p, msg_tch->l2h, msgb_l2len(msg_tch));
+
+	/* free message */
+	if (msg_tch)
+		msgb_free(msg_tch);
+	if (msg_facch)
+		msgb_free(msg_facch);
+
+send_burst:
+	/* compose burst */
+	burst = *bursts_p + bid * 116;
+	memset(bits, 0, 3);
+	memcpy(bits + 3, burst, 58);
+	memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26);
+	memcpy(bits + 87, burst + 58, 58);
+	memset(bits + 145, 0, 3);
+
+	if (nbits)
+		*nbits = GSM_BURST_LEN;
+
+	LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid);
+
+	return bits;
+}
diff --git a/src/osmo-bts-trx/sched_lchan_xcch.c b/src/osmo-bts-trx/sched_lchan_xcch.c
new file mode 100644
index 0000000..a16553f
--- /dev/null
+++ b/src/osmo-bts-trx/sched_lchan_xcch.c
@@ -0,0 +1,234 @@
+/*
+ * (C) 2013 by Andreas Eversberg <jolly at eversberg.eu>
+ * (C) 2015-2017 by Harald Welte <laforge at gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/coding/gsm0503_coding.h>
+
+#include <osmo-bts/bts.h>
+#include <osmo-bts/l1sap.h>
+#include <osmo-bts/logging.h>
+#include <osmo-bts/scheduler.h>
+#include <osmo-bts/scheduler_backend.h>
+
+#include <sched_utils.h>
+
+/*! \brief a single (SDCCH/SACCH) burst was received by the PHY, process it */
+int rx_data_fn(struct l1sched_trx *l1t, enum trx_chan_type chan,
+	       uint8_t bid, const struct trx_ul_burst_ind *bi)
+{
+	struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, bi->tn);
+	struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
+	sbit_t *burst, **bursts_p = &chan_state->ul_bursts;
+	uint32_t *first_fn = &chan_state->ul_first_fn;
+	uint8_t *mask = &chan_state->ul_mask;
+	float *rssi_sum = &chan_state->rssi_sum;
+	uint8_t *rssi_num = &chan_state->rssi_num;
+	int32_t *toa256_sum = &chan_state->toa256_sum;
+	uint8_t *toa_num = &chan_state->toa_num;
+	int32_t *ci_cb_sum = &chan_state->ci_cb_sum;
+	uint8_t *ci_cb_num = &chan_state->ci_cb_num;
+	uint8_t l2[GSM_MACBLOCK_LEN], l2_len;
+	int n_errors = 0;
+	int n_bits_total = 0;
+	int16_t lqual_cb;
+	uint16_t ber10k;
+	int rc;
+
+	/* If handover RACH detection is turned on, treat this burst as an Access Burst.
+	 * Handle NOPE.ind as usually to ensure proper Uplink measurement reporting. */
+	if (chan_state->ho_rach_detect == 1 && ~bi->flags & TRX_BI_F_NOPE_IND)
+		return rx_rach_fn(l1t, chan, bid, bi);
+
+	LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
+	       "Received Data, bid=%u\n", bid);
+
+	/* allocate burst memory, if not already */
+	if (!*bursts_p) {
+		*bursts_p = talloc_zero_size(tall_bts_ctx, 464);
+		if (!*bursts_p)
+			return -ENOMEM;
+	}
+
+	/* clear burst & store frame number of first burst */
+	if (bid == 0) {
+		memset(*bursts_p, 0, 464);
+		*mask = 0x0;
+		*first_fn = bi->fn;
+		*rssi_sum = 0;
+		*rssi_num = 0;
+		*toa256_sum = 0;
+		*toa_num = 0;
+		*ci_cb_sum = 0;
+		*ci_cb_num = 0;
+	}
+
+	/* update mask + RSSI */
+	*mask |= (1 << bid);
+	*rssi_sum += bi->rssi;
+	(*rssi_num)++;
+	*toa256_sum += bi->toa256;
+	(*toa_num)++;
+
+	/* C/I: Carrier-to-Interference ratio (in centiBels) */
+	if (bi->flags & TRX_BI_F_CI_CB) {
+		*ci_cb_sum += bi->ci_cb;
+		(*ci_cb_num)++;
+	}
+
+	/* Copy burst to buffer of 4 bursts. If the burst indication contains
+	 * no data, ensure that the buffer does not stay uninitialized */
+	burst = *bursts_p + bid * 116;
+	if (bi->burst_len > 0) {
+		memcpy(burst, bi->burst + 3, 58);
+		memcpy(burst + 58, bi->burst + 87, 58);
+	} else
+		memset(burst, 0, 58 * 2);
+
+	/* wait until complete set of bursts */
+	if (bid != 3)
+		return 0;
+
+	/* check for complete set of bursts */
+	if ((*mask & 0xf) != 0xf) {
+		LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
+			"Received incomplete data (%u/%u)\n",
+			bi->fn % l1ts->mf_period, l1ts->mf_period);
+
+		/* we require first burst to have correct FN */
+		if (!(*mask & 0x1)) {
+			*mask = 0x0;
+			return 0;
+		}
+	}
+	*mask = 0x0;
+
+	/* decode */
+	rc = gsm0503_xcch_decode(l2, *bursts_p, &n_errors, &n_bits_total);
+	if (rc) {
+		LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
+			"Received bad data (%u/%u)\n",
+			bi->fn % l1ts->mf_period, l1ts->mf_period);
+		l2_len = 0;
+	} else
+		l2_len = GSM_MACBLOCK_LEN;
+
+	lqual_cb = *ci_cb_num ? (*ci_cb_sum / *ci_cb_num) : 0;
+	ber10k = compute_ber10k(n_bits_total, n_errors);
+	return _sched_compose_ph_data_ind(l1t, bi->tn, *first_fn,
+					  chan, l2, l2_len,
+					  *rssi_sum / *rssi_num,
+					  *toa256_sum / *toa_num,
+					  lqual_cb, ber10k,
+					  PRES_INFO_UNKNOWN);
+}
+
+/* obtain a to-be-transmitted xCCH (e.g SACCH or SDCCH) burst */
+ubit_t *tx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
+		   enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
+{
+	struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
+	struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn];
+	struct msgb *msg = NULL; /* make GCC happy */
+	ubit_t *burst, **bursts_p = &l1ts->chan_state[chan].dl_bursts;
+	static ubit_t bits[GSM_BURST_LEN];
+
+	/* send burst, if we already got a frame */
+	if (bid > 0) {
+		if (!*bursts_p)
+			return NULL;
+		goto send_burst;
+	}
+
+	/* get mac block from queue */
+	msg = _sched_dequeue_prim(l1t, tn, fn, chan);
+	if (msg)
+		goto got_msg;
+
+	LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No prim for transmit.\n");
+
+no_msg:
+	/* free burst memory */
+	if (*bursts_p) {
+		talloc_free(*bursts_p);
+		*bursts_p = NULL;
+	}
+	return NULL;
+
+got_msg:
+	/* check validity of message */
+	if (msgb_l2len(msg) != GSM_MACBLOCK_LEN) {
+		LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim not 23 bytes, please FIX! "
+			"(len=%d)\n", msgb_l2len(msg));
+		/* free message */
+		msgb_free(msg);
+		goto no_msg;
+	}
+
+	/* BURST BYPASS */
+
+	/* handle loss detection of SACCH */
+	if (L1SAP_IS_LINK_SACCH(trx_chan_desc[chan].link_id)) {
+		/* count and send BFI */
+		if (++(l1ts->chan_state[chan].lost_frames) > 1) {
+			/* TODO: Should we pass old TOA here? Otherwise we risk
+			 * unnecessary decreasing TA */
+
+			/* Note: RSSI is set to 0 to indicate to the higher
+			 * layers that this is a faked ph_data_ind */
+			_sched_compose_ph_data_ind(l1t, tn, 0, chan, NULL, 0,
+						   0, 0, 0, 10000,
+						   PRES_INFO_INVALID);
+		}
+	}
+
+	/* allocate burst memory, if not already */
+	if (!*bursts_p) {
+		*bursts_p = talloc_zero_size(tall_bts_ctx, 464);
+		if (!*bursts_p)
+			return NULL;
+	}
+
+	/* encode bursts */
+	gsm0503_xcch_encode(*bursts_p, msg->l2h);
+
+	/* free message */
+	msgb_free(msg);
+
+send_burst:
+	/* compose burst */
+	burst = *bursts_p + bid * 116;
+	memset(bits, 0, 3);
+	memcpy(bits + 3, burst, 58);
+	memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26);
+	memcpy(bits + 87, burst + 58, 58);
+	memset(bits + 145, 0, 3);
+
+	if (nbits)
+		*nbits = GSM_BURST_LEN;
+
+	LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid);
+
+	return bits;
+}
diff --git a/src/osmo-bts-trx/sched_utils.h b/src/osmo-bts-trx/sched_utils.h
new file mode 100644
index 0000000..4a1aaf5
--- /dev/null
+++ b/src/osmo-bts-trx/sched_utils.h
@@ -0,0 +1,42 @@
+/* Auxiliary scheduler utilities.
+ *
+ * (C) 2017 by Harald Welte <laforge at gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <errno.h>
+
+extern void *tall_bts_ctx;
+
+/* Compute the bit error rate in 1/10000 units */
+static inline uint16_t compute_ber10k(int n_bits_total, int n_errors)
+{
+	if (n_bits_total == 0)
+		return 10000;
+	else
+		return 10000 * n_errors / n_bits_total;
+}
+
+/* determine if the FN is transmitting a CMR (1) or not (0) */
+static inline int fn_is_codec_mode_request(uint32_t fn)
+{
+	return (((fn + 4) % 26) >> 2) & 1;
+}
diff --git a/src/osmo-bts-trx/scheduler_trx.c b/src/osmo-bts-trx/scheduler_trx.c
index 97b590d..4fb0e23 100644
--- a/src/osmo-bts-trx/scheduler_trx.c
+++ b/src/osmo-bts-trx/scheduler_trx.c
@@ -32,12 +32,8 @@
 #include <osmocom/core/msgb.h>
 #include <osmocom/core/talloc.h>
 #include <osmocom/core/timer_compat.h>
-#include <osmocom/codec/codec.h>
-#include <osmocom/codec/ecu.h>
 #include <osmocom/core/bits.h>
 #include <osmocom/gsm/a5.h>
-#include <osmocom/coding/gsm0503_coding.h>
-#include <osmocom/coding/gsm0503_amr_dtx.h>
 
 
 #include <osmo-bts/gsm_data.h>
@@ -45,33 +41,11 @@
 #include <osmo-bts/rsl.h>
 #include <osmo-bts/bts.h>
 #include <osmo-bts/l1sap.h>
-#include <osmo-bts/msg_utils.h>
 #include <osmo-bts/scheduler.h>
 #include <osmo-bts/scheduler_backend.h>
-#include <osmocom/gsm/gsm0502.h>
 
 #include "l1_if.h"
 #include "trx_if.h"
-#include "loops.h"
-
-extern void *tall_bts_ctx;
-
-/* Maximum size of a EGPRS message in bytes */
-#define EGPRS_0503_MAX_BYTES		155
-
-
-/* Compute the bit error rate in 1/10000 units */
-static inline uint16_t compute_ber10k(int n_bits_total, int n_errors)
-{
-	if (n_bits_total == 0)
-		return 10000;
-	else
-		return 10000 * n_errors / n_bits_total;
-}
-
-/*
- * TX on downlink
- */
 
 /* an IDLE burst returns nothing. on C0 it is replaced by dummy burst */
 ubit_t *tx_idle_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
@@ -85,1542 +59,6 @@
 	return NULL;
 }
 
-/* obtain a to-be-transmitted FCCH (frequency correction channel) burst */
-ubit_t *tx_fcch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
-	enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
-{
-	LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting FCCH\n");
-
-	if (nbits)
-		*nbits = GSM_BURST_LEN;
-
-	/* BURST BYPASS */
-
-	return (ubit_t *) _sched_fcch_burst;
-}
-
-/* obtain a to-be-transmitted SCH (synchronization channel) burst */
-ubit_t *tx_sch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
-	enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
-{
-	static ubit_t bits[GSM_BURST_LEN], burst[78];
-	uint8_t sb_info[4];
-	struct	gsm_time t;
-	uint8_t t3p, bsic;
-
-	LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting SCH\n");
-
-	/* BURST BYPASS */
-
-	/* create SB info from GSM time and BSIC */
-	gsm_fn2gsmtime(&t, fn);
-	t3p = t.t3 / 10;
-	bsic = l1t->trx->bts->bsic;
-	sb_info[0] =
-		((bsic &  0x3f) << 2) |
-		((t.t1 & 0x600) >> 9);
-	sb_info[1] =
-		((t.t1 & 0x1fe) >> 1);
-	sb_info[2] =
-		((t.t1 & 0x001) << 7) |
-		((t.t2 &  0x1f) << 2) |
-		((t3p  &   0x6) >> 1);
-	sb_info[3] =
-		 (t3p  &   0x1);
-
-	/* encode bursts */
-	gsm0503_sch_encode(burst, sb_info);
-
-	/* compose burst */
-	memset(bits, 0, 3);
-	memcpy(bits + 3, burst, 39);
-	memcpy(bits + 42, _sched_sch_train, 64);
-	memcpy(bits + 106, burst + 39, 39);
-	memset(bits + 145, 0, 3);
-
-	if (nbits)
-		*nbits = GSM_BURST_LEN;
-
-	return bits;
-}
-
-/* obtain a to-be-transmitted data (SACCH/SDCCH) burst */
-ubit_t *tx_data_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
-	enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
-{
-	struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
-	struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn];
-	struct msgb *msg = NULL; /* make GCC happy */
-	ubit_t *burst, **bursts_p = &l1ts->chan_state[chan].dl_bursts;
-	static ubit_t bits[GSM_BURST_LEN];
-
-	/* send burst, if we already got a frame */
-	if (bid > 0) {
-		if (!*bursts_p)
-			return NULL;
-		goto send_burst;
-	}
-
-	/* get mac block from queue */
-	msg = _sched_dequeue_prim(l1t, tn, fn, chan);
-	if (msg)
-		goto got_msg;
-
-	LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No prim for transmit.\n");
-
-no_msg:
-	/* free burst memory */
-	if (*bursts_p) {
-		talloc_free(*bursts_p);
-		*bursts_p = NULL;
-	}
-	return NULL;
-
-got_msg:
-	/* check validity of message */
-	if (msgb_l2len(msg) != GSM_MACBLOCK_LEN) {
-		LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim not 23 bytes, please FIX! "
-			"(len=%d)\n", msgb_l2len(msg));
-		/* free message */
-		msgb_free(msg);
-		goto no_msg;
-	}
-
-	/* BURST BYPASS */
-
-	/* handle loss detection of SACCH */
-	if (L1SAP_IS_LINK_SACCH(trx_chan_desc[chan].link_id)) {
-		/* count and send BFI */
-		if (++(l1ts->chan_state[chan].lost_frames) > 1) {
-			/* TODO: Should we pass old TOA here? Otherwise we risk
-			 * unnecessary decreasing TA */
-
-			/* Note: RSSI is set to 0 to indicate to the higher
-			 * layers that this is a faked ph_data_ind */
-			_sched_compose_ph_data_ind(l1t, tn, 0, chan, NULL, 0,
-						   0, 0, 0, 10000,
-						   PRES_INFO_INVALID);
-		}
-	}
-
-	/* allocate burst memory, if not already */
-	if (!*bursts_p) {
-		*bursts_p = talloc_zero_size(tall_bts_ctx, 464);
-		if (!*bursts_p)
-			return NULL;
-	}
-
-	/* encode bursts */
-	gsm0503_xcch_encode(*bursts_p, msg->l2h);
-
-	/* free message */
-	msgb_free(msg);
-
-send_burst:
-	/* compose burst */
-	burst = *bursts_p + bid * 116;
-	memset(bits, 0, 3);
-	memcpy(bits + 3, burst, 58);
-	memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26);
-	memcpy(bits + 87, burst + 58, 58);
-	memset(bits + 145, 0, 3);
-
-	if (nbits)
-		*nbits = GSM_BURST_LEN;
-
-	LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid);
-
-	return bits;
-}
-
-/* obtain a to-be-transmitted PDTCH (packet data) burst */
-ubit_t *tx_pdtch_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
-	enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
-{
-	struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
-	struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn];
-	struct msgb *msg = NULL; /* make GCC happy */
-	ubit_t *burst, **bursts_p = &l1ts->chan_state[chan].dl_bursts;
-	enum trx_burst_type *burst_type = &l1ts->chan_state[chan].dl_burst_type;
-	static ubit_t bits[EGPRS_BURST_LEN];
-	int rc = 0;
-
-	/* send burst, if we already got a frame */
-	if (bid > 0) {
-		if (!*bursts_p)
-			return NULL;
-		goto send_burst;
-	}
-
-	/* get mac block from queue */
-	msg = _sched_dequeue_prim(l1t, tn, fn, chan);
-	if (msg)
-		goto got_msg;
-
-	LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No prim for transmit.\n");
-
-no_msg:
-	/* free burst memory */
-	if (*bursts_p) {
-		talloc_free(*bursts_p);
-		*bursts_p = NULL;
-	}
-	return NULL;
-
-got_msg:
-	/* BURST BYPASS */
-
-	/* allocate burst memory, if not already */
-	if (!*bursts_p) {
-		*bursts_p = talloc_zero_size(tall_bts_ctx,
-					     GSM0503_EGPRS_BURSTS_NBITS);
-		if (!*bursts_p)
-			return NULL;
-	}
-
-	/* encode bursts */
-	rc = gsm0503_pdtch_egprs_encode(*bursts_p, msg->l2h, msg->tail - msg->l2h);
-	if (rc < 0)
-		rc = gsm0503_pdtch_encode(*bursts_p, msg->l2h, msg->tail - msg->l2h);
-
-	/* check validity of message */
-	if (rc < 0) {
-		LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim invalid length, please FIX! "
-			"(len=%ld)\n", (long)(msg->tail - msg->l2h));
-		/* free message */
-		msgb_free(msg);
-		goto no_msg;
-	} else if (rc == GSM0503_EGPRS_BURSTS_NBITS) {
-		*burst_type = TRX_BURST_8PSK;
-	} else {
-		*burst_type = TRX_BURST_GMSK;
-	}
-
-	/* free message */
-	msgb_free(msg);
-
-send_burst:
-	/* compose burst */
-	if (*burst_type == TRX_BURST_8PSK) {
-		burst = *bursts_p + bid * 348;
-		memset(bits, 1, 9);
-		memcpy(bits + 9, burst, 174);
-		memcpy(bits + 183, _sched_egprs_tsc[gsm_ts_tsc(ts)], 78);
-		memcpy(bits + 261, burst + 174, 174);
-		memset(bits + 435, 1, 9);
-
-		if (nbits)
-			*nbits = EGPRS_BURST_LEN;
-	} else {
-		burst = *bursts_p + bid * 116;
-		memset(bits, 0, 3);
-		memcpy(bits + 3, burst, 58);
-		memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26);
-		memcpy(bits + 87, burst + 58, 58);
-		memset(bits + 145, 0, 3);
-
-		if (nbits)
-			*nbits = GSM_BURST_LEN;
-	}
-
-	LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid);
-
-	return bits;
-}
-
-/* determine if the FN is transmitting a CMR (1) or not (0) */
-static inline int fn_is_codec_mode_request(uint32_t fn)
-{
-	return (((fn + 4) % 26) >> 2) & 1;
-}
-
-/* common section for generation of TCH bursts (TCH/H and TCH/F) */
-static void tx_tch_common(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
-	enum trx_chan_type chan, uint8_t bid, struct msgb **_msg_tch,
-	struct msgb **_msg_facch)
-{
-	struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
-	struct msgb *msg1, *msg2, *msg_tch = NULL, *msg_facch = NULL;
-	struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
-	uint8_t rsl_cmode = chan_state->rsl_cmode;
-	uint8_t tch_mode = chan_state->tch_mode;
-	struct osmo_phsap_prim *l1sap;
-	int32_t *toa256_sum = &chan_state->toa256_sum;
-	uint8_t *toa_num = &chan_state->toa_num;
-	int16_t toa256;
-
-	/* handle loss detection of received TCH frames */
-	if (rsl_cmode == RSL_CMOD_SPD_SPEECH
-	    && ++(chan_state->lost_frames) > 5) {
-		uint8_t tch_data[GSM_FR_BYTES];
-		int len;
-
-		LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn,
-			"Missing TCH bursts detected, sending BFI\n");
-
-		/* indicate bad frame */
-		switch (tch_mode) {
-		case GSM48_CMODE_SPEECH_V1: /* FR / HR */
-			if (chan != TRXC_TCHF) { /* HR */
-				tch_data[0] = 0x70; /* F = 0, FT = 111 */
-				memset(tch_data + 1, 0, 14);
-				len = 15;
-				break;
-			}
-			memset(tch_data, 0, GSM_FR_BYTES);
-			len = GSM_FR_BYTES;
-			break;
-		case GSM48_CMODE_SPEECH_EFR: /* EFR */
-			if (chan != TRXC_TCHF)
-				goto inval_mode1;
-			memset(tch_data, 0, GSM_EFR_BYTES);
-			len = GSM_EFR_BYTES;
-			break;
-		case GSM48_CMODE_SPEECH_AMR: /* AMR */
-			len = osmo_amr_rtp_enc(tch_data,
-				chan_state->codec[chan_state->dl_cmr],
-				chan_state->codec[chan_state->dl_ft], AMR_BAD);
-			if (len < 2) {
-				LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn,
-				       "Failed to encode AMR_BAD frame (rc=%d), "
-				       "not sending BFI\n", len);
-				return;
-			}
-			memset(tch_data + 2, 0, len - 2);
-			break;
-		default:
-inval_mode1:
-			LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode invalid, please fix!\n");
-			len = 0;
-		}
-
-		if (len) {
-			if (*toa_num == 0)
-				toa256 = 0;
-			else
-				toa256 = *toa256_sum / *toa_num;
-
-			/* Note: RSSI is set to 0 to indicate to the higher
-			 * layers that this is a faked tch_ind */
-			_sched_compose_tch_ind(l1t, tn, fn, chan, tch_data, len, toa256, 10000, 0, 0);
-		}
-	}
-
-	/* get frame and unlink from queue */
-	msg1 = _sched_dequeue_prim(l1t, tn, fn, chan);
-	msg2 = _sched_dequeue_prim(l1t, tn, fn, chan);
-	if (msg1) {
-		l1sap = msgb_l1sap_prim(msg1);
-		if (l1sap->oph.primitive == PRIM_TCH) {
-			msg_tch = msg1;
-			if (msg2) {
-				l1sap = msgb_l1sap_prim(msg2);
-				if (l1sap->oph.primitive == PRIM_TCH) {
-					LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn,
-						"TCH twice, please FIX!\n");
-					msgb_free(msg2);
-				} else
-					msg_facch = msg2;
-			}
-		} else {
-			msg_facch = msg1;
-			if (msg2) {
-				l1sap = msgb_l1sap_prim(msg2);
-				if (l1sap->oph.primitive != PRIM_TCH) {
-					LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn,
-						"FACCH twice, please FIX!\n");
-					msgb_free(msg2);
-				} else
-					msg_tch = msg2;
-			}
-		}
-	} else if (msg2) {
-		l1sap = msgb_l1sap_prim(msg2);
-		if (l1sap->oph.primitive == PRIM_TCH)
-			msg_tch = msg2;
-		else
-			msg_facch = msg2;
-	}
-
-	/* check validity of message */
-	if (msg_facch && msgb_l2len(msg_facch) != GSM_MACBLOCK_LEN) {
-		LOGL1S(DL1P, LOGL_FATAL, l1t, tn, chan, fn, "Prim not 23 bytes, please FIX! "
-			"(len=%d)\n", msgb_l2len(msg_facch));
-		/* free message */
-		msgb_free(msg_facch);
-		msg_facch = NULL;
-	}
-
-	/* check validity of message, get AMR ft and cmr */
-	if (!msg_facch && msg_tch) {
-		int len;
-		uint8_t cmr_codec;
-		int cmr, ft, i;
-		enum osmo_amr_type ft_codec;
-		enum osmo_amr_quality bfi;
-		int8_t sti, cmi;
-
-		if (rsl_cmode != RSL_CMOD_SPD_SPEECH) {
-			LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Dropping speech frame, "
-				"because we are not in speech mode\n");
-			goto free_bad_msg;
-		}
-
-		switch (tch_mode) {
-		case GSM48_CMODE_SPEECH_V1: /* FR / HR */
-			if (chan != TRXC_TCHF) /* HR */
-				len = 15;
-			else
-				len = GSM_FR_BYTES;
-			break;
-		case GSM48_CMODE_SPEECH_EFR: /* EFR */
-			if (chan != TRXC_TCHF)
-				goto inval_mode2;
-			len = GSM_EFR_BYTES;
-			break;
-		case GSM48_CMODE_SPEECH_AMR: /* AMR */
-			len = osmo_amr_rtp_dec(msg_tch->l2h, msgb_l2len(msg_tch),
-					       &cmr_codec, &cmi, &ft_codec,
-					       &bfi, &sti);
-			cmr = -1;
-			ft = -1;
-			for (i = 0; i < chan_state->codecs; i++) {
-				if (chan_state->codec[i] == cmr_codec)
-					cmr = i;
-				if (chan_state->codec[i] == ft_codec)
-					ft = i;
-			}
-			if (cmr >= 0) { /* new request */
-				chan_state->dl_cmr = cmr;
-				/* disable AMR loop */
-				trx_loop_amr_set(chan_state, 0);
-			} else {
-				/* enable AMR loop */
-				trx_loop_amr_set(chan_state, 1);
-			}
-			if (ft < 0) {
-				LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn,
-					"Codec (FT = %d) of RTP frame not in list\n", ft_codec);
-				goto free_bad_msg;
-			}
-			if (fn_is_codec_mode_request(fn) && chan_state->dl_ft != ft) {
-				LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn, "Codec (FT = %d) "
-					" of RTP cannot be changed now, but in next frame\n", ft_codec);
-				goto free_bad_msg;
-			}
-			chan_state->dl_ft = ft;
-			if (bfi == AMR_BAD) {
-				LOGL1S(DL1P, LOGL_NOTICE, l1t, tn, chan, fn,
-					"Transmitting 'bad AMR frame'\n");
-				goto free_bad_msg;
-			}
-			break;
-		default:
-inval_mode2:
-			LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "TCH mode invalid, please fix!\n");
-			goto free_bad_msg;
-		}
-		if (len < 0) {
-			LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot send invalid AMR payload\n");
-			goto free_bad_msg;
-		}
-		if (msgb_l2len(msg_tch) != len) {
-			LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot send payload with "
-				"invalid length! (expecting %d, received %d)\n",
-				len, msgb_l2len(msg_tch));
-free_bad_msg:
-			/* free message */
-			msgb_free(msg_tch);
-			msg_tch = NULL;
-			goto send_frame;
-		}
-	}
-
-send_frame:
-	*_msg_tch = msg_tch;
-	*_msg_facch = msg_facch;
-}
-
-/* obtain a to-be-transmitted TCH/F (Full Traffic Channel) burst */
-ubit_t *tx_tchf_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
-	enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
-{
-	struct msgb *msg_tch = NULL, *msg_facch = NULL;
-	struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
-	struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn];
-	struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
-	uint8_t tch_mode = chan_state->tch_mode;
-	ubit_t *burst, **bursts_p = &chan_state->dl_bursts;
-	static ubit_t bits[GSM_BURST_LEN];
-
-	/* send burst, if we already got a frame */
-	if (bid > 0) {
-		if (!*bursts_p)
-			return NULL;
-		goto send_burst;
-	}
-
-	tx_tch_common(l1t, tn, fn, chan, bid, &msg_tch, &msg_facch);
-
-	/* BURST BYPASS */
-
-	/* allocate burst memory, if not already,
-	 * otherwise shift buffer by 4 bursts for interleaving */
-	if (!*bursts_p) {
-		*bursts_p = talloc_zero_size(tall_bts_ctx, 928);
-		if (!*bursts_p)
-			return NULL;
-	} else {
-		memcpy(*bursts_p, *bursts_p + 464, 464);
-		memset(*bursts_p + 464, 0, 464);
-	}
-
-	/* no message at all */
-	if (!msg_tch && !msg_facch) {
-		LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No TCH or FACCH prim for transmit.\n");
-		goto send_burst;
-	}
-
-	/* encode bursts (prioritize FACCH) */
-	if (msg_facch)
-		gsm0503_tch_fr_encode(*bursts_p, msg_facch->l2h, msgb_l2len(msg_facch),
-			1);
-	else if (tch_mode == GSM48_CMODE_SPEECH_AMR)
-		/* the first FN 4,13,21 defines that CMI is included in frame,
-		 * the first FN 0,8,17 defines that CMR is included in frame.
-		 */
-		gsm0503_tch_afs_encode(*bursts_p, msg_tch->l2h + 2,
-			msgb_l2len(msg_tch) - 2, fn_is_codec_mode_request(fn),
-			chan_state->codec, chan_state->codecs,
-			chan_state->dl_ft,
-			chan_state->dl_cmr);
-	else
-		gsm0503_tch_fr_encode(*bursts_p, msg_tch->l2h, msgb_l2len(msg_tch), 1);
-
-	/* free message */
-	if (msg_tch)
-		msgb_free(msg_tch);
-	if (msg_facch)
-		msgb_free(msg_facch);
-
-send_burst:
-	/* compose burst */
-	burst = *bursts_p + bid * 116;
-	memset(bits, 0, 3);
-	memcpy(bits + 3, burst, 58);
-	memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26);
-	memcpy(bits + 87, burst + 58, 58);
-	memset(bits + 145, 0, 3);
-
-	if (nbits)
-		*nbits = GSM_BURST_LEN;
-
-	LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid);
-
-	return bits;
-}
-
-/* obtain a to-be-transmitted TCH/H (Half Traffic Channel) burst */
-ubit_t *tx_tchh_fn(struct l1sched_trx *l1t, uint8_t tn, uint32_t fn,
-	enum trx_chan_type chan, uint8_t bid, uint16_t *nbits)
-{
-	struct msgb *msg_tch = NULL, *msg_facch = NULL;
-	struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, tn);
-	struct gsm_bts_trx_ts *ts = &l1t->trx->ts[tn];
-	struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
-	uint8_t tch_mode = chan_state->tch_mode;
-	ubit_t *burst, **bursts_p = &chan_state->dl_bursts;
-	static ubit_t bits[GSM_BURST_LEN];
-
-	/* send burst, if we already got a frame */
-	if (bid > 0) {
-		if (!*bursts_p)
-			return NULL;
-		goto send_burst;
-	}
-
-	/* get TCH and/or FACCH */
-	tx_tch_common(l1t, tn, fn, chan, bid, &msg_tch, &msg_facch);
-
-	/* check for FACCH alignment */
-	if (msg_facch && ((((fn + 4) % 26) >> 2) & 1)) {
-		LOGL1S(DL1P, LOGL_ERROR, l1t, tn, chan, fn, "Cannot transmit FACCH starting on "
-			"even frames, please fix RTS!\n");
-		msgb_free(msg_facch);
-		msg_facch = NULL;
-	}
-
-	/* BURST BYPASS */
-
-	/* allocate burst memory, if not already,
-	 * otherwise shift buffer by 2 bursts for interleaving */
-	if (!*bursts_p) {
-		*bursts_p = talloc_zero_size(tall_bts_ctx, 696);
-		if (!*bursts_p)
-			return NULL;
-	} else {
-		memcpy(*bursts_p, *bursts_p + 232, 232);
-		if (chan_state->dl_ongoing_facch) {
-			memcpy(*bursts_p + 232, *bursts_p + 464, 232);
-			memset(*bursts_p + 464, 0, 232);
-		} else {
-			memset(*bursts_p + 232, 0, 232);
-		}
-	}
-
-	/* no message at all */
-	if (!msg_tch && !msg_facch && !chan_state->dl_ongoing_facch) {
-		LOGL1S(DL1P, LOGL_INFO, l1t, tn, chan, fn, "No TCH or FACCH prim for transmit.\n");
-		goto send_burst;
-	}
-
-	/* encode bursts (prioritize FACCH) */
-	if (msg_facch) {
-		gsm0503_tch_hr_encode(*bursts_p, msg_facch->l2h, msgb_l2len(msg_facch));
-		chan_state->dl_ongoing_facch = 1; /* first of two TCH frames */
-	} else if (chan_state->dl_ongoing_facch) /* second of two TCH frames */
-		chan_state->dl_ongoing_facch = 0; /* we are done with FACCH */
-	else if (tch_mode == GSM48_CMODE_SPEECH_AMR)
-		/* the first FN 4,13,21 or 5,14,22 defines that CMI is included
-		 * in frame, the first FN 0,8,17 or 1,9,18 defines that CMR is
-		 * included in frame. */
-		gsm0503_tch_ahs_encode(*bursts_p, msg_tch->l2h + 2,
-			msgb_l2len(msg_tch) - 2, fn_is_codec_mode_request(fn),
-			chan_state->codec, chan_state->codecs,
-			chan_state->dl_ft,
-			chan_state->dl_cmr);
-	else
-		gsm0503_tch_hr_encode(*bursts_p, msg_tch->l2h, msgb_l2len(msg_tch));
-
-	/* free message */
-	if (msg_tch)
-		msgb_free(msg_tch);
-	if (msg_facch)
-		msgb_free(msg_facch);
-
-send_burst:
-	/* compose burst */
-	burst = *bursts_p + bid * 116;
-	memset(bits, 0, 3);
-	memcpy(bits + 3, burst, 58);
-	memcpy(bits + 61, _sched_tsc[gsm_ts_tsc(ts)], 26);
-	memcpy(bits + 87, burst + 58, 58);
-	memset(bits + 145, 0, 3);
-
-	if (nbits)
-		*nbits = GSM_BURST_LEN;
-
-	LOGL1S(DL1P, LOGL_DEBUG, l1t, tn, chan, fn, "Transmitting burst=%u.\n", bid);
-
-	return bits;
-}
-
-
-/*
- * RX on uplink (indication to upper layer)
- */
-
-/* 3GPP TS 05.02, section 5.2.7 */
-#define RACH_EXT_TAIL_LEN	8
-#define RACH_SYNCH_SEQ_LEN	41
-
-enum rach_synch_seq_t {
-	RACH_SYNCH_SEQ_UNKNOWN = -1,
-	RACH_SYNCH_SEQ_TS0, /* GSM, GMSK (default) */
-	RACH_SYNCH_SEQ_TS1, /* EGPRS, 8-PSK */
-	RACH_SYNCH_SEQ_TS2, /* EGPRS, GMSK */
-	RACH_SYNCH_SEQ_NUM
-};
-
-static struct value_string rach_synch_seq_names[] = {
-	{ RACH_SYNCH_SEQ_UNKNOWN,	"UNKNOWN" },
-	{ RACH_SYNCH_SEQ_TS0,		"TS0: GSM, GMSK" },
-	{ RACH_SYNCH_SEQ_TS1,		"TS1: EGPRS, 8-PSK" },
-	{ RACH_SYNCH_SEQ_TS2,		"TS2: EGPRS, GMSK" },
-	{ 0, NULL },
-};
-
-static enum rach_synch_seq_t rach_get_synch_seq(sbit_t *bits, int *best_score)
-{
-	sbit_t *synch_seq_burst = bits + RACH_EXT_TAIL_LEN;
-	enum rach_synch_seq_t seq = RACH_SYNCH_SEQ_TS0;
-	int score[RACH_SYNCH_SEQ_NUM] = { 0 };
-	int max_score = INT_MIN;
-	int i, j;
-
-	/* 3GPP TS 05.02, section 5.2.7 "Access burst (AB)", synch. sequence bits */
-	static const char synch_seq_ref[RACH_SYNCH_SEQ_NUM][RACH_SYNCH_SEQ_LEN] = {
-		[RACH_SYNCH_SEQ_TS0] = "01001011011111111001100110101010001111000",
-		[RACH_SYNCH_SEQ_TS1] = "01010100111110001000011000101111001001101",
-		[RACH_SYNCH_SEQ_TS2] = "11101111001001110101011000001101101110111",
-	};
-
-	/* Get a multiplier for j-th bit of i-th synch. sequence */
-#define RACH_SYNCH_SEQ_MULT \
-	(synch_seq_ref[i][j] == '1' ? -1 : 1)
-
-	/* For each synch. sequence, count the bit match score. Since we deal with
-	 * soft-bits (-127...127), we sum the absolute values of matching ones,
-	 * and subtract the absolute values of different ones, so the resulting
-	 * score is more accurate than it could be with hard-bits. */
-	for (i = 0; i < RACH_SYNCH_SEQ_NUM; i++) {
-		for (j = 0; j < RACH_SYNCH_SEQ_LEN; j++)
-			score[i] += RACH_SYNCH_SEQ_MULT * synch_seq_burst[j];
-
-		/* Keep the maximum value updated */
-		if (score[i] > max_score) {
-			max_score = score[i];
-			seq = i;
-		}
-	}
-
-	/* Calculate an approximate level of our confidence */
-	if (best_score != NULL)
-		*best_score = max_score;
-
-	/* At least 1/3 of a synch. sequence shall match */
-	if (max_score < (127 * RACH_SYNCH_SEQ_LEN / 3))
-		return RACH_SYNCH_SEQ_UNKNOWN;
-
-	return seq;
-}
-
-int rx_rach_fn(struct l1sched_trx *l1t, enum trx_chan_type chan,
-	       uint8_t bid, const struct trx_ul_burst_ind *bi)
-{
-	struct osmo_phsap_prim l1sap;
-	int n_errors = 0;
-	int n_bits_total = 0;
-	uint16_t ra11;
-	uint8_t ra;
-	int rc;
-
-	/* TSC (Training Sequence Code) is an optional parameter of the UL burst
-	 * indication. We need this information in order to decide whether an
-	 * Access Burst is 11-bit encoded or not (see OS#1854). If this information
-	 * is absent, we try to correlate the received synch. sequence with the
-	 * known ones (3GPP TS 05.02, section 5.2.7), and fall-back to the default
-	 * TS0 if it fails. */
-	enum rach_synch_seq_t synch_seq = RACH_SYNCH_SEQ_TS0;
-	int best_score = 127 * RACH_SYNCH_SEQ_LEN;
-
-	/* If logical channel is not either of RACH, PDTCH or PTCCH, this is a
-	 * handover Access Burst, which is always encoded as 8-bit and shall
-	 * contain the generic training sequence (TS0). */
-	if (chan == TRXC_RACH || chan == TRXC_PDTCH || chan == TRXC_PTCCH) {
-		if (bi->flags & TRX_BI_F_TS_INFO)
-			synch_seq = (enum rach_synch_seq_t) bi->tsc;
-		else
-			synch_seq = rach_get_synch_seq((sbit_t *) bi->burst, &best_score);
-	}
-
-	LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
-	       "Received%s RACH (%s): rssi=%d toa256=%d",
-	       (chan != TRXC_RACH) ? " handover" : "",
-	       get_value_string(rach_synch_seq_names, synch_seq),
-	       bi->rssi, bi->toa256);
-	if (bi->flags & TRX_BI_F_CI_CB)
-		LOGPC(DL1P, LOGL_DEBUG, " C/I=%d cB", bi->ci_cb);
-	else
-		LOGPC(DL1P, LOGL_DEBUG, " match=%.1f%%",
-		      best_score * 100.0 / (127 * RACH_SYNCH_SEQ_LEN));
-	LOGPC(DL1P, LOGL_DEBUG, "\n");
-
-	/* Compose a new L1SAP primitive */
-	memset(&l1sap, 0x00, sizeof(l1sap));
-	osmo_prim_init(&l1sap.oph, SAP_GSM_PH, PRIM_PH_RACH, PRIM_OP_INDICATION, NULL);
-	l1sap.u.rach_ind.chan_nr = trx_chan_desc[chan].chan_nr | bi->tn;
-	l1sap.u.rach_ind.acc_delay = (bi->toa256 >= 0) ? bi->toa256 / 256 : 0;
-	l1sap.u.rach_ind.acc_delay_256bits = bi->toa256;
-	l1sap.u.rach_ind.rssi = bi->rssi;
-	l1sap.u.rach_ind.fn = bi->fn;
-
-	/* Link quality is defined by C/I (Carrier-to-Interference ratio),
-	 * which has optional presence. If it's absent, report the
-	 * minimum acceptable value to pass L1SAP checks. */
-	if (bi->flags & TRX_BI_F_CI_CB)
-		l1sap.u.rach_ind.lqual_cb = bi->ci_cb;
-	else
-		l1sap.u.rach_ind.lqual_cb = l1t->trx->bts->min_qual_rach;
-
-	/* Decode RACH depending on its synch. sequence */
-	switch (synch_seq) {
-	case RACH_SYNCH_SEQ_TS1:
-	case RACH_SYNCH_SEQ_TS2:
-		rc = gsm0503_rach_ext_decode_ber(&ra11, bi->burst + RACH_EXT_TAIL_LEN + RACH_SYNCH_SEQ_LEN,
-						 l1t->trx->bts->bsic, &n_errors, &n_bits_total);
-		if (rc) {
-			LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
-			       "Received bad Access Burst\n");
-			return 0;
-		}
-
-		if (synch_seq == RACH_SYNCH_SEQ_TS1)
-			l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_1;
-		else
-			l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_2;
-
-		l1sap.u.rach_ind.is_11bit = 1;
-		l1sap.u.rach_ind.ra = ra11;
-		break;
-
-	case RACH_SYNCH_SEQ_TS0:
-	default:
-		/* Fall-back to the default TS0 if needed */
-		if (synch_seq != RACH_SYNCH_SEQ_TS0) {
-			LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
-			       "Falling-back to the default TS0\n");
-			synch_seq = RACH_SYNCH_SEQ_TS0;
-		}
-
-		rc = gsm0503_rach_decode_ber(&ra, bi->burst + RACH_EXT_TAIL_LEN + RACH_SYNCH_SEQ_LEN,
-					     l1t->trx->bts->bsic, &n_errors, &n_bits_total);
-		if (rc) {
-			LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
-			       "Received bad Access Burst\n");
-			return 0;
-		}
-
-		l1sap.u.rach_ind.burst_type = GSM_L1_BURST_TYPE_ACCESS_0;
-		l1sap.u.rach_ind.is_11bit = 0;
-		l1sap.u.rach_ind.ra = ra;
-		break;
-	}
-
-	l1sap.u.rach_ind.ber10k = compute_ber10k(n_bits_total, n_errors);
-
-	/* forward primitive */
-	l1sap_up(l1t->trx, &l1sap);
-
-	return 0;
-}
-
-/*! \brief a single (SDCCH/SACCH) burst was received by the PHY, process it */
-int rx_data_fn(struct l1sched_trx *l1t, enum trx_chan_type chan,
-	       uint8_t bid, const struct trx_ul_burst_ind *bi)
-{
-	struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, bi->tn);
-	struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
-	sbit_t *burst, **bursts_p = &chan_state->ul_bursts;
-	uint32_t *first_fn = &chan_state->ul_first_fn;
-	uint8_t *mask = &chan_state->ul_mask;
-	float *rssi_sum = &chan_state->rssi_sum;
-	uint8_t *rssi_num = &chan_state->rssi_num;
-	int32_t *toa256_sum = &chan_state->toa256_sum;
-	uint8_t *toa_num = &chan_state->toa_num;
-	int32_t *ci_cb_sum = &chan_state->ci_cb_sum;
-	uint8_t *ci_cb_num = &chan_state->ci_cb_num;
-	uint8_t l2[GSM_MACBLOCK_LEN], l2_len;
-	int n_errors = 0;
-	int n_bits_total = 0;
-	int16_t lqual_cb;
-	uint16_t ber10k;
-	int rc;
-
-	/* If handover RACH detection is turned on, treat this burst as an Access Burst.
-	 * Handle NOPE.ind as usually to ensure proper Uplink measurement reporting. */
-	if (chan_state->ho_rach_detect == 1 && ~bi->flags & TRX_BI_F_NOPE_IND)
-		return rx_rach_fn(l1t, chan, bid, bi);
-
-	LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
-	       "Received Data, bid=%u\n", bid);
-
-	/* allocate burst memory, if not already */
-	if (!*bursts_p) {
-		*bursts_p = talloc_zero_size(tall_bts_ctx, 464);
-		if (!*bursts_p)
-			return -ENOMEM;
-	}
-
-	/* clear burst & store frame number of first burst */
-	if (bid == 0) {
-		memset(*bursts_p, 0, 464);
-		*mask = 0x0;
-		*first_fn = bi->fn;
-		*rssi_sum = 0;
-		*rssi_num = 0;
-		*toa256_sum = 0;
-		*toa_num = 0;
-		*ci_cb_sum = 0;
-		*ci_cb_num = 0;
-	}
-
-	/* update mask + RSSI */
-	*mask |= (1 << bid);
-	*rssi_sum += bi->rssi;
-	(*rssi_num)++;
-	*toa256_sum += bi->toa256;
-	(*toa_num)++;
-
-	/* C/I: Carrier-to-Interference ratio (in centiBels) */
-	if (bi->flags & TRX_BI_F_CI_CB) {
-		*ci_cb_sum += bi->ci_cb;
-		(*ci_cb_num)++;
-	}
-
-	/* Copy burst to buffer of 4 bursts. If the burst indication contains
-	 * no data, ensure that the buffer does not stay uninitialized */
-	burst = *bursts_p + bid * 116;
-	if (bi->burst_len > 0) {
-		memcpy(burst, bi->burst + 3, 58);
-		memcpy(burst + 58, bi->burst + 87, 58);
-	} else
-		memset(burst, 0, 58 * 2);
-
-	/* wait until complete set of bursts */
-	if (bid != 3)
-		return 0;
-
-	/* check for complete set of bursts */
-	if ((*mask & 0xf) != 0xf) {
-		LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
-			"Received incomplete data (%u/%u)\n",
-			bi->fn % l1ts->mf_period, l1ts->mf_period);
-
-		/* we require first burst to have correct FN */
-		if (!(*mask & 0x1)) {
-			*mask = 0x0;
-			return 0;
-		}
-	}
-	*mask = 0x0;
-
-	/* decode */
-	rc = gsm0503_xcch_decode(l2, *bursts_p, &n_errors, &n_bits_total);
-	if (rc) {
-		LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
-			"Received bad data (%u/%u)\n",
-			bi->fn % l1ts->mf_period, l1ts->mf_period);
-		l2_len = 0;
-	} else
-		l2_len = GSM_MACBLOCK_LEN;
-
-	lqual_cb = *ci_cb_num ? (*ci_cb_sum / *ci_cb_num) : 0;
-	ber10k = compute_ber10k(n_bits_total, n_errors);
-	return _sched_compose_ph_data_ind(l1t, bi->tn, *first_fn,
-					  chan, l2, l2_len,
-					  *rssi_sum / *rssi_num,
-					  *toa256_sum / *toa_num,
-					  lqual_cb, ber10k,
-					  PRES_INFO_UNKNOWN);
-}
-
-/*! \brief a single PDTCH burst was received by the PHY, process it */
-int rx_pdtch_fn(struct l1sched_trx *l1t, enum trx_chan_type chan,
-	       uint8_t bid, const struct trx_ul_burst_ind *bi)
-{
-	struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, bi->tn);
-	struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
-	sbit_t *burst, **bursts_p = &chan_state->ul_bursts;
-	uint32_t *first_fn = &chan_state->ul_first_fn;
-	uint8_t *mask = &chan_state->ul_mask;
-	float *rssi_sum = &chan_state->rssi_sum;
-	uint8_t *rssi_num = &chan_state->rssi_num;
-	int32_t *toa256_sum = &chan_state->toa256_sum;
-	uint8_t *toa_num = &chan_state->toa_num;
-	int32_t *ci_cb_sum = &chan_state->ci_cb_sum;
-	uint8_t *ci_cb_num = &chan_state->ci_cb_num;
-	uint8_t l2[EGPRS_0503_MAX_BYTES];
-	int n_errors = 0;
-	int n_bursts_bits = 0;
-	int n_bits_total = 0;
-	int16_t lqual_cb;
-	uint16_t ber10k;
-	int rc;
-
-	LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
-		"Received PDTCH bid=%u\n", bid);
-
-	/* allocate burst memory, if not already */
-	if (!*bursts_p) {
-		*bursts_p = talloc_zero_size(tall_bts_ctx,
-					     GSM0503_EGPRS_BURSTS_NBITS);
-		if (!*bursts_p)
-			return -ENOMEM;
-	}
-
-	/* clear burst */
-	if (bid == 0) {
-		memset(*bursts_p, 0, GSM0503_EGPRS_BURSTS_NBITS);
-		*mask = 0x0;
-		*first_fn = bi->fn;
-		*rssi_sum = 0;
-		*rssi_num = 0;
-		*toa256_sum = 0;
-		*toa_num = 0;
-		*ci_cb_sum = 0;
-		*ci_cb_num = 0;
-	}
-
-	/* update mask + rssi */
-	*mask |= (1 << bid);
-	*rssi_sum += bi->rssi;
-	(*rssi_num)++;
-	*toa256_sum += bi->toa256;
-	(*toa_num)++;
-
-	/* C/I: Carrier-to-Interference ratio (in centiBels) */
-	if (bi->flags & TRX_BI_F_CI_CB) {
-		*ci_cb_sum += bi->ci_cb;
-		(*ci_cb_num)++;
-	}
-
-	/* copy burst to buffer of 4 bursts */
-	if (bi->burst_len == EGPRS_BURST_LEN) {
-		burst = *bursts_p + bid * 348;
-		memcpy(burst, bi->burst + 9, 174);
-		memcpy(burst + 174, bi->burst + 261, 174);
-		n_bursts_bits = GSM0503_EGPRS_BURSTS_NBITS;
-	} else {
-		burst = *bursts_p + bid * 116;
-		memcpy(burst, bi->burst + 3, 58);
-		memcpy(burst + 58, bi->burst + 87, 58);
-		n_bursts_bits = GSM0503_GPRS_BURSTS_NBITS;
-	}
-
-	/* wait until complete set of bursts */
-	if (bid != 3)
-		return 0;
-
-	/* check for complete set of bursts */
-	if ((*mask & 0xf) != 0xf) {
-		LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
-			"Received incomplete frame (%u/%u)\n",
-			bi->fn % l1ts->mf_period, l1ts->mf_period);
-	}
-	*mask = 0x0;
-
-	/*
-	 * Attempt to decode EGPRS bursts first. For 8-PSK EGPRS this is all we
-	 * do. Attempt GPRS decoding on EGPRS failure. If the burst is GPRS,
-	 * then we incur decoding overhead of 31 bits on the Type 3 EGPRS
-	 * header, which is tolerable.
-	 */
-	rc = gsm0503_pdtch_egprs_decode(l2, *bursts_p, n_bursts_bits,
-				NULL, &n_errors, &n_bits_total);
-
-	if ((bi->burst_len == GSM_BURST_LEN) && (rc < 0)) {
-		rc = gsm0503_pdtch_decode(l2, *bursts_p, NULL,
-				  &n_errors, &n_bits_total);
-	}
-
-	if (rc <= 0) {
-		LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
-			"Received bad PDTCH (%u/%u)\n",
-			bi->fn % l1ts->mf_period, l1ts->mf_period);
-		return 0;
-	}
-
-	lqual_cb = *ci_cb_num ? (*ci_cb_sum / *ci_cb_num) : 0;
-	ber10k = compute_ber10k(n_bits_total, n_errors);
-	return _sched_compose_ph_data_ind(l1t, bi->tn,
-					  *first_fn, chan, l2, rc,
-					  *rssi_sum / *rssi_num,
-					  *toa256_sum / *toa_num,
-					  lqual_cb, ber10k,
-					  PRES_INFO_BOTH);
-}
-
-/*! \brief a single TCH/F burst was received by the PHY, process it */
-int rx_tchf_fn(struct l1sched_trx *l1t, enum trx_chan_type chan,
-	       uint8_t bid, const struct trx_ul_burst_ind *bi)
-{
-	struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, bi->tn);
-	struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
-	sbit_t *burst, **bursts_p = &chan_state->ul_bursts;
-	uint8_t *mask = &chan_state->ul_mask;
-	uint8_t rsl_cmode = chan_state->rsl_cmode;
-	uint8_t tch_mode = chan_state->tch_mode;
-	uint8_t tch_data[128]; /* just to be safe */
-	int rc, amr = 0;
-	int n_errors = 0;
-	int n_bits_total = 0;
-	bool bfi_flag = false;
-	struct gsm_lchan *lchan =
-		get_lchan_by_chan_nr(l1t->trx, trx_chan_desc[chan].chan_nr | bi->tn);
-	unsigned int fn_begin;
-	uint16_t ber10k;
-	uint8_t is_sub = 0;
-	uint8_t ft;
-
-	/* If handover RACH detection is turned on, treat this burst as an Access Burst.
-	 * Handle NOPE.ind as usually to ensure proper Uplink measurement reporting. */
-	if (chan_state->ho_rach_detect == 1 && ~bi->flags & TRX_BI_F_NOPE_IND)
-		return rx_rach_fn(l1t, chan, bid, bi);
-
-	LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
-	       "Received TCH/F, bid=%u\n", bid);
-
-	/* allocate burst memory, if not already */
-	if (!*bursts_p) {
-		*bursts_p = talloc_zero_size(tall_bts_ctx, 928);
-		if (!*bursts_p)
-			return -ENOMEM;
-	}
-
-	/* clear burst */
-	if (bid == 0) {
-		memset(*bursts_p + 464, 0, 464);
-		*mask = 0x0;
-	}
-
-	/* update mask */
-	*mask |= (1 << bid);
-
-	/* copy burst to end of buffer of 8 bursts */
-	burst = *bursts_p + bid * 116 + 464;
-	if (bi->burst_len > 0) {
-		memcpy(burst, bi->burst + 3, 58);
-		memcpy(burst + 58, bi->burst + 87, 58);
-	} else
-		memset(burst, 0, 116);
-
-	/* wait until complete set of bursts */
-	if (bid != 3)
-		return 0;
-
-	/* check for complete set of bursts */
-	if ((*mask & 0xf) != 0xf) {
-		LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
-			"Received incomplete frame (%u/%u)\n",
-			bi->fn % l1ts->mf_period, l1ts->mf_period);
-	}
-	*mask = 0x0;
-
-	/* decode
-	 * also shift buffer by 4 bursts for interleaving */
-	switch ((rsl_cmode != RSL_CMOD_SPD_SPEECH) ? GSM48_CMODE_SPEECH_V1
-								: tch_mode) {
-	case GSM48_CMODE_SPEECH_V1: /* FR */
-		rc = gsm0503_tch_fr_decode(tch_data, *bursts_p, 1, 0, &n_errors, &n_bits_total);
-		if (rc >= 0)
-			lchan_set_marker(osmo_fr_check_sid(tch_data, rc), lchan); /* DTXu */
-		break;
-	case GSM48_CMODE_SPEECH_EFR: /* EFR */
-		rc = gsm0503_tch_fr_decode(tch_data, *bursts_p, 1, 1, &n_errors, &n_bits_total);
-		break;
-	case GSM48_CMODE_SPEECH_AMR: /* AMR */
-		/* the first FN 0,8,17 defines that CMI is included in frame,
-		 * the first FN 4,13,21 defines that CMR is included in frame.
-		 * NOTE: A frame ends 7 FN after start.
-		 */
-
-		/* The AFS_ONSET frame itself does not result into an RTP frame
-		 * since it only contains a recognition pattern that marks the
-		 * end of the DTX interval. To mark the end of the DTX interval
-		 * in the RTP stream as well, the voice frame after the
-		 * AFS_ONSET frame is used. */
-		if (chan_state->amr_last_dtx == AFS_ONSET)
-			lchan_set_marker(false, lchan);
-
-		/* we store tch_data + 2 header bytes, the amr variable set to
-		 * 2 will allow us to skip the first 2 bytes in case we did
-		 * receive an FACCH frame instead of a voice frame (we do not
-		 * know this before we actually decode the frame) */
-		amr = 2;
-		rc = gsm0503_tch_afs_decode_dtx(tch_data + amr, *bursts_p,
-			(((bi->fn + 26 - 7) % 26) >> 2) & 1, chan_state->codec,
-			chan_state->codecs, &chan_state->ul_ft,
-			&chan_state->ul_cmr, &n_errors, &n_bits_total, &chan_state->amr_last_dtx);
-
-		/* Tag all frames that are not regular AMR voice frames as
-		 * SUB-Frames */
-		if (chan_state->amr_last_dtx != AMR_OTHER) {
-			LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
-			       "Received AMR SID frame: %s\n",
-			       gsm0503_amr_dtx_frame_name(chan_state->amr_last_dtx));
-			is_sub = 1;
-		}
-
-		/* The occurrence of the following frames indicates that we
-		 * are either at the beginning or in the middle of a talk
-		 * spurt. We update the SID status accordingly, but we do
-		 * not want the marker to be set, since this must only
-		 * happen when the talk spurt is over (see above) */
-		switch (chan_state->amr_last_dtx) {
-		case AFS_SID_FIRST:
-		case AFS_SID_UPDATE:
-		case AFS_SID_UPDATE_CN:
-			lchan_set_marker(true, lchan);
-			lchan->rtp_tx_marker = false;
-			break;
-		}
-
-		if (rc)
-			trx_loop_amr_input(l1t,
-				trx_chan_desc[chan].chan_nr | bi->tn, chan_state,
-				n_errors, n_bits_total);
-		/* only good speech frames get rtp header */
-		if (rc != GSM_MACBLOCK_LEN && rc >= 4) {
-			if (chan_state->amr_last_dtx == AMR_OTHER) {
-				ft = chan_state->codec[chan_state->ul_cmr];
-			} else {
-				/* SID frames will always get Frame Type Index 8 (AMR_SID) */
-				ft = AMR_SID;
-			}
-			rc = osmo_amr_rtp_enc(tch_data,
-				chan_state->codec[chan_state->ul_cmr],
-			        ft, AMR_GOOD);
-		}
-
-		break;
-	default:
-		LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn,
-			"TCH mode %u invalid, please fix!\n",
-			tch_mode);
-		return -EINVAL;
-	}
-	memcpy(*bursts_p, *bursts_p + 464, 464);
-
-	/* Check if the frame is bad */
-	if (rc < 0) {
-		LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
-			"Received bad data (%u/%u)\n",
-			bi->fn % l1ts->mf_period, l1ts->mf_period);
-		bfi_flag = true;
-	} else if (rc < 4) {
-		LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
-			"Received bad data (%u/%u) with invalid codec mode %d\n",
-			bi->fn % l1ts->mf_period, l1ts->mf_period, rc);
-		bfi_flag = true;
-	}
-
-	if (rc != GSM_MACBLOCK_LEN && lchan->ecu_state)
-		osmo_ecu_frame_in(lchan->ecu_state, bfi_flag, tch_data, rc);
-
-	ber10k = compute_ber10k(n_bits_total, n_errors);
-	if (bfi_flag)
-		goto bfi;
-
-	/* FACCH */
-	if (rc == GSM_MACBLOCK_LEN) {
-		fn_begin = gsm0502_fn_remap(bi->fn, FN_REMAP_FACCH_F);
-		_sched_compose_ph_data_ind(l1t, bi->tn, fn_begin, chan,
-			tch_data + amr, GSM_MACBLOCK_LEN,
-			/* FIXME: AVG RSSI and ToA256 */
-			bi->rssi, bi->toa256,
-			0 /* FIXME: AVG C/I */,
-			ber10k, PRES_INFO_UNKNOWN);
-bfi:
-		if (rsl_cmode == RSL_CMOD_SPD_SPEECH) {
-			/* indicate bad frame */
-			if (lchan->tch.dtx.ul_sid) {
-				/* DTXu: pause in progress. Push empty payload to upper layers */
-				rc = 0;
-				goto compose_l1sap;
-			}
-
-			/* If there is an ECU active on this channel, use its output */
-			if (lchan->ecu_state) {
-				rc = osmo_ecu_frame_out(lchan->ecu_state, tch_data);
-				if (rc >= 0) /* Otherwise we send a BFI */
-					goto compose_l1sap;
-			}
-
-			switch (tch_mode) {
-			case GSM48_CMODE_SPEECH_V1: /* FR */
-				memset(tch_data, 0, GSM_FR_BYTES);
-				tch_data[0] = 0xd0;
-				rc = GSM_FR_BYTES;
-				break;
-			case GSM48_CMODE_SPEECH_EFR: /* EFR */
-				memset(tch_data, 0, GSM_EFR_BYTES);
-				tch_data[0] = 0xc0;
-				rc = GSM_EFR_BYTES;
-				break;
-			case GSM48_CMODE_SPEECH_AMR: /* AMR */
-				rc = osmo_amr_rtp_enc(tch_data,
-					chan_state->codec[chan_state->dl_cmr],
-					chan_state->codec[chan_state->dl_ft],
-					AMR_BAD);
-				if (rc < 2) {
-					LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn,
-					       "Failed to encode AMR_BAD frame (rc=%d), "
-					       "not sending BFI\n", rc);
-					return -EINVAL;
-				}
-				memset(tch_data + 2, 0, rc - 2);
-				break;
-			default:
-				LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn,
-					"TCH mode %u invalid, please fix!\n", tch_mode);
-				return -EINVAL;
-			}
-		}
-	}
-
-	if (rsl_cmode != RSL_CMOD_SPD_SPEECH)
-		return 0;
-
-	/* TCH or BFI */
-compose_l1sap:
-	fn_begin = gsm0502_fn_remap(bi->fn, FN_REMAP_TCH_F);
-	return _sched_compose_tch_ind(l1t, bi->tn, fn_begin, chan,
-				      tch_data, rc, bi->toa256, ber10k, bi->rssi, is_sub);
-}
-
-/*! \brief a single TCH/H burst was received by the PHY, process it */
-int rx_tchh_fn(struct l1sched_trx *l1t, enum trx_chan_type chan,
-	       uint8_t bid, const struct trx_ul_burst_ind *bi)
-{
-	struct l1sched_ts *l1ts = l1sched_trx_get_ts(l1t, bi->tn);
-	struct l1sched_chan_state *chan_state = &l1ts->chan_state[chan];
-	sbit_t *burst, **bursts_p = &chan_state->ul_bursts;
-	uint8_t *mask = &chan_state->ul_mask;
-	uint8_t rsl_cmode = chan_state->rsl_cmode;
-	uint8_t tch_mode = chan_state->tch_mode;
-	uint8_t tch_data[128]; /* just to be safe */
-	int rc, amr = 0;
-	int n_errors = 0;
-	int n_bits_total = 0;
-	bool bfi_flag = false;
-	struct gsm_lchan *lchan =
-		get_lchan_by_chan_nr(l1t->trx, trx_chan_desc[chan].chan_nr | bi->tn);
-	/* Note on FN-10: If we are at FN 10, we decoded an even aligned
-	 * TCH/FACCH frame, because our burst buffer carries 6 bursts.
-	 * Even FN ending at: 10,11,19,20,2,3
-	 */
-	int fn_is_odd = (((bi->fn + 26 - 10) % 26) >> 2) & 1;
-	unsigned int fn_begin;
-	uint16_t ber10k;
-	uint8_t is_sub = 0;
-	uint8_t ft;
-
-	/* If handover RACH detection is turned on, treat this burst as an Access Burst.
-	 * Handle NOPE.ind as usually to ensure proper Uplink measurement reporting. */
-	if (chan_state->ho_rach_detect == 1 && ~bi->flags & TRX_BI_F_NOPE_IND)
-		return rx_rach_fn(l1t, chan, bid, bi);
-
-	LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
-		"Received TCH/H, bid=%u\n", bid);
-
-	/* allocate burst memory, if not already */
-	if (!*bursts_p) {
-		*bursts_p = talloc_zero_size(tall_bts_ctx, 696);
-		if (!*bursts_p)
-			return -ENOMEM;
-	}
-
-	/* clear burst */
-	if (bid == 0) {
-		memset(*bursts_p + 464, 0, 232);
-		*mask = 0x0;
-	}
-
-	/* update mask */
-	*mask |= (1 << bid);
-
-	/* copy burst to end of buffer of 6 bursts */
-	burst = *bursts_p + bid * 116 + 464;
-	if (bi->burst_len > 0) {
-		memcpy(burst, bi->burst + 3, 58);
-		memcpy(burst + 58, bi->burst + 87, 58);
-	} else
-		memset(burst, 0, 116);
-
-	/* wait until complete set of bursts */
-	if (bid != 1)
-		return 0;
-
-	/* check for complete set of bursts */
-	if ((*mask & 0x3) != 0x3) {
-		LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
-			"Received incomplete frame (%u/%u)\n",
-			bi->fn % l1ts->mf_period, l1ts->mf_period);
-	}
-	*mask = 0x0;
-
-	/* skip second of two TCH frames of FACCH was received */
-	if (chan_state->ul_ongoing_facch) {
-		chan_state->ul_ongoing_facch = 0;
-		memcpy(*bursts_p, *bursts_p + 232, 232);
-		memcpy(*bursts_p + 232, *bursts_p + 464, 232);
-		ber10k = 0;
-		goto bfi;
-	}
-
-	/* decode
-	 * also shift buffer by 4 bursts for interleaving */
-	switch ((rsl_cmode != RSL_CMOD_SPD_SPEECH) ? GSM48_CMODE_SPEECH_V1
-								: tch_mode) {
-	case GSM48_CMODE_SPEECH_V1: /* HR or signalling */
-		/* Note on FN-10: If we are at FN 10, we decoded an even aligned
-		 * TCH/FACCH frame, because our burst buffer carries 6 bursts.
-		 * Even FN ending at: 10,11,19,20,2,3
-		 */
-		rc = gsm0503_tch_hr_decode(tch_data, *bursts_p,
-			fn_is_odd, &n_errors, &n_bits_total);
-		if (rc >= 0) /* DTXu */
-			lchan_set_marker(osmo_hr_check_sid(tch_data, rc), lchan);
-		break;
-	case GSM48_CMODE_SPEECH_AMR: /* AMR */
-		/* the first FN 0,8,17 or 1,9,18 defines that CMI is included
-		 * in frame, the first FN 4,13,21 or 5,14,22 defines that CMR
-		 * is included in frame.
-		 */
-
-		/* See comment in function rx_tchf_fn() */
-		switch (chan_state->amr_last_dtx) {
-		case AHS_ONSET:
-		case AHS_SID_FIRST_INH:
-		case AHS_SID_UPDATE_INH:
-			lchan_set_marker(false, lchan);
-			break;
-		}
-
-		/* See comment in function rx_tchf_fn() */
-		amr = 2;
-		rc = gsm0503_tch_ahs_decode_dtx(tch_data + amr, *bursts_p,
-			fn_is_odd, fn_is_odd, chan_state->codec,
-			chan_state->codecs, &chan_state->ul_ft,
-			&chan_state->ul_cmr, &n_errors, &n_bits_total, &chan_state->amr_last_dtx);
-
-		/* Tag all frames that are not regular AMR voice frames
-		   as SUB-Frames */
-		if (chan_state->amr_last_dtx != AMR_OTHER) {
-			LOGL1S(DL1P, LOGL_DEBUG, l1t, bi->tn, chan, bi->fn,
-			       "Received AMR SID frame: %s\n",
-			       gsm0503_amr_dtx_frame_name(chan_state->amr_last_dtx));
-			is_sub = 1;
-		}
-
-		/* See comment in function rx_tchf_fn() */
-		switch (chan_state->amr_last_dtx) {
-		case AHS_SID_FIRST_P1:
-		case AHS_SID_FIRST_P2:
-		case AHS_SID_UPDATE:
-		case AHS_SID_UPDATE_CN:
-			lchan_set_marker(true, lchan);
-			lchan->rtp_tx_marker = false;
-			break;
-		}
-
-		if (rc)
-			trx_loop_amr_input(l1t,
-				trx_chan_desc[chan].chan_nr | bi->tn, chan_state,
-				n_errors, n_bits_total);
-
-		/* only good speech frames get rtp header */
-		if (rc != GSM_MACBLOCK_LEN && rc >= 4) {
-			if (chan_state->amr_last_dtx == AMR_OTHER) {
-				ft = chan_state->codec[chan_state->ul_cmr];
-			} else {
-				/* SID frames will always get Frame Type Index 8 (AMR_SID) */
-				ft = AMR_SID;
-			}
-			rc = osmo_amr_rtp_enc(tch_data,
-				chan_state->codec[chan_state->ul_cmr],
-			        ft, AMR_GOOD);
-		}
-
-		break;
-	default:
-		LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn,
-			"TCH mode %u invalid, please fix!\n",
-			tch_mode);
-		return -EINVAL;
-	}
-	memcpy(*bursts_p, *bursts_p + 232, 232);
-	memcpy(*bursts_p + 232, *bursts_p + 464, 232);
-	ber10k = compute_ber10k(n_bits_total, n_errors);
-
-
-	/* Check if the frame is bad */
-	if (rc < 0) {
-		LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
-			"Received bad data (%u/%u)\n",
-			bi->fn % l1ts->mf_period, l1ts->mf_period);
-		bfi_flag = true;
-	} else if (rc < 4) {
-		LOGL1S(DL1P, LOGL_NOTICE, l1t, bi->tn, chan, bi->fn,
-			"Received bad data (%u/%u) with invalid codec mode %d\n",
-			bi->fn % l1ts->mf_period, l1ts->mf_period, rc);
-		bfi_flag = true;
-	}
-
-	if (rc != GSM_MACBLOCK_LEN && lchan->ecu_state)
-		osmo_ecu_frame_in(lchan->ecu_state, bfi_flag, tch_data, rc);
-
-	if (bfi_flag)
-		goto bfi;
-
-	/* FACCH */
-	if (rc == GSM_MACBLOCK_LEN) {
-		chan_state->ul_ongoing_facch = 1;
-		uint16_t ber10k = compute_ber10k(n_bits_total, n_errors);
-		if (lchan->nr == 0)
-			fn_begin = gsm0502_fn_remap(bi->fn, FN_REMAP_FACCH_H0);
-		else
-			fn_begin = gsm0502_fn_remap(bi->fn, FN_REMAP_FACCH_H1);
-		_sched_compose_ph_data_ind(l1t, bi->tn, fn_begin, chan,
-			tch_data + amr, GSM_MACBLOCK_LEN,
-			/* FIXME: AVG both RSSI and ToA */
-			bi->rssi, bi->toa256,
-			0 /* FIXME: AVG C/I */,
-			ber10k, PRES_INFO_UNKNOWN);
-bfi:
-		/* FIXME: a FACCH/H frame replaces two speech frames,
-		 * so we actually need to send two bad frame indications! */
-		if (rsl_cmode == RSL_CMOD_SPD_SPEECH) {
-			/* indicate bad frame */
-			if (lchan->tch.dtx.ul_sid) {
-				/* DTXu: pause in progress. Push empty payload to upper layers */
-				rc = 0;
-				goto compose_l1sap;
-			}
-
-			/* If there is an ECU active on this channel, use its output */
-			if (lchan->ecu_state) {
-				rc = osmo_ecu_frame_out(lchan->ecu_state, tch_data);
-				if (rc >= 0) /* Otherwise we send a BFI */
-					goto compose_l1sap;
-			}
-
-			switch (tch_mode) {
-			case GSM48_CMODE_SPEECH_V1: /* HR */
-				tch_data[0] = 0x70; /* F = 0, FT = 111 */
-				memset(tch_data + 1, 0, 14);
-				rc = 15;
-				break;
-			case GSM48_CMODE_SPEECH_AMR: /* AMR */
-				rc = osmo_amr_rtp_enc(tch_data,
-					chan_state->codec[chan_state->dl_cmr],
-					chan_state->codec[chan_state->dl_ft],
-					AMR_BAD);
-				if (rc < 2) {
-					LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn,
-					       "Failed to encode AMR_BAD frame (rc=%d), "
-					       "not sending BFI\n", rc);
-					return -EINVAL;
-				}
-				memset(tch_data + 2, 0, rc - 2);
-				break;
-			default:
-				LOGL1S(DL1P, LOGL_ERROR, l1t, bi->tn, chan, bi->fn,
-					"TCH mode %u invalid, please fix!\n", tch_mode);
-				return -EINVAL;
-			}
-		}
-	}
-
-	if (rsl_cmode != RSL_CMOD_SPD_SPEECH)
-		return 0;
-
-compose_l1sap:
-	/* TCH or BFI */
-	/* Note on FN 19 or 20: If we received the last burst of a frame,
-	 * it actually starts at FN 8 or 9. A burst starting there, overlaps
-	 * with the slot 12, so an extra FN must be subtracted to get correct
-	 * start of frame.
-	 */
-	if (lchan->nr == 0)
-		fn_begin = gsm0502_fn_remap(bi->fn, FN_REMAP_TCH_H0);
-	else
-		fn_begin = gsm0502_fn_remap(bi->fn, FN_REMAP_TCH_H1);
-	return _sched_compose_tch_ind(l1t, bi->tn, fn_begin, chan,
-				      tch_data, rc, bi->toa256, ber10k, bi->rssi, is_sub);
-}
-
 /* schedule all frames of all TRX for given FN */
 static int trx_sched_fn(struct gsm_bts *bts, uint32_t fn)
 {

-- 
To view, visit https://gerrit.osmocom.org/c/osmo-bts/+/18820
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings

Gerrit-Project: osmo-bts
Gerrit-Branch: master
Gerrit-Change-Id: Ie5663fd90596b4800a4546675a323250bbb24c80
Gerrit-Change-Number: 18820
Gerrit-PatchSet: 1
Gerrit-Owner: fixeria <vyanitskiy at sysmocom.de>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20200613/1ccf3b66/attachment.htm>


More information about the gerrit-log mailing list