aboutsummaryrefslogtreecommitdiffstats
path: root/src/osmo-bts-trx/sched_lchan_tchf.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/osmo-bts-trx/sched_lchan_tchf.c')
-rw-r--r--src/osmo-bts-trx/sched_lchan_tchf.c677
1 files changed, 677 insertions, 0 deletions
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 00000000..5a3e80ac
--- /dev/null
+++ b/src/osmo-bts-trx/sched_lchan_tchf.c
@@ -0,0 +1,677 @@
+/*
+ * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ * (C) 2015-2017 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2020-2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <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/coding/gsm0503_coding.h>
+#include <osmocom/coding/gsm0503_amr_dtx.h>
+
+#include <osmocom/netif/amr.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 <amr_loop.h>
+
+/* 3GPP TS 45.009, table 3.2.1.3-{1,3}: AMR on Uplink TCH/F.
+ *
+ * +---+---+---+---+---+---+---+---+
+ * | a | b | c | d | e | f | g | h | Burst 'a' received first
+ * +---+---+---+---+---+---+---+---+
+ * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Speech/FACCH frame (bursts 'a' .. 'h')
+ *
+ * TDMA frame number of burst 'h' is always used as the table index. */
+static const uint8_t sched_tchf_ul_amr_cmi_map[26] = {
+ [7] = 1, /* TCH/F: a=0 / h=7 */
+ [16] = 1, /* TCH/F: a=8 / h=16 */
+ [24] = 1, /* TCH/F: a=17 / h=24 */
+};
+
+/* TDMA frame number of burst 'a' should be used as the table index. */
+static const uint8_t sched_tchf_dl_amr_cmi_map[26] = {
+ [4] = 1, /* TCH/F: a=4 */
+ [13] = 1, /* TCH/F: a=13 */
+ [21] = 1, /* TCH/F: a=21 */
+};
+
+extern const uint8_t sched_tchh_dl_amr_cmi_map[26];
+
+static int decode_fr_facch(struct l1sched_ts *l1ts,
+ const struct trx_ul_burst_ind *bi)
+{
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[bi->chan];
+ const sbit_t *bursts_p = chan_state->ul_bursts;
+ struct l1sched_meas_set meas_avg;
+ uint8_t data[GSM_MACBLOCK_LEN];
+ int n_errors, n_bits_total;
+ int rc;
+
+ rc = gsm0503_tch_fr_facch_decode(&data[0], BUFTAIL8(bursts_p),
+ &n_errors, &n_bits_total);
+ if (rc != GSM_MACBLOCK_LEN)
+ return rc;
+
+ /* average measurements of the last 8 bursts, obtain TDMA Fn of the first burst */
+ trx_sched_meas_avg(chan_state, &meas_avg, SCHED_MEAS_AVG_M_S8N8);
+
+ _sched_compose_ph_data_ind(l1ts, meas_avg.fn, bi->chan,
+ &data[0], GSM_MACBLOCK_LEN,
+ compute_ber10k(n_bits_total, n_errors),
+ meas_avg.rssi,
+ meas_avg.toa256,
+ meas_avg.ci_cb,
+ PRES_INFO_UNKNOWN);
+ return GSM_MACBLOCK_LEN;
+}
+
+/* Process a single Uplink TCH/F burst received by the PHY.
+ * This function is visualized in file 'doc/trx_sched_tch.txt'. */
+int rx_tchf_fn(struct l1sched_ts *l1ts, const struct trx_ul_burst_ind *bi)
+{
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[bi->chan];
+ struct gsm_lchan *lchan = chan_state->lchan;
+ sbit_t *burst, *bursts_p = chan_state->ul_bursts;
+ uint32_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[290]; /* large enough to hold 290 unpacked bits for CSD */
+ enum sched_meas_avg_mode meas_avg_mode = SCHED_MEAS_AVG_M_S8N8;
+ struct l1sched_meas_set meas_avg;
+ int rc, amr = 0;
+ int n_errors = 0;
+ int n_bits_total = 0;
+ unsigned int fn_begin;
+ uint16_t ber10k;
+ uint8_t is_sub = 0;
+ uint8_t ft;
+ bool amr_is_cmr;
+
+ /* 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(l1ts, bi);
+
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi, "Received TCH/F, bid=%u\n", bi->bid);
+
+ /* shift the buffer by 4 bursts leftwards */
+ if (bi->bid == 0) {
+ memmove(BUFPOS(bursts_p, 0), BUFPOS(bursts_p, 4), 20 * BPLEN);
+ memset(BUFPOS(bursts_p, 20), 0, 4 * BPLEN);
+ *mask = *mask << 4;
+ }
+
+ /* update mask */
+ *mask |= (1 << bi->bid);
+
+ /* store measurements */
+ trx_sched_meas_push(chan_state, bi);
+
+ /* copy burst to end of buffer of 24 bursts */
+ burst = BUFPOS(bursts_p, 20 + bi->bid);
+ if (bi->burst_len > 0) {
+ memcpy(burst, bi->burst + 3, 58);
+ memcpy(burst + 58, bi->burst + 87, 58);
+ }
+
+ /* wait until complete set of bursts */
+ if (bi->bid != 3)
+ return 0;
+
+ /* fill up the burst buffer so that we have 8 bursts in there */
+ if (OSMO_UNLIKELY((*mask & 0xff) != 0xff)) {
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi,
+ "UL burst buffer is not filled up: mask=0x%02x != 0xff\n",
+ *mask);
+ return 0; /* TODO: send BFI */
+ }
+
+ /* TCH/F: speech and signalling frames are interleaved over 8 bursts, while
+ * CSD frames are interleaved over 22 bursts. Unless we're in CSD mode,
+ * decode only the last 8 bursts to avoid introducing additional delays. */
+ switch (tch_mode) {
+ case GSM48_CMODE_SIGN:
+ case GSM48_CMODE_SPEECH_V1: /* FR */
+ rc = gsm0503_tch_fr_decode(tch_data, BUFTAIL8(bursts_p),
+ 1, 0, &n_errors, &n_bits_total);
+ if (rc == GSM_FR_BYTES) /* only for valid *speech* frames */
+ lchan_set_marker(osmo_fr_is_any_sid(tch_data), lchan); /* DTXu */
+ break;
+ case GSM48_CMODE_SPEECH_EFR: /* EFR */
+ rc = gsm0503_tch_fr_decode(tch_data, BUFTAIL8(bursts_p),
+ 1, 1, &n_errors, &n_bits_total);
+ if (rc == GSM_EFR_BYTES) /* only for valid *speech* frames */
+ lchan_set_marker(osmo_efr_is_any_sid(tch_data), lchan); /* DTXu */
+ 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.
+ */
+ amr_is_cmr = !sched_tchf_ul_amr_cmi_map[bi->fn % 26];
+
+ /* 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);
+
+ /* Store AMR payload in tch-data with an offset of 2 bytes, so
+ * that we can easily prepend/fill the RTP AMR header (struct
+ * amr_hdr) with osmo_amr_rtp_enc() later on. The amr variable
+ * is used far below to account for the decoded offset in case
+ * we receive an FACCH frame instead of a voice frame (we
+ * do not know this before we actually decode the frame) */
+ amr = sizeof(struct amr_hdr);
+ rc = gsm0503_tch_afs_decode_dtx(tch_data + amr, BUFTAIL8(bursts_p),
+ amr_is_cmr, 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) {
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, bi,
+ "Received AMR DTX frame (rc=%d, BER %d/%d): %s\n",
+ rc, n_errors, n_bits_total,
+ 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;
+ }
+
+ switch (chan_state->amr_last_dtx) {
+ case AFS_SID_FIRST:
+ case AFS_SID_UPDATE_CN:
+ meas_avg_mode = SCHED_MEAS_AVG_M_S8N4;
+ break;
+ case AFS_SID_UPDATE:
+ case AFS_ONSET:
+ meas_avg_mode = SCHED_MEAS_AVG_M_S4N4;
+ break;
+ }
+
+ /* 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_ft];
+ } 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;
+ /* CSD (TCH/F9.6): 12.0 kbit/s radio interface rate */
+ case GSM48_CMODE_DATA_12k0:
+ /* FACCH/F does not steal TCH/F9.6 frames, but only disturbs some bits */
+ decode_fr_facch(l1ts, bi);
+ rc = gsm0503_tch_fr96_decode(&tch_data[0], BUFPOS(bursts_p, 0),
+ &n_errors, &n_bits_total);
+ meas_avg_mode = SCHED_MEAS_AVG_M_S24N22;
+ break;
+ /* CSD (TCH/F4.8): 6.0 kbit/s radio interface rate */
+ case GSM48_CMODE_DATA_6k0:
+ /* FACCH/F does not steal TCH/F4.8 frames, but only disturbs some bits */
+ decode_fr_facch(l1ts, bi);
+ rc = gsm0503_tch_fr48_decode(&tch_data[0], BUFPOS(bursts_p, 0),
+ &n_errors, &n_bits_total);
+ meas_avg_mode = SCHED_MEAS_AVG_M_S24N22;
+ break;
+ /* CSD (TCH/F2.4): 3.6 kbit/s radio interface rate */
+ case GSM48_CMODE_DATA_3k6:
+ /* TCH/F2.4 employs the same interleaving as TCH/FS (8 bursts),
+ * so FACCH/F *does* steal TCH/F2.4 frames completely. */
+ if (decode_fr_facch(l1ts, bi) == GSM_MACBLOCK_LEN)
+ return 0; /* TODO: emit BFI */
+ rc = gsm0503_tch_fr24_decode(&tch_data[0], BUFTAIL8(bursts_p),
+ &n_errors, &n_bits_total);
+ meas_avg_mode = SCHED_MEAS_AVG_M_S8N8;
+ break;
+ /* CSD (TCH/F14.4): 14.5 kbit/s radio interface rate */
+ case GSM48_CMODE_DATA_14k5:
+ /* FACCH/F does not steal TCH/F14.4 frames, but only disturbs some bits */
+ decode_fr_facch(l1ts, bi);
+ rc = gsm0503_tch_fr144_decode(&tch_data[0], BUFPOS(bursts_p, 0),
+ &n_errors, &n_bits_total);
+ meas_avg_mode = SCHED_MEAS_AVG_M_S24N22;
+ break;
+ default:
+ LOGL1SB(DL1P, LOGL_ERROR, l1ts, bi,
+ "TCH mode %u invalid, please fix!\n",
+ tch_mode);
+ return -EINVAL;
+ }
+
+ ber10k = compute_ber10k(n_bits_total, n_errors);
+
+ /* average measurements of the last N (depends on mode) bursts */
+ trx_sched_meas_avg(chan_state, &meas_avg, meas_avg_mode);
+ /* meas_avg.fn now contains TDMA frame number of the first burst */
+ fn_begin = meas_avg.fn;
+
+ if (tch_mode == GSM48_CMODE_SPEECH_AMR)
+ trx_loop_amr_input(chan_state, &meas_avg);
+
+ /* Check if the frame is bad */
+ if (rc < 4) {
+ LOGL1SB(DL1P, LOGL_NOTICE, l1ts, bi,
+ BAD_DATA_MSG_FMT "\n", BAD_DATA_MSG_ARGS);
+ rc = 0; /* this is how we signal BFI to l1sap */
+ } else if (rc == GSM_MACBLOCK_LEN) { /* FACCH/F */
+ _sched_compose_ph_data_ind(l1ts, fn_begin, bi->chan,
+ &tch_data[amr], GSM_MACBLOCK_LEN,
+ ber10k,
+ meas_avg.rssi,
+ meas_avg.toa256,
+ meas_avg.ci_cb,
+ PRES_INFO_UNKNOWN);
+
+ /* If we are in SPEECH mode we will generate a fake (BFI) TCH
+ * indication as well. This indication is needed by the higher
+ * layers, however we already have reported the measurement
+ * result for the current block together with the FACCH.
+ * To avoid reporting the same measurement result again with
+ * the fake (BFI) TCH indication we set meas_avg.rssi to zero.
+ * Doing so tells l1sap.c to ignore the measurement result. */
+ meas_avg.rssi = 0;
+ rc = 0;
+ }
+
+ if (rsl_cmode == RSL_CMOD_SPD_SIGN)
+ return 0;
+
+ /* TCH or BFI */
+ return _sched_compose_tch_ind(l1ts, fn_begin, bi->chan,
+ &tch_data[0], rc,
+ ber10k,
+ meas_avg.rssi,
+ meas_avg.toa256,
+ meas_avg.ci_cb,
+ 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 tch_dl_dequeue(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br,
+ struct msgb **msg_tch, struct msgb **msg_facch)
+{
+ struct msgb *msg1, *msg2;
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[br->chan];
+ uint8_t rsl_cmode = chan_state->rsl_cmode;
+ uint8_t tch_mode = chan_state->tch_mode;
+ struct osmo_phsap_prim *l1sap;
+
+ /* get frame and unlink from queue */
+ msg1 = _sched_dequeue_prim(l1ts, br);
+ msg2 = _sched_dequeue_prim(l1ts, br);
+ 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) {
+ LOGL1SB(DL1P, LOGL_FATAL, l1ts, br, "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) {
+ LOGL1SB(DL1P, LOGL_FATAL, l1ts, br, "FACCH twice, please FIX!\n");
+ msgb_free(msg2);
+ } else
+ *msg_tch = msg2;
+ }
+ }
+ }
+
+ /* check validity of message */
+ if (*msg_facch != NULL && msgb_l2len(*msg_facch) != GSM_MACBLOCK_LEN) {
+ LOGL1SB(DL1P, LOGL_FATAL, l1ts, br, "Prim has odd len=%u != %u\n",
+ msgb_l2len(*msg_facch), GSM_MACBLOCK_LEN);
+ /* free message */
+ msgb_free(*msg_facch);
+ *msg_facch = NULL;
+ }
+
+ /* check validity of message, get AMR ft and cmr */
+ if (*msg_tch != NULL) {
+ int len;
+ uint8_t cmr_codec;
+ int ft, i;
+ enum osmo_amr_type ft_codec;
+ enum osmo_amr_quality bfi;
+ int8_t sti, cmi;
+ bool amr_is_cmr;
+
+ if (OSMO_UNLIKELY(rsl_cmode == RSL_CMOD_SPD_SIGN)) {
+ LOGL1SB(DL1P, LOGL_NOTICE, l1ts, br, "Dropping a TCH frame, "
+ "because we are not in speech mode\n");
+ goto free_bad_msg;
+ }
+
+ switch (tch_mode) {
+ case GSM48_CMODE_SPEECH_V1: /* FR / HR */
+ if (br->chan != TRXC_TCHF) /* HR */
+ len = GSM_HR_BYTES;
+ else
+ len = GSM_FR_BYTES;
+ break;
+ case GSM48_CMODE_SPEECH_EFR: /* EFR */
+ if (br->chan != TRXC_TCHF)
+ goto inval_mode2;
+ len = GSM_EFR_BYTES;
+ break;
+ case GSM48_CMODE_SPEECH_AMR: /* AMR */
+ len = osmo_amr_rtp_dec(msgb_l2((*msg_tch)), msgb_l2len(*msg_tch),
+ &cmr_codec, &cmi, &ft_codec,
+ &bfi, &sti);
+ if (len < 0) {
+ LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, "Cannot send invalid AMR payload\n");
+ goto free_bad_msg;
+ }
+ ft = -1;
+ for (i = 0; i < chan_state->codecs; i++) {
+ if (chan_state->codec[i] == ft_codec)
+ ft = i;
+ }
+ if (ft < 0) {
+ LOGL1SB(DL1P, LOGL_ERROR, l1ts, br,
+ "Codec (FT = %d) of RTP frame not in list\n", ft_codec);
+ goto free_bad_msg;
+ }
+ if (br->chan == TRXC_TCHF)
+ amr_is_cmr = !sched_tchf_dl_amr_cmi_map[br->fn % 26];
+ else /* TRXC_TCHH_0 or TRXC_TCHH_1 */
+ amr_is_cmr = !sched_tchh_dl_amr_cmi_map[br->fn % 26];
+ if (amr_is_cmr && chan_state->dl_ft != ft) {
+ LOGL1SB(DL1P, LOGL_NOTICE, l1ts, br, "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) {
+ LOGL1SB(DL1P, LOGL_NOTICE, l1ts, br, "Transmitting 'bad AMR frame'\n");
+ goto free_bad_msg;
+ }
+ /* pull the AMR header, it's not being sent over Um */
+ (*msg_tch)->l2h += sizeof(struct amr_hdr);
+ len -= sizeof(struct amr_hdr);
+ break;
+ case GSM48_CMODE_DATA_14k5: /* TCH/F14.4 */
+ if (OSMO_UNLIKELY(br->chan != TRXC_TCHF))
+ goto inval_mode2;
+ len = 290;
+ break;
+ case GSM48_CMODE_DATA_12k0: /* TCH/F9.6 */
+ if (OSMO_UNLIKELY(br->chan != TRXC_TCHF))
+ goto inval_mode2;
+ len = 4 * 60;
+ break;
+ case GSM48_CMODE_DATA_6k0: /* TCH/[FH]4.8 */
+ if (br->chan == TRXC_TCHF)
+ len = 2 * 60;
+ else
+ len = 4 * 60;
+ break;
+ case GSM48_CMODE_DATA_3k6: /* TCH/[FH]2.4 */
+ if (br->chan == TRXC_TCHF)
+ len = 2 * 36;
+ else
+ len = 4 * 36;
+ break;
+ default:
+inval_mode2:
+ LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, "TCH mode invalid, please fix!\n");
+ goto free_bad_msg;
+ }
+ if (msgb_l2len(*msg_tch) != len) {
+ LOGL1SB(DL1P, LOGL_ERROR, l1ts, br, "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;
+ }
+ }
+}
+
+struct msgb *tch_dummy_msgb(size_t size, uint8_t pad)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc(size, __func__);
+ OSMO_ASSERT(msg != NULL);
+
+ msg->l2h = msgb_put(msg, size);
+ memset(msg->l2h, pad, size);
+
+ return msg;
+}
+
+/* obtain a to-be-transmitted TCH/F (Full Traffic Channel) burst */
+int tx_tchf_fn(struct l1sched_ts *l1ts, struct trx_dl_burst_req *br)
+{
+ struct l1sched_chan_state *chan_state = &l1ts->chan_state[br->chan];
+ uint8_t tch_mode = chan_state->tch_mode;
+ ubit_t *burst, *bursts_p = chan_state->dl_bursts;
+ uint8_t *mask = &chan_state->dl_mask;
+ struct msgb *msg_facch = NULL;
+ struct msgb *msg_tch = NULL;
+ struct msgb *msg = NULL;
+
+ /* send burst, if we already got a frame */
+ if (br->bid > 0) {
+ if ((*mask & 0x01) != 0x01)
+ return -ENOMSG;
+ goto send_burst;
+ }
+
+ *mask = *mask << 4;
+
+ /* BURST BYPASS */
+
+ /* shift buffer by 4 bursts for interleaving */
+ memmove(BUFPOS(bursts_p, 0), BUFPOS(bursts_p, 4), 20 * BPLEN);
+ memset(BUFPOS(bursts_p, 20), 0, 4 * BPLEN);
+
+ /* dequeue a TCH and/or a FACCH message to be transmitted */
+ tch_dl_dequeue(l1ts, br, &msg_tch, &msg_facch);
+ if (msg_tch == NULL && msg_facch == NULL) {
+ int rc;
+
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, br, "No TCH or FACCH prim for transmit.\n");
+ /* - If the channel mode is TCH/FS or TCH/EFS, transmit a dummy
+ * speech block with inverted CRC3, designed to induce a BFI
+ * condition in the MS receiver.
+ * - If the channel mode is TCH/AFS, transmit a dummy speech
+ * block with inverted CRC6, designed to induce a BFI
+ * condition in the MS receiver.
+ * - If the channel mode is one of the CSD modes, transmit an
+ * idle frame as described in 3GPP TS 44.021, sections 8.1.6
+ * and 10.2.3 (all data, status and E-bits set to binary '1').
+ * - In all other channel modes, transmit dummy FACCH
+ * like we always did before.
+ */
+ switch (tch_mode) {
+ case GSM48_CMODE_DATA_12k0:
+ case GSM48_CMODE_DATA_6k0:
+ case GSM48_CMODE_DATA_3k6:
+ case GSM48_CMODE_DATA_14k5:
+ break; /* see below */
+ case GSM48_CMODE_SPEECH_V1:
+ case GSM48_CMODE_SPEECH_EFR:
+ rc = gsm0503_tch_fr_encode(BUFPOS(bursts_p, 0), NULL, 0, 1);
+ if (rc == 0)
+ goto send_burst;
+ /* fall-through */
+ case GSM48_CMODE_SIGN:
+ default:
+ 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.
+ */
+ rc = gsm0503_tch_afs_encode(BUFPOS(bursts_p, 0),
+ NULL, 0,
+ !sched_tchf_dl_amr_cmi_map[br->fn % 26],
+ chan_state->codec,
+ chan_state->codecs,
+ chan_state->dl_ft,
+ chan_state->dl_cmr);
+ if (rc == 0)
+ goto send_burst;
+ }
+
+ /* TODO: use randomized padding */
+ msg_facch = tch_dummy_msgb(GSM_MACBLOCK_LEN, GSM_MACBLOCK_PADDING);
+ /* dummy LAPDm func=UI frame */
+ msg_facch->l2h[0] = 0x03;
+ msg_facch->l2h[1] = 0x03;
+ msg_facch->l2h[2] = 0x01;
+ break;
+ }
+ }
+
+ /* Unlike SACCH, FACCH has no dedicated slots on the multiframe layout.
+ * It's multiplexed together with TCH (speech or data) frames basically
+ * by replacing (stealing) their bits, either completely or partly. */
+ msg = (msg_facch != NULL) ? msg_facch : msg_tch;
+ if (msg == msg_facch)
+ chan_state->dl_facch_bursts = 8;
+
+ /* populate the buffer with bursts */
+ switch (tch_mode) {
+ case GSM48_CMODE_SIGN:
+ case GSM48_CMODE_SPEECH_V1:
+ case GSM48_CMODE_SPEECH_EFR:
+ gsm0503_tch_fr_encode(BUFPOS(bursts_p, 0), msg->l2h, msgb_l2len(msg), 1);
+ break;
+ case 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(BUFPOS(bursts_p, 0),
+ msgb_l2(msg), msgb_l2len(msg),
+ !sched_tchf_dl_amr_cmi_map[br->fn % 26],
+ chan_state->codec,
+ chan_state->codecs,
+ chan_state->dl_ft,
+ chan_state->dl_cmr);
+ break;
+ /* CSD (TCH/F9.6): 12.0 kbit/s radio interface rate */
+ case GSM48_CMODE_DATA_12k0:
+ if (msg_tch == NULL)
+ msg_tch = tch_dummy_msgb(4 * 60, 0x01);
+ gsm0503_tch_fr96_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_tch));
+ if (msg_facch != NULL)
+ gsm0503_tch_fr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_facch));
+ break;
+ /* CSD (TCH/F4.8): 6.0 kbit/s radio interface rate */
+ case GSM48_CMODE_DATA_6k0:
+ if (msg_tch == NULL)
+ msg_tch = tch_dummy_msgb(2 * 60, 0x01);
+ gsm0503_tch_fr48_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_tch));
+ if (msg_facch != NULL)
+ gsm0503_tch_fr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_facch));
+ break;
+ /* CSD (TCH/F2.4): 3.6 kbit/s radio interface rate */
+ case GSM48_CMODE_DATA_3k6:
+ /* FACCH/F does steal a TCH/F2.4 frame completely */
+ if (msg_facch != NULL) {
+ gsm0503_tch_fr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_facch));
+ } else {
+ if (msg_tch == NULL)
+ msg_tch = tch_dummy_msgb(2 * 36, 0x01);
+ gsm0503_tch_fr24_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_tch));
+ }
+ break;
+ /* CSD (TCH/F14.4): 14.5 kbit/s radio interface rate */
+ case GSM48_CMODE_DATA_14k5:
+ if (msg_tch == NULL)
+ msg_tch = tch_dummy_msgb(290, 0x01);
+ gsm0503_tch_fr144_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_tch));
+ if (msg_facch != NULL)
+ gsm0503_tch_fr_facch_encode(BUFPOS(bursts_p, 0), msgb_l2(msg_facch));
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+
+ /* free messages */
+ msgb_free(msg_tch);
+ msgb_free(msg_facch);
+
+send_burst:
+ /* compose burst */
+ burst = BUFPOS(bursts_p, br->bid);
+ memcpy(br->burst + 3, burst, 58);
+ memcpy(br->burst + 61, TRX_GMSK_NB_TSC(br), 26);
+ memcpy(br->burst + 87, burst + 58, 58);
+
+ br->burst_len = GSM_BURST_LEN;
+
+ if (chan_state->dl_facch_bursts > 0) {
+ chan_state->dl_facch_bursts--;
+ br->flags |= TRX_BR_F_FACCH;
+ }
+
+ *mask |= (1 << br->bid);
+
+ LOGL1SB(DL1P, LOGL_DEBUG, l1ts, br, "Transmitting burst=%u.\n", br->bid);
+
+ return 0;
+}