summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPau Espin Pedrol <pespin@sysmocom.de>2022-09-02 17:02:17 +0200
committerVadim Yanitskiy <vyanitskiy@sysmocom.de>2022-09-07 00:01:53 +0700
commita53e93fe9c81136b512697e169c5cf5ecd319163 (patch)
tree66a2268c2e787cf475d32bddaf10d4fb61bedcf7
parenta8ace99d7297eb34b76ead2887819cac4e75bfdd (diff)
trxcon: Initial support for forwarding AMRHEADmaster
This allows TTCN3 L1CTL module (used in BTS_Tests) to transmit and receive AMR payloads towards osmo-bts-trx. Related: SYS#5987 Change-Id: Ia20bc96e39726a919a556c83c8be48cb31af7331
-rw-r--r--include/l1ctl_proto.h10
-rw-r--r--src/host/layer23/src/common/l1ctl.c1
-rw-r--r--src/host/trxcon/include/osmocom/bb/l1sched/l1sched.h2
-rw-r--r--src/host/trxcon/include/osmocom/bb/l1sched/logging.h3
-rw-r--r--src/host/trxcon/include/osmocom/bb/trxcon/Makefile.am1
-rw-r--r--src/host/trxcon/include/osmocom/bb/trxcon/sched_utils.h62
-rw-r--r--src/host/trxcon/include/osmocom/bb/trxcon/trxcon.h4
-rw-r--r--src/host/trxcon/src/l1ctl.c4
-rw-r--r--src/host/trxcon/src/sched_lchan_common.c16
-rw-r--r--src/host/trxcon/src/sched_lchan_tchf.c190
-rw-r--r--src/host/trxcon/src/sched_lchan_tchh.c196
-rw-r--r--src/host/trxcon/src/trxcon_fsm.c26
-rw-r--r--src/host/virt_phy/src/l1ctl_sap.c1
-rw-r--r--src/target/firmware/layer1/l23_api.c2
14 files changed, 407 insertions, 111 deletions
diff --git a/include/l1ctl_proto.h b/include/l1ctl_proto.h
index 3720f82a..a15f9051 100644
--- a/include/l1ctl_proto.h
+++ b/include/l1ctl_proto.h
@@ -162,7 +162,10 @@ struct l1ctl_tch_mode_conf {
uint8_t tch_mode; /* enum tch_mode */
uint8_t audio_mode;
uint8_t tch_loop_mode; /* enum l1ctl_tch_loop_mode */
- uint8_t padding[1];
+ struct { /* 3GPP TS 08.58 9.3.52, 3GPP TS 44.018 10.5.2.21aa */
+ uint8_t start_codec;
+ uint8_t codecs_bitmask;
+ } amr;
} __attribute__((packed));
/* data on the CCCH was found. This is following the header */
@@ -241,7 +244,10 @@ struct l1ctl_tch_mode_req {
#define AUDIO_RX_TRAFFIC_IND (1<<3)
uint8_t audio_mode;
uint8_t tch_loop_mode; /* enum l1ctl_tch_loop_mode */
- uint8_t padding[1];
+ struct { /* 3GPP TS 08.58 9.3.52, 3GPP TS 44.018 10.5.2.21aa */
+ uint8_t start_codec;
+ uint8_t codecs_bitmask;
+ } amr;
} __attribute__((packed));
/* the l1_info_ul header is in front */
diff --git a/src/host/layer23/src/common/l1ctl.c b/src/host/layer23/src/common/l1ctl.c
index e33074af..d5204034 100644
--- a/src/host/layer23/src/common/l1ctl.c
+++ b/src/host/layer23/src/common/l1ctl.c
@@ -472,6 +472,7 @@ int l1ctl_tx_tch_mode_req(struct osmocom_ms *ms, uint8_t tch_mode,
req->tch_mode = tch_mode;
req->audio_mode = audio_mode;
req->tch_loop_mode = tch_loop_mode;
+ /* TODO: Set AMR codec in req if req->tch_mode==GSM48_CMODE_SPEECH_AMR */
return osmo_send_l1(ms, msg);
}
diff --git a/src/host/trxcon/include/osmocom/bb/l1sched/l1sched.h b/src/host/trxcon/include/osmocom/bb/l1sched/l1sched.h
index 04303f09..9686562f 100644
--- a/src/host/trxcon/include/osmocom/bb/l1sched/l1sched.h
+++ b/src/host/trxcon/include/osmocom/bb/l1sched/l1sched.h
@@ -297,6 +297,8 @@ struct l1sched_lchan_state {
uint8_t ber_num;
/*! Sum of bit error rates */
float ber_sum;
+ /* last received dtx frame type */
+ uint8_t last_dtx;
} amr;
/*! A5/X encryption state */
diff --git a/src/host/trxcon/include/osmocom/bb/l1sched/logging.h b/src/host/trxcon/include/osmocom/bb/l1sched/logging.h
index fb1c018c..d2b80e37 100644
--- a/src/host/trxcon/include/osmocom/bb/l1sched/logging.h
+++ b/src/host/trxcon/include/osmocom/bb/l1sched/logging.h
@@ -17,12 +17,13 @@ extern int l1sched_log_cat_data;
LOGP_SCHED_CAT(sched, common, level, fmt, ## args)
+#define LOGP_LCHAN_NAME_FMT "TS%u-%s"
#define LOGP_LCHAN_NAME_ARGS(lchan) \
(lchan)->ts->index, l1sched_lchan_desc[(lchan)->type].name
/* Messages using l1sched_lchan_state as the context */
#define LOGP_LCHAN_CAT(lchan, cat, level, fmt, args...) \
- LOGP_SCHED_CAT((lchan)->ts->sched, cat, level, "TS%u-%s " fmt, \
+ LOGP_SCHED_CAT((lchan)->ts->sched, cat, level, LOGP_LCHAN_NAME_FMT " " fmt, \
LOGP_LCHAN_NAME_ARGS(lchan), ## args)
/* Common messages using l1sched_lchan_state as the context */
diff --git a/src/host/trxcon/include/osmocom/bb/trxcon/Makefile.am b/src/host/trxcon/include/osmocom/bb/trxcon/Makefile.am
index 44bcd498..8636c936 100644
--- a/src/host/trxcon/include/osmocom/bb/trxcon/Makefile.am
+++ b/src/host/trxcon/include/osmocom/bb/trxcon/Makefile.am
@@ -2,6 +2,7 @@ noinst_HEADERS = \
l1ctl_proto.h \
l1ctl_server.h \
l1ctl.h \
+ sched_utils.h \
trx_if.h \
logging.h \
trxcon.h \
diff --git a/src/host/trxcon/include/osmocom/bb/trxcon/sched_utils.h b/src/host/trxcon/include/osmocom/bb/trxcon/sched_utils.h
new file mode 100644
index 00000000..f1ca0936
--- /dev/null
+++ b/src/host/trxcon/include/osmocom/bb/trxcon/sched_utils.h
@@ -0,0 +1,62 @@
+/* Auxiliary scheduler utilities.
+ *
+ * (C) 2017 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2022 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 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>
+#include <stdbool.h>
+
+/*! determine whether an uplink AMR block is CMI according to 3GPP TS 45.009.
+ * \param[in] fn_begin frame number of the beginning of the block.
+ * \returns true in case of CMI; false otherwise. */
+static inline bool ul_amr_fn_is_cmi(uint32_t fn_begin)
+{
+ switch (fn_begin % 26) {
+ /*! See also: 3GPP TS 45.009, section 3.2.1.3 Transmitter/Receiver Synchronisation */
+ /* valid for AHS subslot 0 and AFS: */
+ case 0:
+ case 8:
+ case 17:
+ /* valid for AHS subslot 1: */
+ case 1:
+ case 9:
+ case 18:
+ return true;
+ break;
+ /* Complementary values for sanity check */
+ /* valid for AHS subslot 0 and AFS: */
+ case 4:
+ case 13:
+ case 21:
+ /* valid for AHS subslot 1: */
+ case 5:
+ case 14:
+ case 22:
+ return false;
+ break;
+ default:
+ OSMO_ASSERT(false);
+ return false;
+ break;
+ }
+}
diff --git a/src/host/trxcon/include/osmocom/bb/trxcon/trxcon.h b/src/host/trxcon/include/osmocom/bb/trxcon/trxcon.h
index 99b02b64..910d37f6 100644
--- a/src/host/trxcon/include/osmocom/bb/trxcon/trxcon.h
+++ b/src/host/trxcon/include/osmocom/bb/trxcon/trxcon.h
@@ -58,6 +58,10 @@ struct trxcon_param_fbsb_search_req {
/* param of TRXCON_EV_SET_{CCCH,TCH}_MODE_REQ */
struct trxcon_param_set_ccch_tch_mode_req {
uint8_t mode;
+ struct {
+ uint8_t start_codec;
+ uint8_t codecs_bitmask;
+ } amr;
bool applied;
};
diff --git a/src/host/trxcon/src/l1ctl.c b/src/host/trxcon/src/l1ctl.c
index 3e23374f..a321e37e 100644
--- a/src/host/trxcon/src/l1ctl.c
+++ b/src/host/trxcon/src/l1ctl.c
@@ -705,6 +705,10 @@ static int l1ctl_rx_tch_mode_req(struct l1ctl_client *l1c, struct msgb *msg)
struct trxcon_param_set_ccch_tch_mode_req req = {
.mode = mode_req->tch_mode,
};
+ if (mode_req->tch_mode == GSM48_CMODE_SPEECH_AMR) {
+ req.amr.start_codec = mode_req->amr.start_codec;
+ req.amr.codecs_bitmask = mode_req->amr.codecs_bitmask;
+ }
rc = osmo_fsm_inst_dispatch(trxcon->fi, TRXCON_EV_SET_TCH_MODE_REQ, &req);
if (rc != 0 || !req.applied) {
diff --git a/src/host/trxcon/src/sched_lchan_common.c b/src/host/trxcon/src/sched_lchan_common.c
index 6fe5b2e3..cddc9e5d 100644
--- a/src/host/trxcon/src/sched_lchan_common.c
+++ b/src/host/trxcon/src/sched_lchan_common.c
@@ -103,6 +103,8 @@ const char *l1sched_burst_mask2str(const uint8_t *mask, int bits)
*/
size_t l1sched_bad_frame_ind(uint8_t *l2, struct l1sched_lchan_state *lchan)
{
+ int rc;
+
switch (lchan->tch_mode) {
case GSM48_CMODE_SPEECH_V1:
if (lchan->type == L1SCHED_TCHF) { /* Full Rate */
@@ -119,8 +121,18 @@ size_t l1sched_bad_frame_ind(uint8_t *l2, struct l1sched_lchan_state *lchan)
l2[0] = 0xc0;
return GSM_EFR_BYTES;
case GSM48_CMODE_SPEECH_AMR: /* Adaptive Multi Rate */
- /* FIXME: AMR is not implemented yet */
- return 0;
+ rc = osmo_amr_rtp_enc(l2,
+ lchan->amr.codec[lchan->amr.dl_cmr],
+ lchan->amr.codec[lchan->amr.dl_ft],
+ AMR_BAD);
+ if (rc < 2) {
+ LOGP_LCHAND(lchan, LOGL_ERROR,
+ "Failed to encode AMR_BAD frame (rc=%d), "
+ "not sending BFI\n", rc);
+ return 0;
+ }
+ memset(l2 + 2, 0, rc - 2);
+ return rc;
case GSM48_CMODE_SIGN:
LOGP_LCHAND(lchan, LOGL_ERROR, "BFI is not allowed in signalling mode\n");
return 0;
diff --git a/src/host/trxcon/src/sched_lchan_tchf.c b/src/host/trxcon/src/sched_lchan_tchf.c
index 96acf0e5..5e741c8c 100644
--- a/src/host/trxcon/src/sched_lchan_tchf.c
+++ b/src/host/trxcon/src/sched_lchan_tchf.c
@@ -30,10 +30,26 @@
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/coding/gsm0503_coding.h>
+#include <osmocom/coding/gsm0503_amr_dtx.h>
#include <osmocom/codec/codec.h>
#include <osmocom/bb/l1sched/l1sched.h>
#include <osmocom/bb/l1sched/logging.h>
+#include <osmocom/bb/trxcon/sched_utils.h>
+
+/* 3GPP TS 45.009, table 3.2.1.3-{1,3}: AMR on Downlink 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_dl_amr_cmi_map[26] = {
+ [11] = 1, /* TCH/F: a=4 / h=11 */
+ [20] = 1, /* TCH/F: a=13 / h=20 */
+ [3] = 1, /* TCH/F: a=21 / h=3 (21+7=28, 25 is idle -> 29. 29%26=3) */
+};
int rx_tchf_fn(struct l1sched_lchan_state *lchan,
uint32_t fn, uint8_t bid, const sbit_t *bits,
@@ -43,6 +59,9 @@ int rx_tchf_fn(struct l1sched_lchan_state *lchan,
sbit_t *buffer, *offset;
uint8_t l2[128], *mask;
size_t l2_len;
+ int amr = 0;
+ uint8_t ft;
+ bool amr_is_cmr;
/* Set up pointers */
mask = &lchan->rx_burst_mask;
@@ -97,12 +116,37 @@ int rx_tchf_fn(struct l1sched_lchan_state *lchan,
1, 1, &n_errors, &n_bits_total);
break;
case GSM48_CMODE_SPEECH_AMR: /* AMR */
- /**
- * TODO: AMR requires a dedicated loop,
- * which will be implemented later...
+ /* the first FN 4,13,21 defines that CMI is included in frame,
+ * the first FN 0,8,17 defines that CMR/CMC is included in frame.
+ * NOTE: A frame ends 7 FN after start.
*/
- LOGP_LCHAND(lchan, LOGL_ERROR, "AMR isn't supported yet\n");
- return -ENOTSUP;
+ amr_is_cmr = !sched_tchf_dl_amr_cmi_map[fn % 26];
+
+ /* 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(l2 + amr, buffer,
+ amr_is_cmr, lchan->amr.codec, lchan->amr.codecs, &lchan->amr.dl_ft,
+ &lchan->amr.dl_cmr, &n_errors, &n_bits_total, &lchan->amr.last_dtx);
+
+ /* only good speech frames get rtp header */
+ if (rc != GSM_MACBLOCK_LEN && rc >= 4) {
+ if (lchan->amr.last_dtx == AMR_OTHER) {
+ ft = lchan->amr.codec[lchan->amr.dl_ft];
+ } else {
+ /* SID frames will always get Frame Type Index 8 (AMR_SID) */
+ ft = AMR_SID;
+ }
+ rc = osmo_amr_rtp_enc(l2,
+ lchan->amr.codec[lchan->amr.dl_cmr],
+ ft, AMR_GOOD);
+ if (rc < 0)
+ LOGP_LCHAND(lchan, LOGL_ERROR,
+ "osmo_amr_rtp_enc() returned rc=%d\n", rc);
+ }
+ break;
default:
LOGP_LCHAND(lchan, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode);
return -EINVAL;
@@ -121,7 +165,7 @@ int rx_tchf_fn(struct l1sched_lchan_state *lchan,
goto bfi;
} else if (rc == GSM_MACBLOCK_LEN) {
/* FACCH received, forward it to the higher layers */
- l1sched_handle_data_ind(lchan, l2, GSM_MACBLOCK_LEN,
+ l1sched_handle_data_ind(lchan, l2 + amr, GSM_MACBLOCK_LEN,
n_errors, n_bits_total,
L1SCHED_DT_SIGNALING);
@@ -186,63 +230,101 @@ int tx_tchf_fn(struct l1sched_lchan_state *lchan,
if (br->bid > 0)
return 0;
- /* Check the current TCH mode */
- switch (lchan->tch_mode) {
- case GSM48_CMODE_SIGN:
- case GSM48_CMODE_SPEECH_V1: /* FR */
- l2_len = GSM_FR_BYTES;
- break;
- case GSM48_CMODE_SPEECH_EFR: /* EFR */
- l2_len = GSM_EFR_BYTES;
- break;
- case GSM48_CMODE_SPEECH_AMR: /* AMR */
- /**
- * TODO: AMR requires a dedicated loop,
- * which will be implemented later...
- */
- LOGP_LCHAND(lchan, LOGL_ERROR,
- "AMR isn't supported yet, dropping frame...\n");
-
- /* Forget this primitive */
- l1sched_prim_drop(lchan);
-
- return -ENOTSUP;
- default:
- LOGP_LCHAND(lchan, LOGL_ERROR,
- "Invalid TCH mode: %u, dropping frame...\n",
- lchan->tch_mode);
-
- /* Forget this primitive */
- l1sched_prim_drop(lchan);
-
- return -EINVAL;
- }
-
- /* Determine and check the payload length */
- if (lchan->prim->payload_len == GSM_MACBLOCK_LEN) {
- l2_len = GSM_MACBLOCK_LEN; /* FACCH */
- } else if (lchan->prim->payload_len != l2_len) {
- LOGP_LCHAND(lchan, LOGL_ERROR, "Primitive has odd length %zu "
- "(expected %zu for TCH or %u for FACCH), so dropping...\n",
- lchan->prim->payload_len, l2_len, GSM_MACBLOCK_LEN);
-
- l1sched_prim_drop(lchan);
- return -EINVAL;
- }
-
/* Shift buffer by 4 bursts back for interleaving */
memcpy(buffer, buffer + 464, 464);
- /* Encode payload */
- rc = gsm0503_tch_fr_encode(buffer, lchan->prim->payload, l2_len, 1);
+ /* populate the buffer with bursts */
+ if (L1SCHED_PRIM_IS_FACCH(lchan->prim)) {
+ /* Encode payload */
+ rc = gsm0503_tch_fr_encode(buffer, lchan->prim->payload, GSM_MACBLOCK_LEN, 1);
+ } else if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) {
+ int len;
+ uint8_t cmr_codec;
+ int ft, cmr, i;
+ enum osmo_amr_type ft_codec;
+ enum osmo_amr_quality bfi;
+ int8_t sti, cmi;
+ bool amr_fn_is_cmr;
+ /* 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.
+ */
+ amr_fn_is_cmr = !ul_amr_fn_is_cmi(br->fn);
+
+ len = osmo_amr_rtp_dec(lchan->prim->payload, lchan->prim->payload_len,
+ &cmr_codec, &cmi, &ft_codec,
+ &bfi, &sti);
+ if (len < 0) {
+ LOGP_LCHAND(lchan, LOGL_ERROR, "Cannot send invalid AMR payload (%zu): %s\n",
+ lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload, lchan->prim->payload_len));
+ goto free_bad_msg;
+ }
+ ft = -1;
+ cmr = -1;
+ for (i = 0; i < lchan->amr.codecs; i++) {
+ if (lchan->amr.codec[i] == ft_codec)
+ ft = i;
+ if (lchan->amr.codec[i] == cmr_codec)
+ cmr = i;
+ }
+ if (ft < 0) {
+ LOGP_LCHAND(lchan, LOGL_ERROR,
+ "Codec (FT = %d) of RTP frame not in list\n", ft_codec);
+ goto free_bad_msg;
+ }
+ if (amr_fn_is_cmr && lchan->amr.ul_ft != ft) {
+ LOGP_LCHAND(lchan, LOGL_ERROR,
+ "Codec (FT = %d) of RTP cannot be changed now, but in next frame\n",
+ ft_codec);
+ goto free_bad_msg;
+ }
+ lchan->amr.ul_ft = ft;
+ if (cmr < 0) {
+ LOGP_LCHAND(lchan, LOGL_ERROR,
+ "Codec (CMR = %d) of RTP frame not in list\n", cmr_codec);
+ } else {
+ lchan->amr.ul_cmr = cmr;
+ }
+ rc = gsm0503_tch_afs_encode(buffer, lchan->prim->payload + 2,
+ lchan->prim->payload_len - 2, amr_fn_is_cmr,
+ lchan->amr.codec, lchan->amr.codecs,
+ lchan->amr.ul_ft,
+ lchan->amr.ul_cmr);
+ } else {
+ /* Determine and check the payload length */
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SIGN:
+ case GSM48_CMODE_SPEECH_V1: /* FR */
+ l2_len = GSM_FR_BYTES;
+ break;
+ case GSM48_CMODE_SPEECH_EFR: /* EFR */
+ l2_len = GSM_EFR_BYTES;
+ break;
+ default:
+ LOGP_LCHAND(lchan, LOGL_ERROR,
+ "Invalid TCH mode: %u, dropping frame...\n",
+ lchan->tch_mode);
+ /* Forget this primitive */
+ l1sched_prim_drop(lchan);
+ return -EINVAL;
+ }
+ if (lchan->prim->payload_len != l2_len) {
+ LOGP_LCHAND(lchan, LOGL_ERROR, "Primitive has odd length %zu "
+ "(expected %zu for TCH or %u for FACCH), so dropping...\n",
+ lchan->prim->payload_len, l2_len, GSM_MACBLOCK_LEN);
+
+ l1sched_prim_drop(lchan);
+ return -EINVAL;
+ }
+ rc = gsm0503_tch_fr_encode(buffer, lchan->prim->payload, l2_len, 1);
+ }
+
if (rc) {
LOGP_LCHAND(lchan, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n",
lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload,
lchan->prim->payload_len));
-
+free_bad_msg:
/* Forget this primitive */
l1sched_prim_drop(lchan);
-
return -EINVAL;
}
diff --git a/src/host/trxcon/src/sched_lchan_tchh.c b/src/host/trxcon/src/sched_lchan_tchh.c
index 12ddc159..9125a395 100644
--- a/src/host/trxcon/src/sched_lchan_tchh.c
+++ b/src/host/trxcon/src/sched_lchan_tchh.c
@@ -33,10 +33,31 @@
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/coding/gsm0503_coding.h>
+#include <osmocom/coding/gsm0503_amr_dtx.h>
#include <osmocom/codec/codec.h>
#include <osmocom/bb/l1sched/l1sched.h>
#include <osmocom/bb/l1sched/logging.h>
+#include <osmocom/bb/trxcon/sched_utils.h>
+
+/* 3GPP TS 45.009, table 3.2.1.3-{2,4}: AMR on Downlink TCH/H.
+ *
+ * +---+---+---+---+---+---+
+ * | a | b | c | d | e | f | Burst 'a' received first
+ * +---+---+---+---+---+---+
+ * ^^^^^^^^^^^^^^^^^^^^^^^ FACCH frame (bursts 'a' .. 'f')
+ * ^^^^^^^^^^^^^^^ Speech frame (bursts 'a' .. 'd')
+ *
+ * TDMA frame number of burst 'f' is always used as the table index. */
+static const uint8_t sched_tchh_dl_amr_cmi_map[26] = {
+ [15] = 1, /* TCH/H(0): a=4 / d=10 / f=15 */
+ [23] = 1, /* TCH/H(0): a=13 / d=19 / f=23 */
+ [6] = 1, /* TCH/H(0): a=21 / d=2 / f=6 */
+
+ [16] = 1, /* TCH/H(1): a=5 / d=11 / f=16 */
+ [24] = 1, /* TCH/H(1): a=14 / d=20 / f=24 */
+ [7] = 1, /* TCH/H(1): a=22 / d=3 / f=7 */
+};
static const uint8_t tch_h0_traffic_block_map[3][4] = {
/* B0(0,2,4,6), B1(4,6,8,10), B2(8,10,0,2) */
@@ -80,6 +101,18 @@ const uint8_t tch_h1_ul_facch_block_map[3][6] = {
{ 18, 20, 22, 24, 1, 3 },
};
+/* FACCH/H channel mapping for Downlink (see 3GPP TS 45.002, table 1).
+ * This mapping is valid for both FACCH/H(0) and FACCH/H(1).
+ * TDMA frame number of burst 'f' is used as the table index. */
+static const uint8_t sched_tchh_dl_facch_map[26] = {
+ [15] = 1, /* FACCH/H(0): B0(4,6,8,10,13,15) */
+ [16] = 1, /* FACCH/H(1): B0(5,7,9,11,14,16) */
+ [23] = 1, /* FACCH/H(0): B1(13,15,17,19,21,23) */
+ [24] = 1, /* FACCH/H(1): B1(14,16,18,20,22,24) */
+ [6] = 1, /* FACCH/H(0): B2(21,23,0,2,4,6) */
+ [7] = 1, /* FACCH/H(1): B2(22,24,1,3,5,7) */
+};
+
/**
* Can a TCH/H block transmission be initiated / finished
* on a given frame number and a given channel type?
@@ -199,6 +232,9 @@ int rx_tchh_fn(struct l1sched_lchan_state *lchan,
sbit_t *buffer, *offset;
uint8_t l2[128], *mask;
size_t l2_len;
+ int amr = 0;
+ uint8_t ft;
+ bool fn_is_cmi;
/* Set up pointers */
mask = &lchan->rx_burst_mask;
@@ -261,12 +297,34 @@ int rx_tchh_fn(struct l1sched_lchan_state *lchan,
&n_errors, &n_bits_total);
break;
case GSM48_CMODE_SPEECH_AMR: /* AMR */
- /**
- * TODO: AMR requires a dedicated loop,
- * which will be implemented later...
- */
- LOGP_LCHAND(lchan, LOGL_ERROR, "AMR isn't supported yet\n");
- return -ENOTSUP;
+ /* the first FN FN 4,13,21 or 5,14,22 defines that CMI is
+ * included in frame, the first FN FN 0,8,17 or 1,9,18 defines
+ * that CMR/CMC is included in frame. */
+ fn_is_cmi = sched_tchh_dl_amr_cmi_map[fn % 26];
+
+ /* See comment in function rx_tchf_fn() */
+ amr = 2;
+ rc = gsm0503_tch_ahs_decode_dtx(l2 + amr, buffer,
+ !sched_tchh_dl_facch_map[fn % 26],
+ !fn_is_cmi, lchan->amr.codec, lchan->amr.codecs, &lchan->amr.dl_ft,
+ &lchan->amr.dl_cmr, &n_errors, &n_bits_total, &lchan->amr.last_dtx);
+
+ /* only good speech frames get rtp header */
+ if (rc != GSM_MACBLOCK_LEN && rc >= 4) {
+ if (lchan->amr.last_dtx == AMR_OTHER) {
+ ft = lchan->amr.codec[lchan->amr.dl_ft];
+ } else {
+ /* SID frames will always get Frame Type Index 8 (AMR_SID) */
+ ft = AMR_SID;
+ }
+ rc = osmo_amr_rtp_enc(l2,
+ lchan->amr.codec[lchan->amr.dl_cmr],
+ ft, AMR_GOOD);
+ if (rc < 0)
+ LOGP_LCHAND(lchan, LOGL_ERROR,
+ "osmo_amr_rtp_enc() returned rc=%d\n", rc);
+ }
+ break;
default:
LOGP_LCHAND(lchan, LOGL_ERROR, "Invalid TCH mode: %u\n", lchan->tch_mode);
return -EINVAL;
@@ -298,7 +356,7 @@ int rx_tchh_fn(struct l1sched_lchan_state *lchan,
l1sched_lchan_meas_avg(lchan, 6);
/* FACCH/H received, forward to the higher layers */
- l1sched_handle_data_ind(lchan, l2, GSM_MACBLOCK_LEN,
+ l1sched_handle_data_ind(lchan, l2 + amr, GSM_MACBLOCK_LEN,
n_errors, n_bits_total,
L1SCHED_DT_SIGNALING);
@@ -394,62 +452,98 @@ int tx_tchh_fn(struct l1sched_lchan_state *lchan,
goto send_burst;
}
- /* Check the current TCH mode */
- switch (lchan->tch_mode) {
- case GSM48_CMODE_SIGN:
- case GSM48_CMODE_SPEECH_V1: /* HR */
- l2_len = GSM_HR_BYTES + 1;
- break;
- case GSM48_CMODE_SPEECH_AMR: /* AMR */
- /**
- * TODO: AMR requires a dedicated loop,
- * which will be implemented later...
- */
- LOGP_LCHAND(lchan, LOGL_ERROR,
- "AMR isn't supported yet, dropping frame...\n");
-
- /* Forget this primitive */
- l1sched_prim_drop(lchan);
- return -ENOTSUP;
- default:
- LOGP_LCHAND(lchan, LOGL_ERROR,
- "Invalid TCH mode: %u, dropping frame...\n",
- lchan->tch_mode);
-
- /* Forget this primitive */
- l1sched_prim_drop(lchan);
- return -EINVAL;
- }
-
- /* Determine payload length */
+ /* populate the buffer with bursts */
if (L1SCHED_PRIM_IS_FACCH(lchan->prim)) {
- l2_len = GSM_MACBLOCK_LEN; /* FACCH */
- } else if (lchan->prim->payload_len != l2_len) {
- LOGP_LCHAND(lchan, LOGL_ERROR, "Primitive has odd length %zu "
- "(expected %zu for TCH or %u for FACCH), so dropping...\n",
- lchan->prim->payload_len, l2_len, GSM_MACBLOCK_LEN);
-
- /* Forget this primitive */
- l1sched_prim_drop(lchan);
- return -EINVAL;
+ rc = gsm0503_tch_hr_encode(buffer, lchan->prim->payload, lchan->prim->payload_len);
+ lchan->ul_facch_blocks = 6;
+ } else if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) {
+ int len;
+ uint8_t cmr_codec;
+ int ft, cmr, i;
+ enum osmo_amr_type ft_codec;
+ enum osmo_amr_quality bfi;
+ int8_t sti, cmi;
+ bool amr_fn_is_cmr;
+ /* 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.
+ */
+ amr_fn_is_cmr = !ul_amr_fn_is_cmi(br->fn);
+
+ len = osmo_amr_rtp_dec(lchan->prim->payload, lchan->prim->payload_len,
+ &cmr_codec, &cmi, &ft_codec,
+ &bfi, &sti);
+ if (len < 0) {
+ LOGP_LCHAND(lchan, LOGL_ERROR, "Cannot send invalid AMR payload (%zu): %s\n",
+ lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload, lchan->prim->payload_len));
+ goto free_bad_msg;
+ }
+ ft = -1;
+ cmr = -1;
+ for (i = 0; i < lchan->amr.codecs; i++) {
+ if (lchan->amr.codec[i] == ft_codec)
+ ft = i;
+ if (lchan->amr.codec[i] == cmr_codec)
+ cmr = i;
+ }
+ if (ft < 0) {
+ LOGP_LCHAND(lchan, LOGL_ERROR,
+ "Codec (FT = %d) of RTP frame not in list\n", ft_codec);
+ goto free_bad_msg;
+ }
+ if (amr_fn_is_cmr && lchan->amr.ul_ft != ft) {
+ LOGP_LCHAND(lchan, LOGL_ERROR,
+ "Codec (FT = %d) of RTP cannot be changed now, but in next frame\n",
+ ft_codec);
+ goto free_bad_msg;
+ }
+ lchan->amr.ul_ft = ft;
+ if (cmr < 0) {
+ LOGP_LCHAND(lchan, LOGL_ERROR,
+ "Codec (CMR = %d) of RTP frame not in list\n", cmr_codec);
+ } else {
+ lchan->amr.ul_cmr = cmr;
+ }
+ rc = gsm0503_tch_ahs_encode(buffer, lchan->prim->payload + 2,
+ lchan->prim->payload_len - 2, amr_fn_is_cmr,
+ lchan->amr.codec, lchan->amr.codecs,
+ lchan->amr.ul_ft,
+ lchan->amr.ul_cmr);
+ } else {
+ /* Determine and check the payload length */
+ switch (lchan->tch_mode) {
+ case GSM48_CMODE_SIGN:
+ case GSM48_CMODE_SPEECH_V1: /* HR */
+ l2_len = GSM_HR_BYTES + 1;
+ break;
+ default:
+ LOGP_LCHAND(lchan, LOGL_ERROR,
+ "Invalid TCH mode: %u, dropping frame...\n",
+ lchan->tch_mode);
+ /* Forget this primitive */
+ l1sched_prim_drop(lchan);
+ return -EINVAL;
+ }
+ if (lchan->prim->payload_len != l2_len) {
+ LOGP_LCHAND(lchan, LOGL_ERROR, "Primitive has odd length %zu "
+ "(expected %zu for TCH or %u for FACCH), so dropping...\n",
+ lchan->prim->payload_len, l2_len, GSM_MACBLOCK_LEN);
+ /* Forget this primitive */
+ l1sched_prim_drop(lchan);
+ return -EINVAL;
+ }
+ rc = gsm0503_tch_hr_encode(buffer, lchan->prim->payload, l2_len);
}
- /* Encode the payload */
- rc = gsm0503_tch_hr_encode(buffer, lchan->prim->payload, l2_len);
if (rc) {
LOGP_LCHAND(lchan, LOGL_ERROR, "Failed to encode L2 payload (len=%zu): %s\n",
lchan->prim->payload_len, osmo_hexdump(lchan->prim->payload,
lchan->prim->payload_len));
-
+free_bad_msg:
/* Forget this primitive */
l1sched_prim_drop(lchan);
return -EINVAL;
}
- /* A FACCH/H frame occupies 6 bursts */
- if (L1SCHED_PRIM_IS_FACCH(lchan->prim))
- lchan->ul_facch_blocks = 6;
-
send_burst:
/* Determine which burst should be sent */
offset = buffer + br->bid * 116;
diff --git a/src/host/trxcon/src/trxcon_fsm.c b/src/host/trxcon/src/trxcon_fsm.c
index b028ef41..41162918 100644
--- a/src/host/trxcon/src/trxcon_fsm.c
+++ b/src/host/trxcon/src/trxcon_fsm.c
@@ -36,6 +36,7 @@
#include <osmocom/bb/trxcon/l1ctl_server.h>
#include <osmocom/bb/trxcon/l1ctl_proto.h>
#include <osmocom/bb/l1sched/l1sched.h>
+#include <osmocom/bb/l1sched/logging.h>
#define S(x) (1 << (x))
@@ -351,6 +352,31 @@ static void trxcon_st_dedicated_action(struct osmo_fsm_inst *fi,
if (!lchan->active)
continue;
lchan->tch_mode = req->mode;
+ if (req->mode == GSM48_CMODE_SPEECH_AMR) {
+ uint8_t bmask = req->amr.codecs_bitmask;
+ int n = 0;
+ int acum = 0;
+ int pos;
+ while ((pos = ffs(bmask)) != 0) {
+ acum += pos;
+ LOGPFSML(fi, LOGL_DEBUG,
+ LOGP_LCHAN_NAME_FMT " AMR codec[%u] = %u\n",
+ LOGP_LCHAN_NAME_ARGS(lchan), n, acum - 1);
+ lchan->amr.codec[n++] = acum - 1;
+ bmask >>= pos;
+ }
+ if (n == 0) {
+ LOGPFSML(fi, LOGL_ERROR,
+ LOGP_LCHAN_NAME_FMT " Empty AMR codec mode bitmask!\n",
+ LOGP_LCHAN_NAME_ARGS(lchan));
+ continue;
+ }
+ lchan->amr.codecs = n;
+ lchan->amr.dl_ft = req->amr.start_codec;
+ lchan->amr.dl_cmr = req->amr.start_codec;
+ lchan->amr.ul_ft = req->amr.start_codec;
+ lchan->amr.ul_cmr = req->amr.start_codec;
+ }
req->applied = true;
}
}
diff --git a/src/host/virt_phy/src/l1ctl_sap.c b/src/host/virt_phy/src/l1ctl_sap.c
index bab62590..a0e8c0c9 100644
--- a/src/host/virt_phy/src/l1ctl_sap.c
+++ b/src/host/virt_phy/src/l1ctl_sap.c
@@ -486,6 +486,7 @@ void l1ctl_rx_tch_mode_req(struct l1_model_ms *ms, struct msgb *msg)
l1_model_tch_mode_set(ms, tch_mode_req->tch_mode);
ms->state.audio_mode = tch_mode_req->audio_mode;
+ /* TODO: Handle AMR codecs from tch_mode_req if tch_mode_req->tch_mode==GSM48_CMODE_SPEECH_AMR */
LOGPMS(DL1C, LOGL_INFO, ms, "Rx L1CTL_TCH_MODE_REQ (tch_mode=0x%02x audio_mode=0x%02x)\n",
tch_mode_req->tch_mode, tch_mode_req->audio_mode);
diff --git a/src/target/firmware/layer1/l23_api.c b/src/target/firmware/layer1/l23_api.c
index a133c62f..599272f3 100644
--- a/src/target/firmware/layer1/l23_api.c
+++ b/src/target/firmware/layer1/l23_api.c
@@ -553,6 +553,7 @@ static void l1ctl_rx_tch_mode_req(struct msgb *msg)
l1s.tch_sync = 1; /* Needed for audio to work */
l1s.tch_loop_mode = tch_mode_req->tch_loop_mode;
+ /* TODO: Handle AMR codecs from tch_mode_req if tch_mode_req->tch_mode==GSM48_CMODE_SPEECH_AMR */
l1ctl_tx_tch_mode_conf(tch_mode, audio_mode);
}
@@ -726,4 +727,3 @@ void l1a_l23api_init(void)
{
sercomm_register_rx_cb(SC_DLCI_L1A_L23, l1a_l23_rx);
}
-