[PATCH] osmo-bts[master]: DTX DL: use FSM for AMR

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/.

Max gerrit-no-reply at lists.osmocom.org
Fri Oct 7 18:43:39 UTC 2016


Review at  https://gerrit.osmocom.org/1030

DTX DL: use FSM for AMR

Use dedicated FSM to handle all DTX DL related events. This affects both
lc15 and sysmobts and requires corresponding change in OpenBSC.

Change-Id: I74a0b42cb34d525b8a70d264135e82994ca70d31
---
M include/osmo-bts/Makefile.am
A include/osmo-bts/dtx_dl_amr_fsm.h
M include/osmo-bts/msg_utils.h
M src/common/Makefile.am
A src/common/dtx_dl_amr_fsm.c
M src/common/l1sap.c
M src/common/msg_utils.c
M src/osmo-bts-litecell15/l1_if.c
M src/osmo-bts-litecell15/l1_if.h
M src/osmo-bts-litecell15/tch.c
M src/osmo-bts-sysmo/l1_if.c
M src/osmo-bts-sysmo/l1_if.h
M src/osmo-bts-sysmo/tch.c
13 files changed, 636 insertions(+), 126 deletions(-)


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

diff --git a/include/osmo-bts/Makefile.am b/include/osmo-bts/Makefile.am
index d0b55ff..ef4165f 100644
--- a/include/osmo-bts/Makefile.am
+++ b/include/osmo-bts/Makefile.am
@@ -1,4 +1,5 @@
 noinst_HEADERS = abis.h bts.h bts_model.h gsm_data.h logging.h measurement.h \
 		 oml.h paging.h rsl.h signal.h vty.h amr.h pcu_if.h pcuif_proto.h \
 		 handover.h msg_utils.h tx_power.h control_if.h cbch.h l1sap.h \
-		 power_control.h scheduler.h scheduler_backend.h phy_link.h
+		 power_control.h scheduler.h scheduler_backend.h phy_link.h \
+		 dtx_dl_amr_fsm.h
diff --git a/include/osmo-bts/dtx_dl_amr_fsm.h b/include/osmo-bts/dtx_dl_amr_fsm.h
new file mode 100644
index 0000000..8b19595
--- /dev/null
+++ b/include/osmo-bts/dtx_dl_amr_fsm.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/logging.h>
+
+/* DTX DL AMR FSM */
+
+#define X(s) (1 << (s))
+
+enum dtx_dl_amr_fsm_states {
+	ST_VOICE,
+	ST_SID_F1,
+	ST_SID_F2,
+	ST_F1_INH,
+	ST_U_INH,
+	ST_SID_U,
+	ST_ONSET_V,
+	ST_ONSET_F,
+	ST_FACCH_V,
+	ST_FACCH,
+};
+
+enum dtx_dl_amr_fsm_events {
+	E_VOICE,
+	E_ONSET,
+	E_FACCH,
+	E_COMPL,
+	E_INHIB,
+	E_SID_F,
+	E_SID_U,
+};
+
+extern const struct value_string dtx_dl_amr_fsm_event_names[];
+extern struct osmo_fsm dtx_dl_amr_fsm;
diff --git a/include/osmo-bts/msg_utils.h b/include/osmo-bts/msg_utils.h
index f99f3c4..31bd172 100644
--- a/include/osmo-bts/msg_utils.h
+++ b/include/osmo-bts/msg_utils.h
@@ -26,12 +26,11 @@
 };
 
 void lchan_set_marker(bool t, struct gsm_lchan *lchan);
-void save_last_sid(struct gsm_lchan *lchan, const uint8_t *l1_payload,
-		   size_t length, uint32_t fn, int update, uint8_t cmr,
-		   int8_t cmi);
-int dtx_amr_check_onset(struct gsm_lchan *lchan, const uint8_t *rtp_pl,
+void dtx_cache_payload(struct gsm_lchan *lchan, const uint8_t *l1_payload,
+		       size_t length, uint32_t fn, int update);
+int dtx_dl_amr_fsm_step(struct gsm_lchan *lchan, const uint8_t *rtp_pl,
 			size_t rtp_pl_len, uint32_t fn, uint8_t *l1_payload,
-			uint8_t *ft_out);
+			bool marker, uint8_t *len, uint8_t *ft_out);
 uint8_t repeat_last_sid(struct gsm_lchan *lchan, uint8_t *dst, uint32_t fn);
 int msg_verify_ipa_structure(struct msgb *msg);
 int msg_verify_oml_structure(struct msgb *msg);
diff --git a/src/common/Makefile.am b/src/common/Makefile.am
index 856f741..dd368d0 100644
--- a/src/common/Makefile.am
+++ b/src/common/Makefile.am
@@ -7,6 +7,7 @@
 		   rsl.c vty.c paging.c measurement.c amr.c lchan.c \
 		   load_indication.c pcu_sock.c handover.c msg_utils.c \
 		   tx_power.c bts_ctrl_commands.c bts_ctrl_lookup.c \
-		   l1sap.c cbch.c power_control.c main.c phy_link.c
+		   l1sap.c cbch.c power_control.c main.c phy_link.c \
+		   dtx_dl_amr_fsm.c
 
 libl1sched_a_SOURCES = scheduler.c
diff --git a/src/common/dtx_dl_amr_fsm.c b/src/common/dtx_dl_amr_fsm.c
new file mode 100644
index 0000000..7677624
--- /dev/null
+++ b/src/common/dtx_dl_amr_fsm.c
@@ -0,0 +1,304 @@
+/* DTX DL AMR FSM */
+
+/* (C) 2016 by sysmocom s.f.m.c. GmbH
+ *
+ * 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 <osmo-bts/dtx_dl_amr_fsm.h>
+#include <osmo-bts/logging.h>
+
+void dtx_fsm_voice(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	switch (event) {
+	case E_VOICE:
+	case E_FACCH:
+		break;
+	case E_SID_F:
+	case E_SID_U:
+		osmo_fsm_inst_state_chg(fi, ST_SID_F1, 0, 0);
+		break;
+	default:
+		LOGP(DL1P, LOGL_ERROR, "Inexpected event %d\n", event);
+		OSMO_ASSERT(0);
+		break;
+	}
+}
+
+void dtx_fsm_sid_f1(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	switch (event) {
+	case E_SID_F:
+/* FIXME: what shall we do if we get SID-FIRST _again_ (twice in a row)?
+   Was observed during testing, let's just ignore it for now */
+		break;
+	case E_SID_U:
+		osmo_fsm_inst_state_chg(fi, ST_SID_U, 0, 0);
+		break;
+	case E_VOICE:
+		osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0);
+		break;
+	case E_FACCH:
+		osmo_fsm_inst_state_chg(fi, ST_ONSET_F, 0, 0);
+		break;
+	case E_COMPL:
+		osmo_fsm_inst_state_chg(fi, ST_SID_F2, 0, 0);
+		break;
+	case E_INHIB:
+		osmo_fsm_inst_state_chg(fi, ST_F1_INH, 0, 0);
+		break;
+	case E_ONSET:
+		osmo_fsm_inst_state_chg(fi, ST_ONSET_V, 0, 0);
+		break;
+	default:
+		LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+		OSMO_ASSERT(0);
+		break;
+	}
+}
+
+void dtx_fsm_sid_f2(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	switch (event) {
+	case E_SID_U:
+		osmo_fsm_inst_state_chg(fi, ST_SID_U, 0, 0);
+		break;
+	case E_VOICE:
+		osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0);
+		break;
+	case E_FACCH:
+		osmo_fsm_inst_state_chg(fi, ST_ONSET_F, 0, 0);
+		break;
+	default:
+		LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+		OSMO_ASSERT(0);
+		break;
+	}
+}
+
+void dtx_fsm_f1_inh(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	switch (event) {
+	case E_VOICE:
+		osmo_fsm_inst_state_chg(fi, ST_ONSET_V, 0, 0);
+		break;
+	case E_FACCH:
+		osmo_fsm_inst_state_chg(fi, ST_ONSET_F, 0, 0);
+		break;
+	default:
+		LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+		OSMO_ASSERT(0);
+		break;
+	}
+}
+
+void dtx_fsm_u_inh(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	switch (event) {
+	case E_VOICE:
+		osmo_fsm_inst_state_chg(fi, ST_ONSET_V, 0, 0);
+		break;
+	case E_FACCH:
+		osmo_fsm_inst_state_chg(fi, ST_ONSET_F, 0, 0);
+		break;
+	default:
+		LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+		OSMO_ASSERT(0);
+		break;
+	}
+}
+
+void dtx_fsm_sid_upd(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	switch (event) {
+	case E_FACCH:
+		osmo_fsm_inst_state_chg(fi, ST_ONSET_F, 0, 0);
+		break;
+	case E_VOICE:
+		osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0);
+		break;
+	case E_INHIB:
+		osmo_fsm_inst_state_chg(fi, ST_U_INH, 0, 0);
+		break;
+	case E_SID_U:
+	case E_SID_F:
+/* FIXME: what shall we do if we get SID-FIRST _after_ sending SID-UPDATE?
+   Was observed during testing, let's just ignore it for now */
+		break;
+	case E_ONSET:
+		osmo_fsm_inst_state_chg(fi, ST_ONSET_V, 0, 0);
+		break;
+	default:
+		LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+		OSMO_ASSERT(0);
+		break;
+	}
+}
+
+void dtx_fsm_onset_v(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	switch (event) {
+	case E_SID_U:
+	case E_VOICE:
+		osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0);
+		break;
+	case E_FACCH:
+		osmo_fsm_inst_state_chg(fi, ST_FACCH_V, 0, 0);
+		break;
+	default:
+		LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+		OSMO_ASSERT(0);
+		break;
+	}
+}
+
+void dtx_fsm_onset_f(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	switch (event) {
+	case E_SID_U:
+		break;
+	case E_VOICE:
+		osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0);
+		break;
+	case E_FACCH:
+		osmo_fsm_inst_state_chg(fi, ST_FACCH, 0, 0);
+		break;
+	default:
+		LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+		OSMO_ASSERT(0);
+		break;
+	}
+}
+
+void dtx_fsm_facch_v(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	switch (event) {
+	case E_FACCH:
+		break;
+	case E_VOICE:
+		osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0);
+		break;
+	case E_SID_U:
+		break;
+	case E_SID_F:
+		break;
+	default:
+		LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+		OSMO_ASSERT(0);
+		break;
+	}
+}
+
+void dtx_fsm_facch(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	switch (event) {
+	case E_FACCH:
+		break;
+	case E_VOICE:
+		osmo_fsm_inst_state_chg(fi, ST_VOICE, 0, 0);
+		break;
+	case E_SID_U:
+	case E_SID_F:
+		osmo_fsm_inst_state_chg(fi, ST_SID_F1, 0, 0);
+		break;
+	default:
+		LOGP(DL1P, LOGL_ERROR, "Unexpected event %d\n", event);
+		OSMO_ASSERT(0);
+		break;
+	}
+}
+
+static struct osmo_fsm_state dtx_dl_amr_fsm_states[] = {
+	[ST_VOICE] = {
+		.in_event_mask = X(E_SID_F) | X(E_SID_U) | X(E_VOICE) | X(E_FACCH),
+		.out_state_mask = X(ST_SID_F1),
+		.name = "Voice",
+		.action = dtx_fsm_voice,
+	},
+	[ST_SID_F1]= {
+		.in_event_mask = X(E_SID_F) | X(E_SID_U) | X(E_VOICE) | X(E_FACCH) | X(E_COMPL) | X(E_INHIB) | X(E_ONSET),
+		.out_state_mask = X(ST_SID_U) | X(ST_VOICE) | X(ST_ONSET_F) | X(ST_SID_F2) | X(ST_F1_INH) | X(ST_ONSET_V),
+		.name = "SID-FIRST (P1)",
+		.action = dtx_fsm_sid_f1,
+	},
+	[ST_SID_F2]= {
+		.in_event_mask = X(E_SID_U) | X(E_VOICE) | X(E_FACCH),
+		.out_state_mask = X(ST_SID_U) | X(ST_VOICE) | X(ST_ONSET_F),
+		.name = "SID-FIRST (P2)",
+		.action = dtx_fsm_sid_f2,
+	},
+	[ST_F1_INH]= {
+		.in_event_mask = X(E_VOICE) | X(E_FACCH),
+		.out_state_mask = X(ST_VOICE) | X(ST_FACCH_V),
+		.name = "SID-FIRST (Inh)",
+		.action = dtx_fsm_f1_inh,
+	},
+	[ST_U_INH]= {
+		.in_event_mask = X(E_VOICE) | X(E_FACCH),
+		.out_state_mask = X(ST_VOICE) | X(ST_FACCH_V),
+		.name = "SID-UPDATE (Inh)",
+		.action = dtx_fsm_u_inh,
+	},
+	[ST_SID_U]= {
+		.in_event_mask = X(E_FACCH) | X(E_VOICE) | X(E_INHIB) | X(E_SID_U) | X(E_SID_F) | X(E_ONSET),
+		.out_state_mask = X(ST_ONSET_F) | X(ST_VOICE) | X(ST_U_INH) | X(ST_SID_U) | X(ST_ONSET_V),
+		.name = "SID-UPDATE",
+		.action = dtx_fsm_sid_upd,
+	},
+	[ST_ONSET_V]= {
+		.in_event_mask = X(E_VOICE) | X(E_FACCH),
+		.out_state_mask = X(ST_VOICE) | X(ST_FACCH_V),
+		.name = "ONSET (Voice)",
+		.action = dtx_fsm_onset_v,
+	},
+	[ST_ONSET_F]= {
+		.in_event_mask = X(E_VOICE) | X(E_FACCH) | X(E_SID_U),
+		.out_state_mask = X(ST_VOICE) | X(ST_FACCH),
+		.name = "ONSET (FACCH)",
+		.action = dtx_fsm_onset_f,
+	},
+	[ST_FACCH_V]= {
+		.in_event_mask = X(E_FACCH) | X(E_VOICE) | X(E_SID_U) | X(E_SID_F),
+		.out_state_mask = X(ST_FACCH_V) | X(ST_VOICE) | X(ST_SID_F1),
+		.name = "FACCH (Voice)",
+		.action = dtx_fsm_facch_v,
+	},
+	[ST_FACCH]= {
+		.in_event_mask = X(E_FACCH) | X(E_VOICE) | X(E_SID_U) | X(E_SID_F),
+		.out_state_mask = X(ST_FACCH_V) | X(ST_VOICE) | X(ST_SID_F1),
+		.name = "FACCH",
+		.action = dtx_fsm_facch,
+	},
+};
+
+const struct value_string dtx_dl_amr_fsm_event_names[] = {
+	{ E_VOICE,	"Voice" },
+	{ E_FACCH,	"FACCH" },
+	{ E_COMPL,	"Complete P1 -> P2" },
+	{ E_INHIB,	"Inhibit" },
+	{ E_SID_F,	"SID-FIRST" },
+	{ E_SID_U,	"SID-UPDATE" },
+	{ 0, 		NULL }
+};
+
+struct osmo_fsm dtx_dl_amr_fsm = {
+	.name = "DTX DL AMR FSM",
+	.states = dtx_dl_amr_fsm_states,
+	.num_states = ARRAY_SIZE(dtx_dl_amr_fsm_states),
+	.event_names = dtx_dl_amr_fsm_event_names,
+	.log_subsys = DL1C,
+};
diff --git a/src/common/l1sap.c b/src/common/l1sap.c
index 943bdfe..40e8d52 100644
--- a/src/common/l1sap.c
+++ b/src/common/l1sap.c
@@ -33,12 +33,12 @@
 #include <osmocom/core/gsmtap.h>
 #include <osmocom/core/gsmtap_util.h>
 #include <osmocom/core/utils.h>
-
 #include <osmocom/trau/osmo_ortp.h>
 
 #include <osmo-bts/logging.h>
 #include <osmo-bts/gsm_data.h>
 #include <osmo-bts/l1sap.h>
+#include <osmo-bts/dtx_dl_amr_fsm.h>
 #include <osmo-bts/pcu_if.h>
 #include <osmo-bts/measurement.h>
 #include <osmo-bts/bts.h>
@@ -528,6 +528,7 @@
 	uint8_t *p, *si;
 	struct lapdm_entity *le;
 	struct osmo_phsap_prim pp;
+	bool dtxd_facch = false;
 	int rc;
 
 	chan_nr = rts_ind->chan_nr;
@@ -582,8 +583,11 @@
 			p[0] = lchan->ms_power_ctrl.current;
 			p[1] = lchan->rqd_ta;
 			le = &lchan->lapdm_ch.lapdm_acch;
-		} else
+		} else {
+			if (lchan->ts->trx->bts->dtxd)
+				dtxd_facch = true;
 			le = &lchan->lapdm_ch.lapdm_dcch;
+		}
 		rc = lapdm_phsap_dequeue_prim(le, &pp);
 		if (rc < 0) {
 			if (L1SAP_IS_LINK_SACCH(link_id)) {
@@ -609,6 +613,10 @@
 				memcpy(p, pp.oph.msg->data, GSM_MACBLOCK_LEN);
 				/* check if it is a RR CIPH MODE CMD. if yes, enable RX ciphering */
 				check_for_ciph_cmd(pp.oph.msg, lchan, chan_nr);
+				if (dtxd_facch)
+					osmo_fsm_inst_dispatch(lchan->tch.dtx_dl_amr_fsm,
+							       E_FACCH,
+							       (void *)lchan);
 			}
 			msgb_free(pp.oph.msg);
 		}
@@ -1136,6 +1144,13 @@
 	rc = l1sap_chan_act_dact_modify(trx, chan_nr, PRIM_INFO_ACTIVATE, 0);
 	if (rc)
 		return -RSL_ERR_EQUIPMENT_FAIL;
+
+	/* Init DTX DL FSM if necessary */
+	//FIXME: only do it for AMR TCH/*
+	osmo_fsm_register(&dtx_dl_amr_fsm); //FIXME: unreg and dealloc on lchan teradown
+	lchan->tch.dtx_dl_amr_fsm = osmo_fsm_inst_alloc(&dtx_dl_amr_fsm,
+							tall_bts_ctx, lchan,
+							LOGL_ERROR, lchan->name);
 	return 0;
 }
 
diff --git a/src/common/msg_utils.c b/src/common/msg_utils.c
index 99a211f..92410b7 100644
--- a/src/common/msg_utils.c
+++ b/src/common/msg_utils.c
@@ -17,6 +17,7 @@
  *
  */
 
+#include <osmo-bts/dtx_dl_amr_fsm.h>
 #include <osmo-bts/msg_utils.h>
 #include <osmo-bts/logging.h>
 #include <osmo-bts/oml.h>
@@ -26,6 +27,7 @@
 #include <osmocom/gsm/protocol/gsm_12_21.h>
 #include <osmocom/gsm/abis_nm.h>
 #include <osmocom/core/msgb.h>
+#include <osmocom/core/fsm.h>
 #include <osmocom/trau/osmo_ortp.h>
 
 #include <arpa/inet.h>
@@ -99,19 +101,15 @@
 	}
 }
 
-
 /*! \brief Store the last SID frame in lchan context
  *  \param[in] lchan Logical channel on which we check scheduling
  *  \param[in] l1_payload buffer with SID data
  *  \param[in] length length of l1_payload
  *  \param[in] fn Frame Number for which we check scheduling
  *  \param[in] update 0 if SID_FIRST, 1 if SID_UPDATE, -1 if not AMR SID
- *  \param[in] cmr Codec Mode Request (AMR-specific, ignored otherwise)
- *  \param[in] cmi Codec Mode Indication (AMR-specific, ignored otherwise)
  */
-void save_last_sid(struct gsm_lchan *lchan, const uint8_t *l1_payload,
-		   size_t length, uint32_t fn, int update, uint8_t cmr,
-		   int8_t cmi)
+void dtx_cache_payload(struct gsm_lchan *lchan, const uint8_t *l1_payload,
+		       size_t length, uint32_t fn, int update)
 {
 	size_t amr = (update < 0) ? 0 : 2,
 	    copy_len = OSMO_MIN(length + 1, ARRAY_SIZE(lchan->tch.last_sid.buf));
@@ -120,62 +118,91 @@
 	lchan->tch.last_sid.fn = fn;
 	lchan->tch.last_sid.is_update = update;
 
-	if (amr)
-		amr_set_mode_pref(lchan->tch.last_sid.buf, &lchan->tch.amr_mr,
-				  cmi, cmr);
 	memcpy(lchan->tch.last_sid.buf + amr, l1_payload, copy_len);
 }
 
-/*! \brief Check current and cached SID to decide if talkspurt takes place
+/*! \brief Check current state of DTX DL AMR FSM and dispatch necessary events
  *  \param[in] lchan Logical channel on which we check scheduling
  *  \param[in] rtp_pl buffer with RTP data
  *  \param[in] rtp_pl_len length of rtp_pl
  *  \param[in] fn Frame Number for which we check scheduling
  *  \param[in] l1_payload buffer where CMR and CMI prefix should be added
+ *  \param[out] len Length of expected L1 payload
  *  \param[out] ft_out Frame Type to be populated after decoding
- *  \returns 0 if frame should be send immediately (2 byte CMR,CMI prefix added:
- *           caller must adjust length as necessary),
- *           1 if ONSET event is detected
- *           negative if no sending is necessary (either error or cached SID
- *           UPDATE)
+ *  \returns 0 in case of success; negative on error
  */
-int dtx_amr_check_onset(struct gsm_lchan *lchan, const uint8_t *rtp_pl,
+int dtx_dl_amr_fsm_step(struct gsm_lchan *lchan, const uint8_t *rtp_pl,
 			size_t rtp_pl_len, uint32_t fn, uint8_t *l1_payload,
-			uint8_t *ft_out)
+			bool marker, uint8_t *len, uint8_t *ft_out)
 {
 	uint8_t cmr;
 	enum osmo_amr_type ft;
 	enum osmo_amr_quality bfi;
 	int8_t sti, cmi;
-	osmo_amr_rtp_dec(rtp_pl, rtp_pl_len, &cmr, &cmi, &ft, &bfi, &sti);
-	*ft_out = ft; /* only needed for old sysmo firmware */
+	int rc;
 
-	if (ft == AMR_SID) {
-		save_last_sid(lchan, rtp_pl, rtp_pl_len, fn, sti, cmr, cmi);
-		if (sti) /* SID_UPDATE should be cached and send later */
-			return -EAGAIN;
-		else { /* SID_FIRST - cached and send right away */
-			amr_set_mode_pref(l1_payload, &lchan->tch.amr_mr, cmi,
-					  cmr);
-			return 0;
-		}
+	if (rtp_pl == NULL) { /* SID-FIRST P1 -> P2 */
+		*len = 3;
+		memcpy(l1_payload, lchan->tch.last_sid.buf, 2);
+		osmo_fsm_inst_dispatch(lchan->tch.dtx_dl_amr_fsm, E_COMPL,
+				       (void *)lchan);
+		return 0;
 	}
 
-	if (ft != AMR_NO_DATA && !osmo_amr_is_speech(ft)) {
+	rc = osmo_amr_rtp_dec(rtp_pl, rtp_pl_len, &cmr, &cmi, &ft, &bfi, &sti);
+	if (rc < 0) {
+		LOGP(DRTP, LOGL_ERROR, "failed to decode AMR RTP (length %zu)\n",
+		     rtp_pl_len);
+		return rc;
+	}
+
+	/* only needed for old sysmo firmware: */
+	*ft_out = ft;
+
+	/* populate L1 payload with CMR/CMI - might be ignored by caller: */
+	amr_set_mode_pref(l1_payload, &lchan->tch.amr_mr, cmi == -1 ? 0 : cmi,
+			  cmr);
+	/* populate DTX cache with CMR/CMI - overwrite cache which will be
+	   either updated or invalidated by caller anyway: */
+	amr_set_mode_pref(lchan->tch.last_sid.buf, &lchan->tch.amr_mr,
+			  cmi == -1 ? 0 : cmi, cmr);
+	*len = 3 + rtp_pl_len;
+
+	/* DTX DL is not enabled, move along */
+	if (!lchan->ts->trx->bts->dtxd)
+		return 0;
+
+	if (osmo_amr_is_speech(ft)) {
+		if (lchan->tch.dtx_dl_amr_fsm->state == ST_SID_F1 ||
+		    lchan->tch.dtx_dl_amr_fsm->state == ST_SID_U) /* AMR HR */
+			if (lchan->type == GSM_LCHAN_TCH_H && marker)
+				return osmo_fsm_inst_dispatch(lchan->tch.dtx_dl_amr_fsm,
+							      E_INHIB,
+							      (void *)lchan);
+		/* AMR FR */
+		if (marker && lchan->tch.dtx_dl_amr_fsm->state == ST_SID_U)
+			return osmo_fsm_inst_dispatch(lchan->tch.dtx_dl_amr_fsm,
+						      E_ONSET, (void *)lchan);
+		return osmo_fsm_inst_dispatch(lchan->tch.dtx_dl_amr_fsm, E_VOICE,
+					      (void *)lchan);
+	}
+
+	if (ft == AMR_SID) {
+		dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, sti);
+		return osmo_fsm_inst_dispatch(lchan->tch.dtx_dl_amr_fsm,
+					      sti ? E_SID_U : E_SID_F,
+					      (void *)lchan);
+	}
+
+	if (ft != AMR_NO_DATA) {
 		LOGP(DRTP, LOGL_ERROR, "unsupported AMR FT 0x%02x\n", ft);
 		return -ENOTSUP;
 	}
 
-	if (osmo_amr_is_speech(ft)) {
-		if (lchan->tch.last_sid.len) { /* force ONSET */
-			lchan->tch.last_sid.len = 0;
-			return 1;
-		}
-		/* We received AMR SPEECH frame - invalidate saved SID */
-		lchan->tch.last_sid.len = 0;
-		amr_set_mode_pref(l1_payload, &lchan->tch.amr_mr, cmi, cmr);
-	}
-
+	if (marker)
+		osmo_fsm_inst_dispatch(lchan->tch.dtx_dl_amr_fsm, E_VOICE,
+				       (void *)lchan);
+	*len = 0;
 	return 0;
 }
 
diff --git a/src/osmo-bts-litecell15/l1_if.c b/src/osmo-bts-litecell15/l1_if.c
index a4378c1..de0e6c9 100644
--- a/src/osmo-bts-litecell15/l1_if.c
+++ b/src/osmo-bts-litecell15/l1_if.c
@@ -432,7 +432,7 @@
 }
 
 static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg,
-		       struct osmo_phsap_prim *l1sap)
+		      struct osmo_phsap_prim *l1sap, bool use_cache, bool marker)
 {
 	struct lc15l1_hdl *fl1 = trx_lc15l1_hdl(trx);
 	struct gsm_lchan *lchan;
@@ -468,7 +468,7 @@
 		rc = l1if_tch_encode(lchan,
 				     l1p->u.phDataReq.msgUnitParam.u8Buffer,
 				     &l1p->u.phDataReq.msgUnitParam.u8Size,
-				     msg->data, msg->len, u32Fn,
+				     msg->data, msg->len, u32Fn, use_cache,
 				     l1sap->u.tch.marker);
 		if (rc < 0) {
 		/* no data encoded for L1: smth will be generated below */
@@ -506,10 +506,8 @@
 	/* send message to DSP's queue */
 	osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], nmsg);
 
-	if (l1sap->u.tch.marker) { /* DTX: send voice after ONSET was sent */
-		l1sap->u.tch.marker = 0;
-		return ph_tch_req(trx, l1sap->oph.msg, l1sap);
-	}
+	if (rc > 0 && trx->bts->dtxd) /* DTX: send voice after ONSET was sent */
+		return ph_tch_req(trx, l1sap->oph.msg, l1sap, true, false);
 
 	return 0;
 }
@@ -577,7 +575,7 @@
 		rc = ph_data_req(trx, msg, l1sap);
 		break;
 	case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST):
-		rc = ph_tch_req(trx, msg, l1sap);
+		rc = ph_tch_req(trx, msg, l1sap, false, l1sap->u.tch.marker);
 		break;
 	case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST):
 		rc = mph_info_req(trx, msg, l1sap);
diff --git a/src/osmo-bts-litecell15/l1_if.h b/src/osmo-bts-litecell15/l1_if.h
index adb197d..7feee56 100644
--- a/src/osmo-bts-litecell15/l1_if.h
+++ b/src/osmo-bts-litecell15/l1_if.h
@@ -90,8 +90,8 @@
 
 /* tch.c */
 int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len,
-		     const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn,
-		     bool marker);
+		    const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn,
+		    bool use_cache, bool marker);
 int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg);
 int l1if_tch_fill(struct gsm_lchan *lchan, uint8_t *l1_buffer);
 struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan, uint32_t fn);
diff --git a/src/osmo-bts-litecell15/tch.c b/src/osmo-bts-litecell15/tch.c
index e246ffb..fb16d07 100644
--- a/src/osmo-bts-litecell15/tch.c
+++ b/src/osmo-bts-litecell15/tch.c
@@ -45,6 +45,7 @@
 #include <osmo-bts/measurement.h>
 #include <osmo-bts/amr.h>
 #include <osmo-bts/l1sap.h>
+#include <osmo-bts/dtx_dl_amr_fsm.h>
 
 #include <nrw/litecell15/litecell15.h>
 #include <nrw/litecell15/gsml1prim.h>
@@ -198,8 +199,7 @@
  *  \returns number of \a l1_payload bytes filled
  */
 static int rtppayload_to_l1_amr(uint8_t *l1_payload, const uint8_t *rtp_payload,
-				uint8_t payload_len,
-				struct gsm_lchan *lchan, uint8_t ft)
+				uint8_t payload_len, uint8_t ft)
 {
 	memcpy(l1_payload, rtp_payload, payload_len);
 	return payload_len;
@@ -210,10 +210,11 @@
 /*! \brief function for incoming RTP via TCH.req
  *  \param[in] rtp_pl buffer containing RTP payload
  *  \param[in] rtp_pl_len length of \a rtp_pl
+ *  \param[in] use_cache Use cached payload instead of parsing RTP
  *  \param[in] marker RTP header Marker bit (indicates speech onset)
  *  \returns 0 if encoding result can be sent further to L1 without extra actions
  *           positive value if data is ready AND extra actions are required
- *           negative value otherwise
+ *           negative value otherwise (no data for L1 encoded)
  *
  * This function prepares a msgb with a L1 PH-DATA.req primitive and
  * queues it into lchan->dl_tch_queue.
@@ -223,7 +224,8 @@
  * pre-fill the primtive.
  */
 int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len,
-	const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn, bool marker)
+		    const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn,
+		    bool use_cache, bool marker)
 {
 	uint8_t *payload_type;
 	uint8_t *l1_payload, ft;
@@ -252,7 +254,7 @@
 				is_sid = osmo_hr_check_sid(rtp_pl, rtp_pl_len);
 		}
 		if (is_sid)
-			save_last_sid(lchan, rtp_pl, rtp_pl_len, fn, -1, 0, 0);
+			dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, -1);
 		break;
 	case GSM48_CMODE_SPEECH_EFR:
 		*payload_type = GsmL1_TchPlType_Efr;
@@ -261,25 +263,75 @@
 		/* FIXME: detect and save EFR SID */
 		break;
 	case GSM48_CMODE_SPEECH_AMR:
-		rc = dtx_amr_check_onset(lchan, rtp_pl, rtp_pl_len, fn,
-					 l1_payload, &ft);
-
-		if (marker || rc > 0) {
-			*payload_type = GsmL1_TchPlType_Amr_Onset;
-			*len = 1;
-			if (rc != 0) {
-				LOGP(DRTP, LOGL_NOTICE, "%s SPEECH frame without"
-				     " Marker: ONSET forced\n",
-				     get_value_string(osmo_amr_type_names, ft));
-				return rc;
-			}
-			LOGP(DRTP, LOGL_DEBUG, "%s SPEECH frame with Marker\n",
-			     get_value_string(osmo_amr_type_names, ft));
-		}
-		else {
+		if (use_cache) {
 			*payload_type = GsmL1_TchPlType_Amr;
-			rc = 2 + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl,
-						      rtp_pl_len, lchan, ft);
+			rtppayload_to_l1_amr(l1_payload, lchan->tch.last_sid.buf,
+					     lchan->tch.last_sid.len, ft);
+			*len = lchan->tch.last_sid.len + 1;
+			return 0;
+		}
+
+		rc = dtx_dl_amr_fsm_step(lchan, rtp_pl, rtp_pl_len, fn,
+					 l1_payload, marker, len, &ft);
+		if (rc < 0)
+			return rc;
+		if (!lchan->ts->trx->bts->dtxd) {
+			*payload_type = GsmL1_TchPlType_Amr;
+			rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
+					     ft);
+			return 0;
+		}
+
+		/* DTX DL-specific logic below: */
+		switch (lchan->tch.dtx_dl_amr_fsm->state) {
+		case ST_ONSET_V:
+		case ST_ONSET_F:
+			*payload_type = GsmL1_TchPlType_Amr_Onset;
+			dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0);
+			*len = 1;
+			return 1;
+		case ST_VOICE:
+			*payload_type = GsmL1_TchPlType_Amr;
+			rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
+					     ft);
+			return 0;
+		case ST_SID_F1:
+			if (lchan->type == GSM_LCHAN_TCH_H) { /* AMR HR */
+				*payload_type = GsmL1_TchPlType_Amr_SidFirstP1;
+				rtppayload_to_l1_amr(l1_payload + 2, rtp_pl,
+						     rtp_pl_len, ft);
+				return 0;
+			}
+			/* AMR FR */
+			*payload_type = GsmL1_TchPlType_Amr;
+			rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
+					     ft);
+			return 0;
+		case ST_SID_F2:
+			*payload_type = GsmL1_TchPlType_Amr;
+			rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
+					     ft);
+			return 0;
+		case ST_F1_INH:
+			*payload_type = GsmL1_TchPlType_Amr_SidFirstInH;
+			*len = 3;
+			dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0);
+			return 1;
+		case ST_U_INH:
+			*payload_type = GsmL1_TchPlType_Amr_SidUpdateInH;
+			*len = 3;
+			dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0);
+			return 1;
+		case ST_SID_U:
+			return -EAGAIN;
+		case ST_FACCH_V:
+		case ST_FACCH:
+			/* FIXME: if this possible at all? */
+			return 0;
+		default:
+			LOGP(DRTP, LOGL_ERROR, "Unhandled DTX DL AMR FSM state "
+			     "%d\n", lchan->tch.dtx_dl_amr_fsm->state);
+			return -EINVAL;
 		}
 		break;
 	default:
@@ -442,7 +494,20 @@
 
 	switch (lchan->tch_mode) {
 	case GSM48_CMODE_SPEECH_AMR:
-		*payload_type = GsmL1_TchPlType_Amr;
+		if (lchan->type == GSM_LCHAN_TCH_H &&
+		    lchan->tch.dtx_dl_amr_fsm->state == ST_SID_F1 &&
+		    lchan->ts->trx->bts->dtxd) {
+			*payload_type = GsmL1_TchPlType_Amr_SidFirstP2;
+			rc = dtx_dl_amr_fsm_step(lchan, NULL, 0, fn, l1_payload,
+						 false, &(msu_param->u8Size),
+						 NULL);
+			if (rc < 0) {
+				msgb_free(msg);
+				return NULL;
+			}
+			return msg;
+		} else
+			*payload_type = GsmL1_TchPlType_Amr;
 		break;
 	case GSM48_CMODE_SPEECH_V1:
 		if (lchan->type == GSM_LCHAN_TCH_F)
@@ -458,12 +523,13 @@
 		return NULL;
 	}
 
-	rc = repeat_last_sid(lchan, l1_payload, fn);
-	if (!rc) {
-		msgb_free(msg);
-		return NULL;
+	if (lchan->ts->trx->bts->dtxd) {
+		rc = repeat_last_sid(lchan, l1_payload, fn);
+		if (!rc) {
+			msgb_free(msg);
+			return NULL;
+		}
+		msu_param->u8Size = rc;
 	}
-	msu_param->u8Size = rc;
-
 	return msg;
 }
diff --git a/src/osmo-bts-sysmo/l1_if.c b/src/osmo-bts-sysmo/l1_if.c
index 09627af..a03a133 100644
--- a/src/osmo-bts-sysmo/l1_if.c
+++ b/src/osmo-bts-sysmo/l1_if.c
@@ -427,7 +427,7 @@
 }
 
 static int ph_tch_req(struct gsm_bts_trx *trx, struct msgb *msg,
-		       struct osmo_phsap_prim *l1sap)
+		      struct osmo_phsap_prim *l1sap, bool use_cache, bool marker)
 {
 	struct femtol1_hdl *fl1 = trx_femtol1_hdl(trx);
 	struct gsm_lchan *lchan;
@@ -463,7 +463,7 @@
 		rc = l1if_tch_encode(lchan,
 				     l1p->u.phDataReq.msgUnitParam.u8Buffer,
 				     &l1p->u.phDataReq.msgUnitParam.u8Size,
-				     msg->data, msg->len, u32Fn,
+				     msg->data, msg->len, u32Fn, use_cache,
 				     l1sap->u.tch.marker);
 		if (rc < 0) {
 		/* no data encoded for L1: smth will be generated below */
@@ -501,10 +501,8 @@
 	/* send message to DSP's queue */
 	osmo_wqueue_enqueue(&fl1->write_q[MQ_L1_WRITE], nmsg);
 
-	if (l1sap->u.tch.marker) { /* DTX: send voice after ONSET was sent */
-		l1sap->u.tch.marker = 0;
-		return ph_tch_req(trx, l1sap->oph.msg, l1sap);
-	}
+	if (rc > 0 && trx->bts->dtxd) /* DTX: send voice after ONSET was sent */
+		return ph_tch_req(trx, l1sap->oph.msg, l1sap, true, false);
 
 	return 0;
 }
@@ -572,7 +570,7 @@
 		rc = ph_data_req(trx, msg, l1sap);
 		break;
 	case OSMO_PRIM(PRIM_TCH, PRIM_OP_REQUEST):
-		rc = ph_tch_req(trx, msg, l1sap);
+		rc = ph_tch_req(trx, msg, l1sap, false, l1sap->u.tch.marker);
 		break;
 	case OSMO_PRIM(PRIM_MPH_INFO, PRIM_OP_REQUEST):
 		rc = mph_info_req(trx, msg, l1sap);
diff --git a/src/osmo-bts-sysmo/l1_if.h b/src/osmo-bts-sysmo/l1_if.h
index ece7a01..0dc919b 100644
--- a/src/osmo-bts-sysmo/l1_if.h
+++ b/src/osmo-bts-sysmo/l1_if.h
@@ -110,8 +110,8 @@
 
 /* tch.c */
 int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len,
-		     const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn,
-		     bool marker);
+		    const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn,
+		    bool use_cache, bool marker);
 int l1if_tch_rx(struct gsm_bts_trx *trx, uint8_t chan_nr, struct msgb *l1p_msg);
 int l1if_tch_fill(struct gsm_lchan *lchan, uint8_t *l1_buffer);
 struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan, uint32_t fn);
diff --git a/src/osmo-bts-sysmo/tch.c b/src/osmo-bts-sysmo/tch.c
index ca9045e..bacaeda 100644
--- a/src/osmo-bts-sysmo/tch.c
+++ b/src/osmo-bts-sysmo/tch.c
@@ -42,6 +42,7 @@
 #include <osmo-bts/measurement.h>
 #include <osmo-bts/amr.h>
 #include <osmo-bts/l1sap.h>
+#include <osmo-bts/dtx_dl_amr_fsm.h>
 
 #include <sysmocom/femtobts/superfemto.h>
 #include <sysmocom/femtobts/gsml1prim.h>
@@ -281,8 +282,7 @@
  *  \returns number of \a l1_payload bytes filled
  */
 static int rtppayload_to_l1_amr(uint8_t *l1_payload, const uint8_t *rtp_payload,
-				uint8_t payload_len,
-				struct gsm_lchan *lchan, uint8_t ft)
+				uint8_t payload_len, uint8_t ft)
 {
 #ifdef USE_L1_RTP_MODE
 	memcpy(l1_payload, rtp_payload, payload_len);
@@ -306,10 +306,11 @@
 /*! \brief function for incoming RTP via TCH.req
  *  \param[in] rtp_pl buffer containing RTP payload
  *  \param[in] rtp_pl_len length of \a rtp_pl
+ *  \param[in] use_cache Use cached payload instead of parsing RTP
  *  \param[in] marker RTP header Marker bit (indicates speech onset)
  *  \returns 0 if encoding result can be sent further to L1 without extra actions
  *           positive value if data is ready AND extra actions are required
- *           negative value otherwise
+ *           negative value otherwise (no data for L1 encoded)
  *
  * This function prepares a msgb with a L1 PH-DATA.req primitive and
  * queues it into lchan->dl_tch_queue.
@@ -319,7 +320,8 @@
  * pre-fill the primtive.
  */
 int l1if_tch_encode(struct gsm_lchan *lchan, uint8_t *data, uint8_t *len,
-	const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn, bool marker)
+		    const uint8_t *rtp_pl, unsigned int rtp_pl_len, uint32_t fn,
+		    bool use_cache, bool marker)
 {
 	uint8_t *payload_type;
 	uint8_t *l1_payload, ft;
@@ -348,7 +350,7 @@
 				is_sid = osmo_hr_check_sid(rtp_pl, rtp_pl_len);
 		}
 		if (is_sid)
-			save_last_sid(lchan, rtp_pl, rtp_pl_len, fn, -1, 0, 0);
+			dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, -1);
 		break;
 #if defined(L1_HAS_EFR) && defined(USE_L1_RTP_MODE)
 	case GSM48_CMODE_SPEECH_EFR:
@@ -359,25 +361,75 @@
 		break;
 #endif
 	case GSM48_CMODE_SPEECH_AMR:
-		rc = dtx_amr_check_onset(lchan, rtp_pl, rtp_pl_len, fn,
-					 l1_payload, &ft);
-
-		if (marker || rc > 0) {
-			*payload_type = GsmL1_TchPlType_Amr_Onset;
-			*len = 1;
-			if (rc != 0) {
-				LOGP(DRTP, LOGL_NOTICE, "%s SPEECH frame without"
-				     " Marker: ONSET forced\n",
-				     get_value_string(osmo_amr_type_names, ft));
-				return rc;
-			}
-			LOGP(DRTP, LOGL_DEBUG, "%s SPEECH frame with Marker\n",
-			     get_value_string(osmo_amr_type_names, ft));
-		}
-		else {
+		if (use_cache) {
 			*payload_type = GsmL1_TchPlType_Amr;
-			rc = 2 + rtppayload_to_l1_amr(l1_payload + 2, rtp_pl,
-						      rtp_pl_len, lchan, ft);
+			rtppayload_to_l1_amr(l1_payload, lchan->tch.last_sid.buf,
+					     lchan->tch.last_sid.len, ft);
+			*len = lchan->tch.last_sid.len + 1;
+			return 0;
+		}
+
+		rc = dtx_dl_amr_fsm_step(lchan, rtp_pl, rtp_pl_len, fn,
+					 l1_payload, marker, len, &ft);
+		if (rc < 0)
+			return rc;
+		if (!lchan->ts->trx->bts->dtxd) {
+			*payload_type = GsmL1_TchPlType_Amr;
+			rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
+					     ft);
+			return 0;
+		}
+
+		/* DTX DL-specific logic below: */
+		switch (lchan->tch.dtx_dl_amr_fsm->state) {
+		case ST_ONSET_V:
+		case ST_ONSET_F:
+			*payload_type = GsmL1_TchPlType_Amr_Onset;
+			dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0);
+			*len = 1;
+			return 1;
+		case ST_VOICE:
+			*payload_type = GsmL1_TchPlType_Amr;
+			rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
+					     ft);
+			return 0;
+		case ST_SID_F1:
+			if (lchan->type == GSM_LCHAN_TCH_H) { /* AMR HR */
+				*payload_type = GsmL1_TchPlType_Amr_SidFirstP1;
+				rtppayload_to_l1_amr(l1_payload + 2, rtp_pl,
+						     rtp_pl_len, ft);
+				return 0;
+			}
+			/* AMR FR */
+			*payload_type = GsmL1_TchPlType_Amr;
+			rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
+					     ft);
+			return 0;
+		case ST_SID_F2:
+			*payload_type = GsmL1_TchPlType_Amr;
+			rtppayload_to_l1_amr(l1_payload + 2, rtp_pl, rtp_pl_len,
+					     ft);
+			return 0;
+		case ST_F1_INH:
+			*payload_type = GsmL1_TchPlType_Amr_SidFirstInH;
+			*len = 3;
+			dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0);
+			return 1;
+		case ST_U_INH:
+			*payload_type = GsmL1_TchPlType_Amr_SidUpdateInH;
+			*len = 3;
+			dtx_cache_payload(lchan, rtp_pl, rtp_pl_len, fn, 0);
+			return 1;
+		case ST_SID_U:
+			return -EAGAIN;
+		case ST_FACCH_V:
+		case ST_FACCH:
+			/* FIXME: if this possible at all? */
+			return 0;
+		default:
+			LOGP(DRTP, LOGL_ERROR, "Unhandled DTX DL AMR FSM state "
+			     "%d\n", lchan->tch.dtx_dl_amr_fsm->state);
+			return -EINVAL;
 		}
 		break;
 	default:
@@ -544,7 +596,20 @@
 
 	switch (lchan->tch_mode) {
 	case GSM48_CMODE_SPEECH_AMR:
-		*payload_type = GsmL1_TchPlType_Amr;
+		if (lchan->type == GSM_LCHAN_TCH_H &&
+		    lchan->tch.dtx_dl_amr_fsm->state == ST_SID_F1 &&
+		    lchan->ts->trx->bts->dtxd) {
+			*payload_type = GsmL1_TchPlType_Amr_SidFirstP2;
+			rc = dtx_dl_amr_fsm_step(lchan, NULL, 0, fn, l1_payload,
+						 false, &(msu_param->u8Size),
+						 NULL);
+			if (rc < 0) {
+				msgb_free(msg);
+				return NULL;
+			}
+			return msg;
+		} else
+			*payload_type = GsmL1_TchPlType_Amr;
 		break;
 	case GSM48_CMODE_SPEECH_V1:
 		if (lchan->type == GSM_LCHAN_TCH_F)
@@ -560,12 +625,13 @@
 		return NULL;
 	}
 
-	rc = repeat_last_sid(lchan, l1_payload, fn);
-	if (!rc) {
-		msgb_free(msg);
-		return NULL;
+	if (lchan->ts->trx->bts->dtxd) {
+		rc = repeat_last_sid(lchan, l1_payload, fn);
+		if (!rc) {
+			msgb_free(msg);
+			return NULL;
+		}
+		msu_param->u8Size = rc;
 	}
-	msu_param->u8Size = rc;
-
 	return msg;
 }

-- 
To view, visit https://gerrit.osmocom.org/1030
To unsubscribe, visit https://gerrit.osmocom.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I74a0b42cb34d525b8a70d264135e82994ca70d31
Gerrit-PatchSet: 1
Gerrit-Project: osmo-bts
Gerrit-Branch: master
Gerrit-Owner: Max <msuraev at sysmocom.de>



More information about the gerrit-log mailing list