Change in osmo-bsc[master]: lchan_fsm: split off lchan_rtp_fsm, establish RTP a bit earlier

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

Neels Hofmeyr gerrit-no-reply at lists.osmocom.org
Mon Jul 23 01:00:26 UTC 2018


Neels Hofmeyr has uploaded this change for review. ( https://gerrit.osmocom.org/10103


Change subject: lchan_fsm: split off lchan_rtp_fsm, establish RTP a bit earlier
......................................................................

lchan_fsm: split off lchan_rtp_fsm, establish RTP a bit earlier

Change-Id: Id7a4407d9b63be05ce63f5f2768b7d7e3d5c86fb
---
M include/osmocom/bsc/Makefile.am
M include/osmocom/bsc/gsm_data.h
M include/osmocom/bsc/lchan_fsm.h
A include/osmocom/bsc/lchan_rtp_fsm.h
M src/osmo-bsc/Makefile.am
M src/osmo-bsc/abis_rsl.c
M src/osmo-bsc/bsc_subscr_conn_fsm.c
M src/osmo-bsc/handover_fsm.c
M src/osmo-bsc/lchan_fsm.c
A src/osmo-bsc/lchan_rtp_fsm.c
M tests/handover/Makefile.am
M tests/handover/handover_test.c
12 files changed, 1,038 insertions(+), 481 deletions(-)



  git pull ssh://gerrit.osmocom.org:29418/osmo-bsc refs/changes/03/10103/1

diff --git a/include/osmocom/bsc/Makefile.am b/include/osmocom/bsc/Makefile.am
index f1a9ce7..ac62d5e 100644
--- a/include/osmocom/bsc/Makefile.am
+++ b/include/osmocom/bsc/Makefile.am
@@ -29,6 +29,7 @@
 	handover_vty.h \
 	ipaccess.h \
 	lchan_fsm.h \
+	lchan_rtp_fsm.h \
 	lchan_select.h \
 	meas_feed.h \
 	meas_rep.h \
diff --git a/include/osmocom/bsc/gsm_data.h b/include/osmocom/bsc/gsm_data.h
index 7d89371..7bd0943 100644
--- a/include/osmocom/bsc/gsm_data.h
+++ b/include/osmocom/bsc/gsm_data.h
@@ -499,13 +499,15 @@
 	char *name;
 
 	struct osmo_fsm_inst *fi;
+	struct osmo_fsm_inst *fi_rtp;
 	struct mgwep_ci *mgw_endpoint_ci_bts;
 
 	struct {
 		enum lchan_activate_mode activ_for;
+		bool activ_ack; /*< true as soon as RSL Chan Activ Ack is received */
 		bool concluded; /*< true as soon as LCHAN_ST_ESTABLISHED is reached */
 		bool requires_voice_stream;
-		bool mgw_endpoint_available;
+		bool wait_before_switching_rtp; /*< true = requires LCHAN_EV_READY_TO_SWITCH_RTP */
 		uint16_t msc_assigned_cic;
 		enum gsm0808_cause gsm0808_error_cause;
 		struct gsm_lchan *re_use_mgw_endpoint_from_lchan;
diff --git a/include/osmocom/bsc/lchan_fsm.h b/include/osmocom/bsc/lchan_fsm.h
index 49701c1..35b8847 100644
--- a/include/osmocom/bsc/lchan_fsm.h
+++ b/include/osmocom/bsc/lchan_fsm.h
@@ -16,13 +16,9 @@
 	LCHAN_ST_UNUSED,
 	LCHAN_ST_WAIT_TS_READY,
 	LCHAN_ST_WAIT_ACTIV_ACK, /*< After RSL Chan Act Ack, lchan is active but RTP not configured. */
-	LCHAN_ST_WAIT_RLL_ESTABLISH,
-	LCHAN_ST_WAIT_MGW_ENDPOINT_AVAILABLE,
-	LCHAN_ST_WAIT_IPACC_CRCX_ACK,
-	LCHAN_ST_WAIT_IPACC_MDCX_ACK,
-	LCHAN_ST_WAIT_MGW_ENDPOINT_CONFIGURED,
+	LCHAN_ST_WAIT_RLL_RTP_ESTABLISH,
 	LCHAN_ST_ESTABLISHED, /*< Active and RTP is fully configured. */
-	LCHAN_ST_WAIT_SAPIS_RELEASED,
+	LCHAN_ST_WAIT_RLL_RTP_RELEASED,
 	LCHAN_ST_WAIT_BEFORE_RF_RELEASE,
 	LCHAN_ST_WAIT_RF_RELEASE_ACK,
 	LCHAN_ST_WAIT_AFTER_ERROR,
@@ -36,13 +32,9 @@
 	LCHAN_EV_RSL_CHAN_ACTIV_ACK,
 	LCHAN_EV_RSL_CHAN_ACTIV_NACK,
 	LCHAN_EV_RLL_ESTABLISH_IND,
-	LCHAN_EV_MGW_ENDPOINT_AVAILABLE,
-	LCHAN_EV_MGW_ENDPOINT_CONFIGURED,
-	LCHAN_EV_MGW_ENDPOINT_ERROR,
-	LCHAN_EV_IPACC_CRCX_ACK,
-	LCHAN_EV_IPACC_CRCX_NACK,
-	LCHAN_EV_IPACC_MDCX_ACK,
-	LCHAN_EV_IPACC_MDCX_NACK,
+	LCHAN_EV_RTP_READY,
+	LCHAN_EV_RTP_ERROR,
+	LCHAN_EV_RTP_RELEASED,
 	LCHAN_EV_RLL_REL_IND,
 	LCHAN_EV_RLL_REL_CONF,
 	LCHAN_EV_RSL_RF_CHAN_REL_ACK,
@@ -66,11 +58,13 @@
 	 * When a dyn TS was selected, the lchan->type has been set to the desired rate. */
 	enum gsm48_chan_mode chan_mode;
 	bool requires_voice_stream;
+	bool wait_before_switching_rtp;
 	uint16_t msc_assigned_cic;
 	struct gsm_lchan *old_lchan;
 };
 
 void lchan_activate(struct gsm_lchan *lchan, struct lchan_activate_info *info);
+void lchan_ready_to_switch_rtp(struct gsm_lchan *lchan);
 
 static inline const char *lchan_state_name(struct gsm_lchan *lchan)
 {
@@ -86,4 +80,5 @@
 bool lchan_may_receive_data(struct gsm_lchan *lchan);
 
 void lchan_forget_conn(struct gsm_lchan *lchan);
-void lchan_forget_mgw_endpoint(struct gsm_lchan *lchan);
+
+void lchan_set_last_error(struct gsm_lchan *lchan, const char *fmt, ...);
diff --git a/include/osmocom/bsc/lchan_rtp_fsm.h b/include/osmocom/bsc/lchan_rtp_fsm.h
new file mode 100644
index 0000000..fa0e746
--- /dev/null
+++ b/include/osmocom/bsc/lchan_rtp_fsm.h
@@ -0,0 +1,45 @@
+/* osmo-bsc API to manage lchans, logical channels in GSM cells. */
+#pragma once
+
+#define LOG_LCHAN_RTP(lchan, level, fmt, args...) do { \
+	if (lchan->fi_rtp) \
+		LOGPFSML(lchan->fi_rtp, level, fmt, ## args); \
+	else \
+		LOGP(DLMGCP, level, "%s (not initialized) " fmt, gsm_lchan_name(lchan), \
+		     ## args); \
+	} while(0)
+
+struct gsm_lchan;
+
+enum lchan_rtp_fsm_state {
+	LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_AVAILABLE,
+	LCHAN_RTP_ST_WAIT_LCHAN_READY,
+	LCHAN_RTP_ST_WAIT_IPACC_CRCX_ACK,
+	LCHAN_RTP_ST_WAIT_IPACC_MDCX_ACK,
+	LCHAN_RTP_ST_WAIT_READY_TO_SWITCH_RTP,
+	LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_CONFIGURED,
+	LCHAN_RTP_ST_READY,
+	LCHAN_RTP_ST_ROLLBACK,
+	LCHAN_RTP_ST_ESTABLISHED,
+};
+
+enum lchan_rtp_fsm_event {
+	LCHAN_RTP_EV_LCHAN_READY,
+	LCHAN_RTP_EV_READY_TO_SWITCH_RTP,
+	LCHAN_RTP_EV_MGW_ENDPOINT_AVAILABLE,
+	LCHAN_RTP_EV_MGW_ENDPOINT_ERROR,
+	LCHAN_RTP_EV_IPACC_CRCX_ACK,
+	LCHAN_RTP_EV_IPACC_CRCX_NACK,
+	LCHAN_RTP_EV_IPACC_MDCX_ACK,
+	LCHAN_RTP_EV_IPACC_MDCX_NACK,
+	LCHAN_RTP_EV_READY_TO_SWITCH,
+	LCHAN_RTP_EV_MGW_ENDPOINT_CONFIGURED,
+	LCHAN_RTP_EV_ROLLBACK, /*< Give the RTP back to the old lchan, if any */
+	LCHAN_RTP_EV_ESTABLISHED, /*< All done, forget about the old lchan, if any */
+	LCHAN_RTP_EV_RELEASE,
+};
+
+void lchan_rtp_fsm_start(struct gsm_lchan *lchan);
+struct mgwep_ci *lchan_use_mgw_endpoint_ci_bts(struct gsm_lchan *lchan);
+bool lchan_rtp_established(struct gsm_lchan *lchan);
+void lchan_forget_mgw_endpoint(struct gsm_lchan *lchan);
diff --git a/src/osmo-bsc/Makefile.am b/src/osmo-bsc/Makefile.am
index 0c9d93c..5b717a1 100644
--- a/src/osmo-bsc/Makefile.am
+++ b/src/osmo-bsc/Makefile.am
@@ -67,6 +67,7 @@
 	handover_logic.c \
 	handover_vty.c \
 	lchan_fsm.c \
+	lchan_rtp_fsm.c \
 	lchan_select.c \
 	meas_feed.c \
 	meas_rep.c \
diff --git a/src/osmo-bsc/abis_rsl.c b/src/osmo-bsc/abis_rsl.c
index 28002e4..7ab97a9 100644
--- a/src/osmo-bsc/abis_rsl.c
+++ b/src/osmo-bsc/abis_rsl.c
@@ -35,6 +35,7 @@
 #include <osmocom/gsm/gsm_utils.h>
 #include <osmocom/bsc/abis_rsl.h>
 #include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/lchan_rtp_fsm.h>
 #include <osmocom/bsc/bsc_rll.h>
 #include <osmocom/bsc/debug.h>
 #include <osmocom/gsm/tlv.h>
@@ -1885,6 +1886,11 @@
 	struct tlv_parsed tv;
 	struct gsm_lchan *lchan = msg->lchan;
 
+	if (!lchan->fi_rtp) {
+		LOG_LCHAN(msg->lchan, LOGL_ERROR, "Rx RSL IPACC: CRCX ACK message for unconfigured lchan");
+		return -EINVAL;
+	}
+
 	/* the BTS has acknowledged a local bind, it now tells us the IP
 	* address and port number to which it has bound the given logical
 	* channel */
@@ -1899,17 +1905,37 @@
 
 	ipac_parse_rtp(lchan, &tv, "CRCX");
 
-	osmo_fsm_inst_dispatch(lchan->fi, LCHAN_EV_IPACC_CRCX_ACK, 0);
+	osmo_fsm_inst_dispatch(lchan->fi_rtp, LCHAN_RTP_EV_IPACC_CRCX_ACK, 0);
 
 	return 0;
 }
 
+static int abis_rsl_rx_ipacc_crcx_nack(struct msgb *msg)
+{
+	struct e1inp_sign_link *sign_link = msg->dst;
+	struct gsm_lchan *lchan = msg->lchan;
+
+	rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]);
+
+	if (!lchan->fi_rtp) {
+		LOG_LCHAN(msg->lchan, LOGL_ERROR, "Rx RSL IPACC: CRCX NACK message for unconfigured lchan");
+		return -EINVAL;
+	}
+	osmo_fsm_inst_dispatch(msg->lchan->fi_rtp, LCHAN_RTP_EV_IPACC_CRCX_NACK, 0);
+	return 0;
+}
+
 static int abis_rsl_rx_ipacc_mdcx_ack(struct msgb *msg)
 {
 	struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
 	struct tlv_parsed tv;
 	struct gsm_lchan *lchan = msg->lchan;
 
+	if (!lchan->fi_rtp) {
+		LOG_LCHAN(msg->lchan, LOGL_ERROR, "Rx RSL IPACC: MDCX ACK message for unconfigured lchan");
+		return -EINVAL;
+	}
+
 	/* the BTS has acknowledged a remote connect request and
 	 * it now tells us the IP address and port number to which it has
 	 * connected the given logical channel */
@@ -1917,11 +1943,26 @@
 	rsl_tlv_parse(&tv, dh->data, msgb_l2len(msg)-sizeof(*dh));
 	ipac_parse_rtp(lchan, &tv, "MDCX");
 
-	osmo_fsm_inst_dispatch(lchan->fi, LCHAN_EV_IPACC_MDCX_ACK, 0);
+	osmo_fsm_inst_dispatch(lchan->fi_rtp, LCHAN_RTP_EV_IPACC_MDCX_ACK, 0);
 
 	return 0;
 }
 
+static int abis_rsl_rx_ipacc_mdcx_nack(struct msgb *msg)
+{
+	struct e1inp_sign_link *sign_link = msg->dst;
+	struct gsm_lchan *lchan = msg->lchan;
+
+	rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]);
+
+	if (!lchan->fi_rtp) {
+		LOG_LCHAN(msg->lchan, LOGL_ERROR, "Rx RSL IPACC: MDCX NACK message for unconfigured lchan");
+		return -EINVAL;
+	}
+	osmo_fsm_inst_dispatch(msg->lchan->fi_rtp, LCHAN_RTP_EV_IPACC_MDCX_NACK, 0);
+	return 0;
+}
+
 static int abis_rsl_rx_ipacc_dlcx_ind(struct msgb *msg)
 {
 	struct abis_rsl_dchan_hdr *dh = msgb_l2(msg);
@@ -1964,8 +2005,7 @@
 	case RSL_MT_IPAC_CRCX_NACK:
 		/* somehow the BTS was unable to bind the lchan to its local
 		 * port?!? */
-		rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]);
-		osmo_fsm_inst_dispatch(msg->lchan->fi, LCHAN_EV_IPACC_CRCX_NACK, 0);
+		rc = abis_rsl_rx_ipacc_crcx_nack(msg);
 		break;
 	case RSL_MT_IPAC_MDCX_ACK:
 		/* the BTS tells us that a connect operation was successful */
@@ -1974,8 +2014,7 @@
 	case RSL_MT_IPAC_MDCX_NACK:
 		/* somehow the BTS was unable to connect the lchan to a remote
 		 * port */
-		rate_ctr_inc(&sign_link->trx->bts->bts_ctrs->ctr[BTS_CTR_RSL_IPA_NACK]);
-		osmo_fsm_inst_dispatch(msg->lchan->fi, LCHAN_EV_IPACC_MDCX_NACK, 0);
+		rc = abis_rsl_rx_ipacc_mdcx_nack(msg);
 		break;
 	case RSL_MT_IPAC_DLCX_IND:
 		rc = abis_rsl_rx_ipacc_dlcx_ind(msg);
diff --git a/src/osmo-bsc/bsc_subscr_conn_fsm.c b/src/osmo-bsc/bsc_subscr_conn_fsm.c
index f56b2af..c6c291a 100644
--- a/src/osmo-bsc/bsc_subscr_conn_fsm.c
+++ b/src/osmo-bsc/bsc_subscr_conn_fsm.c
@@ -28,6 +28,7 @@
 #include <osmocom/bsc/gsm_data.h>
 #include <osmocom/bsc/handover_fsm.h>
 #include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/lchan_rtp_fsm.h>
 #include <osmocom/bsc/bsc_subscriber.h>
 #include <osmocom/bsc/osmo_bsc_sigtran.h>
 #include <osmocom/bsc/osmo_bsc_lcls.h>
@@ -597,7 +598,10 @@
 	conn->lchan = new_lchan;
 	conn->lchan->conn = conn;
 
-	if (old_lchan) {
+	if (conn->lchan->fi_rtp)
+		osmo_fsm_inst_dispatch(conn->lchan->fi_rtp, LCHAN_RTP_EV_ESTABLISHED, 0);
+
+	if (old_lchan && (old_lchan != new_lchan)) {
 		lchan_forget_conn(old_lchan);
 		lchan_release(old_lchan, false, false, 0);
 	}
diff --git a/src/osmo-bsc/handover_fsm.c b/src/osmo-bsc/handover_fsm.c
index e3f64cd..d70d049 100644
--- a/src/osmo-bsc/handover_fsm.c
+++ b/src/osmo-bsc/handover_fsm.c
@@ -32,6 +32,7 @@
 #include <osmocom/bsc/bsc_subscr_conn_fsm.h>
 #include <osmocom/bsc/lchan_select.h>
 #include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/lchan_rtp_fsm.h>
 #include <osmocom/bsc/gsm_04_08_utils.h>
 #include <osmocom/bsc/abis_rsl.h>
 #include <osmocom/bsc/bsc_msc_data.h>
@@ -359,6 +360,7 @@
 			.requires_voice_stream = conn->lchan->mgw_endpoint_ci_bts ? true : false,
 			.msc_assigned_cic = conn->ho.inter_bsc_in.msc_assigned_cic,
 			.old_lchan = conn->lchan,
+			.wait_before_switching_rtp = true,
 		};
 
 		lchan_activate(ho->new_lchan, &info);
@@ -845,6 +847,9 @@
 			}
 		}
 
+		if (ho->new_lchan->fi_rtp)
+			osmo_fsm_inst_dispatch(ho->new_lchan->fi_rtp,
+					       LCHAN_RTP_EV_READY_TO_SWITCH_RTP, 0);
 		ho_fsm_state_chg(HO_ST_WAIT_RR_HO_COMPLETE);
 		/* The lchan FSM will already start to redirect the RTP stream */
 		return;
@@ -853,7 +858,9 @@
 		LOG_HO(conn, LOGL_ERROR,
 			"Received RR Handover Complete, but haven't even seen a Handover Detect yet;"
 			" Accepting handover anyway\n");
-
+		if (ho->new_lchan->fi_rtp)
+			osmo_fsm_inst_dispatch(ho->new_lchan->fi_rtp,
+					       LCHAN_RTP_EV_READY_TO_SWITCH_RTP, 0);
 		ho_fsm_state_chg(HO_ST_WAIT_LCHAN_ESTABLISHED);
 		return;
 
diff --git a/src/osmo-bsc/lchan_fsm.c b/src/osmo-bsc/lchan_fsm.c
index f25e7b9..58adb99 100644
--- a/src/osmo-bsc/lchan_fsm.c
+++ b/src/osmo-bsc/lchan_fsm.c
@@ -1,5 +1,4 @@
-/* osmo-bsc API to allocate an lchan, complete with dyn TS switchover and MGCP communication to allocate
- * RTP endpoints.
+/* osmo-bsc API to allocate an lchan, complete with dyn TS switchover.
  *
  * (C) 2018 by sysmocom - s.f.m.c. GmbH <info at sysmocom.de>
  * All Rights Reserved
@@ -26,6 +25,7 @@
 
 #include <osmocom/bsc/debug.h>
 #include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/lchan_rtp_fsm.h>
 #include <osmocom/bsc/timeslot_fsm.h>
 #include <osmocom/bsc/mgw_endpoint_fsm.h>
 #include <osmocom/bsc/bsc_subscr_conn_fsm.h>
@@ -53,11 +53,7 @@
 		return false;
 
 	switch (lchan->fi->state) {
-	case LCHAN_ST_WAIT_RLL_ESTABLISH:
-	case LCHAN_ST_WAIT_MGW_ENDPOINT_AVAILABLE:
-	case LCHAN_ST_WAIT_IPACC_CRCX_ACK:
-	case LCHAN_ST_WAIT_IPACC_MDCX_ACK:
-	case LCHAN_ST_WAIT_MGW_ENDPOINT_CONFIGURED:
+	case LCHAN_ST_WAIT_RLL_RTP_ESTABLISH:
 	case LCHAN_ST_ESTABLISHED:
 		return true;
 	default:
@@ -65,7 +61,7 @@
 	}
 }
 
-static void lchan_set_last_error(struct gsm_lchan *lchan, const char *fmt, ...)
+void lchan_set_last_error(struct gsm_lchan *lchan, const char *fmt, ...)
 {
 	va_list ap;
 	/* This dance allows using an existing error reason in above fmt */
@@ -137,11 +133,12 @@
 	}
 }
 
-static void lchan_on_activation_success(struct gsm_lchan *lchan)
+static void lchan_on_fully_established(struct gsm_lchan *lchan)
 {
 	switch (lchan->activate.activ_for) {
 	case FOR_MS_CHANNEL_REQUEST:
-		/* Nothing to do here, MS is free to use the channel. */
+		/* No signalling to do here, MS is free to use the channel, and should go on to connect
+		 * to the MSC and establish a subscriber connection. */
 		break;
 
 	case FOR_ASSIGNMENT:
@@ -161,6 +158,9 @@
 		}
 		osmo_fsm_inst_dispatch(lchan->conn->assignment.fi, ASSIGNMENT_EV_LCHAN_ESTABLISHED,
 				       lchan);
+		/* The lchan->fi_rtp will be notified of LCHAN_RTP_EV_ESTABLISHED in
+		 * gscon_change_primary_lchan() upon assignment_success(). On failure before then, we
+		 * will try to roll back a modified RTP connection. */
 		break;
 
 	case FOR_HANDOVER:
@@ -178,6 +178,9 @@
 			break;
 		}
 		osmo_fsm_inst_dispatch(lchan->conn->ho.fi, HO_EV_LCHAN_ESTABLISHED, lchan);
+		/* The lchan->fi_rtp will be notified of LCHAN_RTP_EV_ESTABLISHED in
+		 * gscon_change_primary_lchan() upon handover_end(HO_RESULT_OK). On failure before then,
+		 * we will try to roll back a modified RTP connection. */
 		break;
 
 	default:
@@ -190,12 +193,8 @@
 struct state_timeout lchan_fsm_timeouts[32] = {
 	[LCHAN_ST_WAIT_TS_READY]	= { .T=23001 },
 	[LCHAN_ST_WAIT_ACTIV_ACK]	= { .T=23002 },
-	[LCHAN_ST_WAIT_RLL_ESTABLISH]	= { .T=3101 },
-	[LCHAN_ST_WAIT_MGW_ENDPOINT_AVAILABLE] = { .T=23004 },
-	[LCHAN_ST_WAIT_IPACC_CRCX_ACK]	= { .T=23005 },
-	[LCHAN_ST_WAIT_IPACC_MDCX_ACK]	= { .T=23006 },
-	[LCHAN_ST_WAIT_MGW_ENDPOINT_CONFIGURED] = { .T=23004 },
-	[LCHAN_ST_WAIT_SAPIS_RELEASED]	= { .T=3109 },
+	[LCHAN_ST_WAIT_RLL_RTP_ESTABLISH]	= { .T=3101 },
+	[LCHAN_ST_WAIT_RLL_RTP_RELEASED]	= { .T=3109 },
 	[LCHAN_ST_WAIT_BEFORE_RF_RELEASE]	= { .T=3111 },
 	[LCHAN_ST_WAIT_RF_RELEASE_ACK]	= { .T=3111 },
 	[LCHAN_ST_WAIT_AFTER_ERROR]	= { .T=993111 },
@@ -215,7 +214,7 @@
 #define lchan_fail_to(state_chg, fmt, args...) do { \
 		struct gsm_lchan *_lchan = fi->priv; \
 		uint32_t state_was = fi->state; \
-		lchan_set_last_error(fi->priv, "lchan %s in state %s: " fmt, \
+		lchan_set_last_error(_lchan, "lchan %s in state %s: " fmt, \
 				     _lchan->activate.concluded ? "failure" : "allocation failed", \
 				     osmo_fsm_state_name(fi->fsm, state_was), ## args); \
 		if (!_lchan->activate.concluded) \
@@ -229,13 +228,9 @@
 	[LCHAN_ST_UNUSED] 			= LCHAN_ST_UNUSED,
 	[LCHAN_ST_WAIT_TS_READY] 		= LCHAN_ST_UNUSED,
 	[LCHAN_ST_WAIT_ACTIV_ACK] 		= LCHAN_ST_BORKEN,
-	[LCHAN_ST_WAIT_RLL_ESTABLISH] 		= LCHAN_ST_WAIT_RF_RELEASE_ACK,
-	[LCHAN_ST_WAIT_MGW_ENDPOINT_AVAILABLE] 	= LCHAN_ST_WAIT_SAPIS_RELEASED,
-	[LCHAN_ST_WAIT_IPACC_CRCX_ACK] 		= LCHAN_ST_WAIT_SAPIS_RELEASED,
-	[LCHAN_ST_WAIT_IPACC_MDCX_ACK] 		= LCHAN_ST_WAIT_SAPIS_RELEASED,
-	[LCHAN_ST_WAIT_MGW_ENDPOINT_CONFIGURED]	= LCHAN_ST_WAIT_SAPIS_RELEASED,
-	[LCHAN_ST_ESTABLISHED] 			= LCHAN_ST_WAIT_SAPIS_RELEASED,
-	[LCHAN_ST_WAIT_SAPIS_RELEASED] 		= LCHAN_ST_WAIT_RF_RELEASE_ACK,
+	[LCHAN_ST_WAIT_RLL_RTP_ESTABLISH] 	= LCHAN_ST_WAIT_RF_RELEASE_ACK,
+	[LCHAN_ST_ESTABLISHED] 			= LCHAN_ST_WAIT_RLL_RTP_RELEASED,
+	[LCHAN_ST_WAIT_RLL_RTP_RELEASED] 		= LCHAN_ST_WAIT_RF_RELEASE_ACK,
 	[LCHAN_ST_WAIT_BEFORE_RF_RELEASE] 	= LCHAN_ST_WAIT_RF_RELEASE_ACK,
 	[LCHAN_ST_WAIT_RF_RELEASE_ACK] 		= LCHAN_ST_BORKEN,
 	[LCHAN_ST_WAIT_AFTER_ERROR] 		= LCHAN_ST_UNUSED,
@@ -322,11 +317,16 @@
 	osmo_fsm_inst_update_id_f(lchan->fi, "%u-%u-%u-%s-%u",
 				  lchan->ts->trx->bts->nr, lchan->ts->trx->nr, lchan->ts->nr,
 				  gsm_pchan_id(lchan->ts->pchan_on_init), lchan->nr);
+	if (lchan->fi_rtp)
+		osmo_fsm_inst_update_id_f(lchan->fi_rtp, lchan->fi->id);
 }
 
+extern void lchan_rtp_fsm_init();
+
 void lchan_fsm_init()
 {
 	OSMO_ASSERT(osmo_fsm_register(&lchan_fsm) == 0);
+	lchan_rtp_fsm_init();
 }
 
 void lchan_fsm_alloc(struct gsm_lchan *lchan)
@@ -357,6 +357,8 @@
 		talloc_free(lchan->rqd_ref);
 		lchan->rqd_ref = NULL;
 	}
+	if (lchan->fi_rtp)
+		osmo_fsm_inst_term(lchan->fi_rtp, OSMO_FSM_TERM_REQUEST, 0);
 	if (lchan->mgw_endpoint_ci_bts) {
 		mgw_endpoint_ci_dlcx(lchan->mgw_endpoint_ci_bts);
 		lchan->mgw_endpoint_ci_bts = NULL;
@@ -424,6 +426,7 @@
 		lchan->conn = info->for_conn;
 		lchan->activate.activ_for = info->activ_for;
 		lchan->activate.requires_voice_stream = info->requires_voice_stream;
+		lchan->activate.wait_before_switching_rtp = info->wait_before_switching_rtp;
 		lchan->activate.msc_assigned_cic = info->msc_assigned_cic;
 		lchan->activate.concluded = false;
 		lchan->activate.re_use_mgw_endpoint_from_lchan = info->old_lchan;
@@ -477,24 +480,8 @@
 	}
 }
 
-/* While activating an lchan, for example for Handover, we may want to re-use another lchan's MGW
- * endpoint CI. If Handover fails half way, the old lchan must keep its MGW endpoint CI, and we must not
- * clean it up. Hence keep another lchan's mgw_endpoint_ci_bts out of lchan until all is done. */
-static struct mgwep_ci *lchan_use_mgw_endpoint_ci_bts(struct gsm_lchan *lchan)
-{
-	if (lchan->mgw_endpoint_ci_bts)
-		return lchan->mgw_endpoint_ci_bts;
-	if (lchan_state_is(lchan, LCHAN_ST_ESTABLISHED))
-		return NULL;
-	if (lchan->activate.re_use_mgw_endpoint_from_lchan)
-		return lchan->activate.re_use_mgw_endpoint_from_lchan->mgw_endpoint_ci_bts;
-	return NULL;
-}
-
 static void lchan_fsm_wait_ts_ready_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
 {
-	struct mgw_endpoint *mgwep;
-	struct mgcp_conn_peer crcx_info = {};
 	struct gsm_lchan *lchan = lchan_fi_lchan(fi);
 	struct mgwep_ci *use_mgwep_ci = lchan_use_mgw_endpoint_ci_bts(lchan);
 
@@ -518,32 +505,8 @@
 	osmo_fsm_inst_dispatch(lchan->ts->fi, TS_EV_LCHAN_REQUESTED, lchan);
 
 	/* Prepare an MGW endpoint CI if appropriate. */
-	if (!lchan->activate.requires_voice_stream)
-		return;
-
-	if (use_mgwep_ci) {
-		lchan->activate.mgw_endpoint_available = true;
-		return;
-	}
-
-	mgwep = gscon_ensure_mgw_endpoint(lchan->conn, lchan->activate.msc_assigned_cic);
-	if (!mgwep) {
-		lchan_fail("Internal error: cannot obtain MGW endpoint handle for conn");
-		return;
-	}
-
-	lchan->mgw_endpoint_ci_bts = mgw_endpoint_ci_add(mgwep, "to-BTS");
-
-	if (lchan->conn)
-		crcx_info.call_id = lchan->conn->sccp.conn_id;
-	crcx_info.ptime = 20;
-	mgcp_pick_codec(&crcx_info, lchan);
-
-	mgw_endpoint_ci_request(lchan->mgw_endpoint_ci_bts,
-				MGCP_VERB_CRCX, &crcx_info,
-				lchan->fi,
-				LCHAN_EV_MGW_ENDPOINT_AVAILABLE,
-				LCHAN_EV_MGW_ENDPOINT_ERROR, 0);
+	if (lchan->activate.requires_voice_stream)
+		lchan_rtp_fsm_start(lchan);
 }
 
 static void lchan_fsm_wait_ts_ready(struct osmo_fsm_inst *fi, uint32_t event, void *data)
@@ -556,10 +519,18 @@
 		lchan_fsm_state_chg(LCHAN_ST_WAIT_ACTIV_ACK);
 		break;
 
-	case LCHAN_EV_MGW_ENDPOINT_AVAILABLE:
-		/* conn FSM is already done preparing an MGW endpoint. Remember that. */
-		lchan->activate.mgw_endpoint_available = true;
-		break;
+	case LCHAN_EV_RTP_RELEASED:
+	case LCHAN_EV_RTP_ERROR:
+		if (lchan->release_requested) {
+			/* Already in release, the RTP is not the initial cause of failure.
+			 * Just ignore. */
+			return;
+		}
+
+		lchan_fail("Failed to setup RTP stream: %s in state %s\n",
+			   osmo_fsm_event_name(fi->fsm, event),
+			   osmo_fsm_inst_state_name(fi));
+		return;
 
 	default:
 		OSMO_ASSERT(false);
@@ -597,18 +568,16 @@
 		lchan_fail_to(LCHAN_ST_UNUSED, "Tx Chan Activ failed: %s (%d)", strerror(-rc), rc);
 }
 
+static void lchan_fsm_post_activ_ack(struct osmo_fsm_inst *fi);
+
 static void lchan_fsm_wait_activ_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
 {
 	struct gsm_lchan *lchan = lchan_fi_lchan(fi);
 	switch (event) {
 
-	case LCHAN_EV_MGW_ENDPOINT_AVAILABLE:
-		lchan->activate.mgw_endpoint_available = true;
-		break;
-
 	case LCHAN_EV_RSL_CHAN_ACTIV_ACK:
-		/* Chan Activ was ack'd, but we need an RLL Establish to be sure it's working out. */
-		lchan_fsm_state_chg(LCHAN_ST_WAIT_RLL_ESTABLISH);
+		lchan->activate.activ_ack = true;
+		lchan_fsm_post_activ_ack(fi);
 		break;
 
 	case LCHAN_EV_RSL_CHAN_ACTIV_NACK:
@@ -632,12 +601,26 @@
 		}
 		break;
 
+	case LCHAN_EV_RTP_RELEASED:
+	case LCHAN_EV_RTP_ERROR:
+		if (lchan->release_requested) {
+			/* Already in release, the RTP is not the initial cause of failure.
+			 * Just ignore. */
+			return;
+		}
+
+		lchan_fail_to(LCHAN_ST_WAIT_RF_RELEASE_ACK,
+			      "Failed to setup RTP stream: %s in state %s\n",
+			      osmo_fsm_event_name(fi->fsm, event),
+			      osmo_fsm_inst_state_name(fi));
+		return;
+
 	default:
 		OSMO_ASSERT(false);
 	}
 }
 
-static void lchan_fsm_wait_rll_establish_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+static void lchan_fsm_post_activ_ack(struct osmo_fsm_inst *fi)
 {
 	int rc;
 	struct gsm_lchan *lchan = lchan_fi_lchan(fi);
@@ -703,62 +686,44 @@
 			  lchan_activate_mode_name(lchan->activate.activ_for));
 		break;
 	}
+
+	lchan_fsm_state_chg(LCHAN_ST_WAIT_RLL_RTP_ESTABLISH);
 }
 
-static void lchan_fsm_wait_rll_establish(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static void lchan_fsm_wait_rll_rtp_establish_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+	if (lchan->fi_rtp)
+		osmo_fsm_inst_dispatch(lchan->fi_rtp, LCHAN_RTP_EV_LCHAN_READY, 0);
+}
+
+static void lchan_fsm_wait_rll_rtp_establish(struct osmo_fsm_inst *fi, uint32_t event, void *data)
 {
 	struct gsm_lchan *lchan = lchan_fi_lchan(fi);
 	switch (event) {
 
-	case LCHAN_EV_MGW_ENDPOINT_AVAILABLE:
-		lchan->activate.mgw_endpoint_available = true;
-		break;
-
 	case LCHAN_EV_RLL_ESTABLISH_IND:
-		lchan->sapis[0] = LCHAN_SAPI_MS;
-		if (lchan->activate.requires_voice_stream) {
-			/* For Abis/IP, we would technically only need the MGW endpoint one step later,
-			 * on IPACC MDCX. But usually the MGW endpoint is anyway done by now, so keep one
-			 * common endpoint wait state for all BTS types. */
-			lchan_fsm_state_chg(LCHAN_ST_WAIT_MGW_ENDPOINT_AVAILABLE);
-		} else
+		if (!lchan->activate.requires_voice_stream
+		    || lchan_rtp_established(lchan))
 			lchan_fsm_state_chg(LCHAN_ST_ESTABLISHED);
-		break;
-
-	default:
-		OSMO_ASSERT(false);
-	}
-}
-
-static void lchan_fsm_tch_post_endpoint_available(struct osmo_fsm_inst *fi);
-
-static void lchan_fsm_wait_mgw_endpoint_available_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
-{
-	struct gsm_lchan *lchan = lchan_fi_lchan(fi);
-
-	if (lchan->release_requested) {
-		lchan_fail("Release requested while activating");
-		return;
-	}
-
-	if (lchan->activate.mgw_endpoint_available) {
-		LOG_LCHAN(lchan, LOGL_DEBUG, "MGW endpoint already available\n");
-		lchan_fsm_tch_post_endpoint_available(fi);
-	}
-}
-
-static void lchan_fsm_wait_mgw_endpoint_available(struct osmo_fsm_inst *fi, uint32_t event, void *data)
-{
-	struct gsm_lchan *lchan = lchan_fi_lchan(fi);
-	switch (event) {
-
-	case LCHAN_EV_MGW_ENDPOINT_AVAILABLE:
-		lchan->activate.mgw_endpoint_available = true;
-		lchan_fsm_tch_post_endpoint_available(fi);
 		return;
 
-	case LCHAN_EV_RLL_ESTABLISH_IND:
-		/* abis_rsl.c has noticed that a SAPI was established, no need to take action here. */
+	case LCHAN_EV_RTP_READY:
+		if (lchan->sapis[0] != LCHAN_SAPI_UNUSED)
+			lchan_fsm_state_chg(LCHAN_ST_ESTABLISHED);
+		return;
+
+	case LCHAN_EV_RTP_RELEASED:
+	case LCHAN_EV_RTP_ERROR:
+		if (lchan->release_requested) {
+			/* Already in release, the RTP is not the initial cause of failure.
+			 * Just ignore. */
+			return;
+		}
+
+		lchan_fail("Failed to setup RTP stream: %s in state %s\n",
+			   osmo_fsm_event_name(fi->fsm, event),
+			   osmo_fsm_inst_state_name(fi));
 		return;
 
 	default:
@@ -766,208 +731,6 @@
 	}
 }
 
-static void lchan_fsm_tch_post_endpoint_available(struct osmo_fsm_inst *fi)
-{
-	struct gsm_lchan *lchan = lchan_fi_lchan(fi);
-
-	LOG_LCHAN(lchan, LOGL_DEBUG, "MGW endpoint: %s\n",
-		  mgwep_ci_name(lchan_use_mgw_endpoint_ci_bts(lchan)));
-
-	if (is_ipaccess_bts(lchan->ts->trx->bts))
-		lchan_fsm_state_chg(LCHAN_ST_WAIT_IPACC_CRCX_ACK);
-	else
-		lchan_fsm_state_chg(LCHAN_ST_WAIT_MGW_ENDPOINT_CONFIGURED);
-}
-
-static void lchan_fsm_wait_ipacc_crcx_ack_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
-{
-	int rc;
-	int val;
-	struct gsm_lchan *lchan = lchan_fi_lchan(fi);
-
-	if (lchan->release_requested) {
-		lchan_fail("Release requested while activating");
-		return;
-	}
-
-	val = ipacc_speech_mode(lchan->tch_mode, lchan->type);
-	if (val < 0) {
-		lchan_fail("Cannot determine Abis/IP speech mode for tch_mode=%s type=%s\n",
-			   get_value_string(gsm48_chan_mode_names, lchan->tch_mode),
-			   gsm_lchant_name(lchan->type));
-		return;
-	}
-	lchan->abis_ip.speech_mode = val;
-
-	val = ipacc_payload_type(lchan->tch_mode, lchan->type);
-	if (val < 0) {
-		lchan_fail("Cannot determine Abis/IP payload type for tch_mode=%s type=%s\n",
-			   get_value_string(gsm48_chan_mode_names, lchan->tch_mode),
-			   gsm_lchant_name(lchan->type));
-		return;
-	}
-	lchan->abis_ip.rtp_payload = val;
-
-	/* recv-only */
-	ipacc_speech_mode_set_direction(&lchan->abis_ip.speech_mode, false);
-
-	rc = rsl_tx_ipacc_crcx(lchan);
-	if (rc)
-		lchan_fail("Failure to transmit IPACC CRCX to BTS (rc=%d, %s)",
-			   rc, strerror(-rc));
-}
-
-static void lchan_fsm_wait_ipacc_crcx_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
-{
-	switch (event) {
-
-	case LCHAN_EV_IPACC_CRCX_ACK:
-		/* the CRCX ACK parsing has already noted the RTP port information at
-		 * lchan->abis_ip.bound_*, see ipac_parse_rtp(). We'll use that in
-		 * lchan_fsm_wait_mgw_endpoint_configured_onenter(). */
-		lchan_fsm_state_chg(LCHAN_ST_WAIT_IPACC_MDCX_ACK);
-		return;
-
-	case LCHAN_EV_IPACC_CRCX_NACK:
-		lchan_fail("Received NACK on IPACC CRCX");
-		return;
-
-	case LCHAN_EV_RLL_ESTABLISH_IND:
-		/* abis_rsl.c has noticed that a SAPI was established, no need to take action here. */
-		return;
-
-	default:
-		OSMO_ASSERT(false);
-	}
-}
-
-static void lchan_fsm_wait_ipacc_mdcx_ack_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
-{
-	int rc;
-	struct gsm_lchan *lchan = lchan_fi_lchan(fi);
-	const struct mgcp_conn_peer *mgw_rtp;
-
-	if (lchan->release_requested) {
-		lchan_fail("Release requested while activating");
-		return;
-	}
-
-	mgw_rtp = mgwep_ci_get_rtp_info(lchan_use_mgw_endpoint_ci_bts(lchan));
-
-	if (!mgw_rtp) {
-		lchan_fail("Cannot send IPACC MDCX to BTS:"
-			   " there is no RTP IP+port set that the BTS should send RTP to.");
-		return;
-	}
-
-	/* Other RTP settings were already setup in lchan_fsm_wait_ipacc_crcx_ack_onenter() */
-	lchan->abis_ip.connect_ip = ntohl(inet_addr(mgw_rtp->addr));
-	lchan->abis_ip.connect_port = mgw_rtp->port;
-
-	/* send-recv */
-	ipacc_speech_mode_set_direction(&lchan->abis_ip.speech_mode, true);
-
-	rc = rsl_tx_ipacc_mdcx(lchan);
-	if (rc)
-		lchan_fail("Failure to transmit IPACC MDCX to BTS (rc=%d, %s)",
-			   rc, strerror(-rc));
-
-}
-
-static void lchan_fsm_wait_ipacc_mdcx_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
-{
-	switch (event) {
-
-	case LCHAN_EV_IPACC_MDCX_ACK:
-		/* Finally, the lchan and its RTP are established. */
-		lchan_fsm_state_chg(LCHAN_ST_WAIT_MGW_ENDPOINT_CONFIGURED);
-		return;
-
-	case LCHAN_EV_IPACC_MDCX_NACK:
-		lchan_fail("Received NACK on IPACC MDCX");
-		return;
-
-	case LCHAN_EV_RLL_ESTABLISH_IND:
-		/* abis_rsl.c has noticed that a SAPI was established, no need to take action here. */
-		return;
-
-	default:
-		OSMO_ASSERT(false);
-	}
-}
-
-/* Tell the MGW endpoint about the RTP port allocated on BTS side. */
-static void lchan_fsm_wait_mgw_endpoint_configured_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
-{
-	int rc;
-	struct mgcp_conn_peer mdcx_info;
-	struct in_addr addr;
-	const char *addr_str;
-	struct gsm_lchan *lchan = lchan_fi_lchan(fi);
-
-	if (lchan->release_requested) {
-		lchan_fail("Release requested while activating");
-		return;
-	}
-
-	mdcx_info = (struct mgcp_conn_peer){
-		.port = lchan->abis_ip.bound_port,
-		.ptime = 20,
-	};
-	mgcp_pick_codec(&mdcx_info, lchan);
-
-	addr.s_addr = osmo_ntohl(lchan->abis_ip.bound_ip);
-	addr_str = inet_ntoa(addr);
-	rc = osmo_strlcpy(mdcx_info.addr, addr_str, sizeof(mdcx_info.addr));
-	if (rc <= 0 || rc >= sizeof(mdcx_info.addr)) {
-		lchan_fail("Cannot compose BTS side RTP IP address to send to MGW: '%s'",
-			   addr_str);
-		return;
-	}
-
-	/* At this point, we are taking over an old lchan's MGW endpoint (if any). */
-	if (!lchan->mgw_endpoint_ci_bts
-	    && lchan->activate.re_use_mgw_endpoint_from_lchan) {
-		lchan->mgw_endpoint_ci_bts =
-			lchan->activate.re_use_mgw_endpoint_from_lchan->mgw_endpoint_ci_bts;
-		/* The old lchan shall forget the enpoint now. */
-		lchan->activate.re_use_mgw_endpoint_from_lchan->mgw_endpoint_ci_bts = NULL;
-	}
-
-	if (!lchan->mgw_endpoint_ci_bts) {
-		lchan_fail("No MGW endpoint ci configured");
-		return;
-	}
-
-	LOG_LCHAN(lchan, LOGL_DEBUG, "Sending BTS side RTP port info %s:%u to MGW %s\n",
-		  mdcx_info.addr, mdcx_info.port, mgwep_ci_name(lchan->mgw_endpoint_ci_bts));
-	mgw_endpoint_ci_request(lchan->mgw_endpoint_ci_bts, MGCP_VERB_MDCX,
-				&mdcx_info, fi, LCHAN_EV_MGW_ENDPOINT_CONFIGURED,
-				LCHAN_EV_MGW_ENDPOINT_ERROR, 0);
-}
-
-static void lchan_fsm_wait_mgw_endpoint_configured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
-{
-	switch (event) {
-
-	case LCHAN_EV_MGW_ENDPOINT_CONFIGURED:
-		lchan_fsm_state_chg(LCHAN_ST_ESTABLISHED);
-		return;
-
-	case LCHAN_EV_MGW_ENDPOINT_ERROR:
-		lchan_fail("Error while redirecting the MGW to the BTS' RTP port");
-		return;
-
-	case LCHAN_EV_RLL_ESTABLISH_IND:
-		/* abis_rsl.c has noticed that a SAPI was established, no need to take action here. */
-		return;
-
-	default:
-		OSMO_ASSERT(false);
-	}
-}
-
-
 static void lchan_fsm_established_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
 {
 	struct gsm_lchan *lchan = lchan_fi_lchan(fi);
@@ -981,7 +744,7 @@
 	 * like Immediate Assignment or BSSMAP Assignment Complete, and if then, way later, some other
 	 * error occurs, e.g. during release, that we don't send a NACK out of context. */
 	lchan->activate.concluded = true;
-	lchan_on_activation_success(lchan);
+	lchan_on_fully_established(lchan);
 }
 
 #define for_each_sapi(sapi, start, lchan) \
@@ -1015,8 +778,7 @@
 	return sapis;
 }
 
-static void handle_rll_rel_ind_or_conf(struct osmo_fsm_inst *fi, uint32_t event, void *data,
-				       bool wait_for_sapi0_rel)
+static void handle_rll_rel_ind_or_conf(struct osmo_fsm_inst *fi, uint32_t event, void *data)
 {
 	uint8_t link_id;
 	uint8_t sapi;
@@ -1043,12 +805,13 @@
 		gscon_lchan_releasing(lchan->conn, lchan);
 	}
 
-	if (!lchan_active_sapis(lchan, wait_for_sapi0_rel? 0 : 1))
-		lchan_fsm_state_chg(LCHAN_ST_WAIT_BEFORE_RF_RELEASE);
+	/* The caller shall check whether all SAPIs are released and cause a state chg */
 }
 
 static void lchan_fsm_established(struct osmo_fsm_inst *fi, uint32_t event, void *data)
 {
+	struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+
 	switch (event) {
 	case LCHAN_EV_RLL_ESTABLISH_IND:
 		/* abis_rsl.c has noticed that a SAPI was established, no need to take action here. */
@@ -1056,7 +819,22 @@
 
 	case LCHAN_EV_RLL_REL_IND:
 	case LCHAN_EV_RLL_REL_CONF:
-		handle_rll_rel_ind_or_conf(fi, event, data, true);
+		handle_rll_rel_ind_or_conf(fi, event, data);
+		if (!lchan_active_sapis(lchan, 0))
+			lchan_fsm_state_chg(LCHAN_ST_WAIT_RLL_RTP_RELEASED);
+		return;
+
+	case LCHAN_EV_RTP_RELEASED:
+	case LCHAN_EV_RTP_ERROR:
+		if (lchan->release_requested) {
+			/* Already in release, the RTP is not the initial cause of failure.
+			 * Just ignore. */
+			return;
+		}
+
+		lchan_fail("RTP stream closed unexpectedly: %s in state %s\n",
+			   osmo_fsm_event_name(fi->fsm, event),
+			   osmo_fsm_inst_state_name(fi));
 		return;
 
 	default:
@@ -1079,7 +857,7 @@
 	}
 }
 
-static void lchan_fsm_wait_sapis_released_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+static void lchan_fsm_wait_rll_rtp_released_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
 {
 	int sapis;
 	int sapi;
@@ -1089,9 +867,12 @@
 		if (lchan->sapis[sapi])
 			LOG_LCHAN(lchan, LOGL_DEBUG, "SAPI[%d] = %d\n", sapi, lchan->sapis[sapi]);
 
-	if (lchan->conn)
+	if (lchan->conn && lchan->sapis[0] != LCHAN_SAPI_UNUSED)
 		gsm48_send_rr_release(lchan);
 
+	if (lchan->fi_rtp)
+		osmo_fsm_inst_dispatch(lchan->fi_rtp, LCHAN_RTP_EV_RELEASE, 0);
+
 	if (lchan->deact_sacch && should_sacch_deact(lchan))
 		rsl_deact_sacch(lchan);
 
@@ -1117,16 +898,33 @@
 		sapis = 0;
 	}
 
-	if (!sapis)
+	if (!sapis && !lchan->fi_rtp)
 		lchan_fsm_state_chg(LCHAN_ST_WAIT_BEFORE_RF_RELEASE);
 }
 
-static void lchan_fsm_wait_sapis_released(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static void lchan_fsm_wait_rll_rtp_released(struct osmo_fsm_inst *fi, uint32_t event, void *data)
 {
-	/* When we're telling the MS to release, we're fine to carry on with RF Channel Release when SAPI
-	 * 0 release is not confirmed yet.
-	 * TODO: that's how the code was before lchan FSM, is this correct/useful? */
-	handle_rll_rel_ind_or_conf(fi, event, data, false);
+	struct gsm_lchan *lchan = lchan_fi_lchan(fi);
+	switch (event) {
+
+	case LCHAN_EV_RLL_REL_IND:
+	case LCHAN_EV_RLL_REL_CONF:
+		/* When we're telling the MS to release, we're fine to carry on with RF Channel Release
+		 * when SAPI 0 release is not confirmed yet.
+		 * TODO: that's how the code was before lchan FSM, is this correct/useful? */
+		handle_rll_rel_ind_or_conf(fi, event, data);
+		break;
+	
+	case LCHAN_EV_RTP_RELEASED:
+	case LCHAN_EV_RTP_ERROR:
+		break;
+
+	default:
+		OSMO_ASSERT(false);
+	}
+
+	if (!lchan_active_sapis(lchan, 1) && !lchan->fi_rtp)
+		lchan_fsm_state_chg(LCHAN_ST_WAIT_BEFORE_RF_RELEASE);
 }
 
 static void lchan_fsm_wait_rf_release_ack_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
@@ -1194,6 +992,10 @@
 		 * independently from the BTS model, right?? */
 		return;
 
+	case LCHAN_EV_RTP_RELEASED:
+	case LCHAN_EV_RTP_ERROR:
+		return;
+
 	default:
 		OSMO_ASSERT(false);
 	}
@@ -1219,7 +1021,8 @@
 		.action = lchan_fsm_wait_ts_ready,
 		.in_event_mask = 0
 			| S(LCHAN_EV_TS_READY)
-			| S(LCHAN_EV_MGW_ENDPOINT_AVAILABLE)
+			| S(LCHAN_EV_RTP_ERROR)
+			| S(LCHAN_EV_RTP_RELEASED)
 			,
 		.out_state_mask = 0
 			| S(LCHAN_ST_UNUSED)
@@ -1231,94 +1034,33 @@
 		.onenter = lchan_fsm_wait_activ_ack_onenter,
 		.action = lchan_fsm_wait_activ_ack,
 		.in_event_mask = 0
-			| S(LCHAN_EV_MGW_ENDPOINT_AVAILABLE)
 			| S(LCHAN_EV_RSL_CHAN_ACTIV_ACK)
 			| S(LCHAN_EV_RSL_CHAN_ACTIV_NACK)
+			| S(LCHAN_EV_RTP_ERROR)
+			| S(LCHAN_EV_RTP_RELEASED)
 			,
 		.out_state_mask = 0
 			| S(LCHAN_ST_UNUSED)
-			| S(LCHAN_ST_WAIT_RLL_ESTABLISH)
+			| S(LCHAN_ST_WAIT_RLL_RTP_ESTABLISH)
 			| S(LCHAN_ST_BORKEN)
 			| S(LCHAN_ST_WAIT_RF_RELEASE_ACK)
 			,
 	},
-	[LCHAN_ST_WAIT_RLL_ESTABLISH] = {
-		.name = "WAIT_RLL_ESTABLISH",
-		.onenter = lchan_fsm_wait_rll_establish_onenter,
-		.action = lchan_fsm_wait_rll_establish,
+	[LCHAN_ST_WAIT_RLL_RTP_ESTABLISH] = {
+		.name = "WAIT_RLL_RTP_ESTABLISH",
+		.onenter = lchan_fsm_wait_rll_rtp_establish_onenter,
+		.action = lchan_fsm_wait_rll_rtp_establish,
 		.in_event_mask = 0
-			| S(LCHAN_EV_MGW_ENDPOINT_AVAILABLE)
 			| S(LCHAN_EV_RLL_ESTABLISH_IND)
-			,
-		.out_state_mask = 0
-			| S(LCHAN_ST_UNUSED)
-			| S(LCHAN_ST_WAIT_MGW_ENDPOINT_AVAILABLE)
-			| S(LCHAN_ST_ESTABLISHED)
-			| S(LCHAN_ST_WAIT_RF_RELEASE_ACK)
-			| S(LCHAN_ST_WAIT_SAPIS_RELEASED)
-			,
-	},
-	[LCHAN_ST_WAIT_MGW_ENDPOINT_AVAILABLE] = {
-		.name = "WAIT_MGW_ENDPOINT_AVAILABLE",
-		.onenter = lchan_fsm_wait_mgw_endpoint_available_onenter,
-		.action = lchan_fsm_wait_mgw_endpoint_available,
-		.in_event_mask = 0
-			| S(LCHAN_EV_MGW_ENDPOINT_AVAILABLE)
-			| S(LCHAN_EV_RLL_ESTABLISH_IND) /* ignored */
-			,
-		.out_state_mask = 0
-			| S(LCHAN_ST_UNUSED)
-			| S(LCHAN_ST_WAIT_IPACC_CRCX_ACK)
-			| S(LCHAN_ST_WAIT_MGW_ENDPOINT_CONFIGURED)
-			| S(LCHAN_ST_WAIT_SAPIS_RELEASED)
-			| S(LCHAN_ST_WAIT_RF_RELEASE_ACK)
-			,
-	},
-	[LCHAN_ST_WAIT_IPACC_CRCX_ACK] = {
-		.name = "WAIT_IPACC_CRCX_ACK",
-		.onenter = lchan_fsm_wait_ipacc_crcx_ack_onenter,
-		.action = lchan_fsm_wait_ipacc_crcx_ack,
-		.in_event_mask = 0
-			| S(LCHAN_EV_IPACC_CRCX_ACK)
-			| S(LCHAN_EV_IPACC_CRCX_NACK)
-			| S(LCHAN_EV_RLL_ESTABLISH_IND) /* ignored */
-			,
-		.out_state_mask = 0
-			| S(LCHAN_ST_UNUSED)
-			| S(LCHAN_ST_WAIT_IPACC_MDCX_ACK)
-			| S(LCHAN_ST_WAIT_SAPIS_RELEASED)
-			| S(LCHAN_ST_WAIT_RF_RELEASE_ACK)
-			,
-	},
-	[LCHAN_ST_WAIT_IPACC_MDCX_ACK] = {
-		.name = "WAIT_IPACC_MDCX_ACK",
-		.onenter = lchan_fsm_wait_ipacc_mdcx_ack_onenter,
-		.action = lchan_fsm_wait_ipacc_mdcx_ack,
-		.in_event_mask = 0
-			| S(LCHAN_EV_IPACC_MDCX_ACK)
-			| S(LCHAN_EV_IPACC_MDCX_NACK)
-			| S(LCHAN_EV_RLL_ESTABLISH_IND) /* ignored */
-			,
-		.out_state_mask = 0
-			| S(LCHAN_ST_UNUSED)
-			| S(LCHAN_ST_WAIT_MGW_ENDPOINT_CONFIGURED)
-			| S(LCHAN_ST_WAIT_SAPIS_RELEASED)
-			| S(LCHAN_ST_WAIT_RF_RELEASE_ACK)
-			,
-	},
-	[LCHAN_ST_WAIT_MGW_ENDPOINT_CONFIGURED] = {
-		.name = "WAIT_MGW_ENDPOINT_CONFIGURED",
-		.onenter = lchan_fsm_wait_mgw_endpoint_configured_onenter,
-		.action = lchan_fsm_wait_mgw_endpoint_configured,
-		.in_event_mask = 0
-			| S(LCHAN_EV_MGW_ENDPOINT_CONFIGURED)
-			| S(LCHAN_EV_RLL_ESTABLISH_IND) /* ignored */
+			| S(LCHAN_EV_RTP_READY)
+			| S(LCHAN_EV_RTP_ERROR)
+			| S(LCHAN_EV_RTP_RELEASED)
 			,
 		.out_state_mask = 0
 			| S(LCHAN_ST_UNUSED)
 			| S(LCHAN_ST_ESTABLISHED)
-			| S(LCHAN_ST_WAIT_SAPIS_RELEASED)
 			| S(LCHAN_ST_WAIT_RF_RELEASE_ACK)
+			| S(LCHAN_ST_WAIT_RLL_RTP_RELEASED)
 			,
 	},
 	[LCHAN_ST_ESTABLISHED] = {
@@ -1329,21 +1071,25 @@
 			| S(LCHAN_EV_RLL_REL_IND)
 			| S(LCHAN_EV_RLL_REL_CONF)
 			| S(LCHAN_EV_RLL_ESTABLISH_IND) /* ignored */
+			| S(LCHAN_EV_RTP_ERROR)
+			| S(LCHAN_EV_RTP_RELEASED)
 			,
 		.out_state_mask = 0
 			| S(LCHAN_ST_UNUSED)
-			| S(LCHAN_ST_WAIT_SAPIS_RELEASED)
+			| S(LCHAN_ST_WAIT_RLL_RTP_RELEASED)
 			| S(LCHAN_ST_WAIT_BEFORE_RF_RELEASE)
 			| S(LCHAN_ST_WAIT_RF_RELEASE_ACK)
 			,
 	},
-	[LCHAN_ST_WAIT_SAPIS_RELEASED] = {
-		.name = "WAIT_SAPIS_RELEASED",
-		.onenter = lchan_fsm_wait_sapis_released_onenter,
-		.action = lchan_fsm_wait_sapis_released,
+	[LCHAN_ST_WAIT_RLL_RTP_RELEASED] = {
+		.name = "WAIT_RLL_RTP_RELEASED",
+		.onenter = lchan_fsm_wait_rll_rtp_released_onenter,
+		.action = lchan_fsm_wait_rll_rtp_released,
 		.in_event_mask = 0
 			| S(LCHAN_EV_RLL_REL_IND)
 			| S(LCHAN_EV_RLL_REL_CONF)
+			| S(LCHAN_EV_RTP_ERROR)
+			| S(LCHAN_EV_RTP_RELEASED)
 			,
 		.out_state_mask = 0
 			| S(LCHAN_ST_UNUSED)
@@ -1388,6 +1134,8 @@
 			| S(LCHAN_EV_RSL_CHAN_ACTIV_ACK)
 			| S(LCHAN_EV_RSL_CHAN_ACTIV_NACK)
 			| S(LCHAN_EV_RSL_RF_CHAN_REL_ACK)
+			| S(LCHAN_EV_RTP_ERROR)
+			| S(LCHAN_EV_RTP_RELEASED)
 			,
 		.out_state_mask = 0
 			| S(LCHAN_ST_UNUSED)
@@ -1403,13 +1151,9 @@
 	OSMO_VALUE_STRING(LCHAN_EV_RSL_CHAN_ACTIV_ACK),
 	OSMO_VALUE_STRING(LCHAN_EV_RSL_CHAN_ACTIV_NACK),
 	OSMO_VALUE_STRING(LCHAN_EV_RLL_ESTABLISH_IND),
-	OSMO_VALUE_STRING(LCHAN_EV_MGW_ENDPOINT_AVAILABLE),
-	OSMO_VALUE_STRING(LCHAN_EV_MGW_ENDPOINT_CONFIGURED),
-	OSMO_VALUE_STRING(LCHAN_EV_MGW_ENDPOINT_ERROR),
-	OSMO_VALUE_STRING(LCHAN_EV_IPACC_CRCX_ACK),
-	OSMO_VALUE_STRING(LCHAN_EV_IPACC_CRCX_NACK),
-	OSMO_VALUE_STRING(LCHAN_EV_IPACC_MDCX_ACK),
-	OSMO_VALUE_STRING(LCHAN_EV_IPACC_MDCX_NACK),
+	OSMO_VALUE_STRING(LCHAN_EV_RTP_READY),
+	OSMO_VALUE_STRING(LCHAN_EV_RTP_ERROR),
+	OSMO_VALUE_STRING(LCHAN_EV_RTP_RELEASED),
 	OSMO_VALUE_STRING(LCHAN_EV_RLL_REL_IND),
 	OSMO_VALUE_STRING(LCHAN_EV_RLL_REL_CONF),
 	OSMO_VALUE_STRING(LCHAN_EV_RSL_RF_CHAN_REL_ACK),
@@ -1421,27 +1165,12 @@
 
 void lchan_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
 {
-	struct gsm_lchan *lchan = lchan_fi_lchan(fi);
-
 	switch (event) {
 
 	case LCHAN_EV_TS_ERROR:
 		lchan_fail_to(LCHAN_ST_UNUSED, "LCHAN_EV_TS_ERROR");
 		return;
 
-	case LCHAN_EV_MGW_ENDPOINT_ERROR:
-		/* This event during activation means that it was not possible to establish an endpoint.
-		 * After activation was successful, it could also come in at any point to signal that the
-		 * MGW side has become unavailable, which should lead to graceful release. */
-		if (fi->state == LCHAN_ST_WAIT_MGW_ENDPOINT_AVAILABLE) {
-			/* This state is actually waiting for availability. Fail it immediately. */
-			lchan_fail("LCHAN_EV_MGW_ENDPOINT_ERROR");
-			return;
-		}
-		LOG_LCHAN(lchan, LOGL_ERROR, "Releasing due to MGW endpoint error\n");
-		lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
-		return;
-
 	default:
 		return;
 	}
@@ -1477,28 +1206,26 @@
 	lchan->rsl_error_cause = cause_rr;
 	lchan->deact_sacch = sacch_deact;
 
-	/* This would also happen later, but better to do this a sooner. */
-	if (lchan->mgw_endpoint_ci_bts) {
-		mgw_endpoint_ci_dlcx(lchan->mgw_endpoint_ci_bts);
-		lchan->mgw_endpoint_ci_bts = NULL;
-	}
-
 	/* States waiting for events will notice the desire to release when done waiting, so it is enough
 	 * to mark for release. */
 	lchan->release_requested = true;
 
-	/* But when in error, shortcut that. */
+	/* If we took the RTP over from another lchan, put it back. */
+	if (lchan->fi_rtp && lchan->release_in_error)
+		osmo_fsm_inst_dispatch(lchan->fi_rtp, LCHAN_RTP_EV_ROLLBACK, 0);
+
+	/* But when in error, don't wait for the next state to pick up release_requested. */
 	if (lchan->release_in_error) {
 		switch (lchan->fi->state) {
 		default:
-			/* Normally we deact SACCH in lchan_fsm_wait_sapis_released_onenter(). When
+			/* Normally we deact SACCH in lchan_fsm_wait_rll_rtp_released_onenter(). When
 			 * skipping that, but asked to SACCH deact, do it now. */
 			if (lchan->deact_sacch)
 				rsl_deact_sacch(lchan);
 			lchan_fsm_state_chg(LCHAN_ST_WAIT_RF_RELEASE_ACK);
 			return;
 		case LCHAN_ST_WAIT_TS_READY:
-			lchan_fsm_state_chg(LCHAN_ST_UNUSED);
+			lchan_fsm_state_chg(LCHAN_ST_WAIT_RLL_RTP_RELEASED);
 			return;
 		case LCHAN_ST_WAIT_RF_RELEASE_ACK:
 		case LCHAN_ST_BORKEN:
@@ -1509,7 +1236,7 @@
 	/* The only non-broken state that would stay stuck without noticing the release_requested flag
 	 * is: */
 	if (fi->state == LCHAN_ST_ESTABLISHED)
-		lchan_fsm_state_chg(LCHAN_ST_WAIT_SAPIS_RELEASED);
+		lchan_fsm_state_chg(LCHAN_ST_WAIT_RLL_RTP_RELEASED);
 }
 
 void lchan_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
@@ -1525,14 +1252,6 @@
 	lchan->fi = NULL;
 }
 
-/* The mgw_endpoint was invalidated, just and simply forget the pointer without cleanup. */
-void lchan_forget_mgw_endpoint(struct gsm_lchan *lchan)
-{
-	if (!lchan)
-		return;
-	lchan->mgw_endpoint_ci_bts = NULL;
-}
-
 /* The conn is deallocating, just forget all about it */
 void lchan_forget_conn(struct gsm_lchan *lchan)
 {
@@ -1551,7 +1270,6 @@
 	.allstate_action = lchan_fsm_allstate_action,
 	.allstate_event_mask = 0
 		| S(LCHAN_EV_TS_ERROR)
-		| S(LCHAN_EV_MGW_ENDPOINT_ERROR)
 		,
 	.timer_cb = lchan_fsm_timer_cb,
 	.cleanup = lchan_fsm_cleanup,
diff --git a/src/osmo-bsc/lchan_rtp_fsm.c b/src/osmo-bsc/lchan_rtp_fsm.c
new file mode 100644
index 0000000..3530b8a
--- /dev/null
+++ b/src/osmo-bsc/lchan_rtp_fsm.c
@@ -0,0 +1,743 @@
+/* osmo-bsc API to switch the RTP stream for an lchan.
+ *
+ * (C) 2018 by sysmocom - s.f.m.c. GmbH <info at sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels at hofmeyr.de>
+ *
+ * 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 <osmocom/core/fsm.h>
+
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/gsm_timers.h>
+#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/lchan_rtp_fsm.h>
+#include <osmocom/bsc/mgw_endpoint_fsm.h>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
+#include <osmocom/bsc/abis_rsl.h>
+
+static struct osmo_fsm lchan_rtp_fsm;
+
+struct gsm_lchan *lchan_rtp_fi_lchan(struct osmo_fsm_inst *fi)
+{
+	OSMO_ASSERT(fi);
+	OSMO_ASSERT(fi->fsm == &lchan_rtp_fsm);
+	OSMO_ASSERT(fi->priv);
+	return fi->priv;
+}
+
+struct state_timeout lchan_rtp_fsm_timeouts[32] = {
+	[LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_AVAILABLE] = { .T=23004 },
+	[LCHAN_RTP_ST_WAIT_IPACC_CRCX_ACK]	= { .T=23005 },
+	[LCHAN_RTP_ST_WAIT_IPACC_MDCX_ACK]	= { .T=23006 },
+	[LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_CONFIGURED] = { .T=23004 },
+};
+
+/* Transition to a state, using the T timer defined in lchan_rtp_fsm_timeouts.
+ * The actual timeout value is in turn obtained from network->T_defs.
+ * Assumes local variable fi exists. */
+#define lchan_rtp_fsm_state_chg(state) \
+	fsm_inst_state_chg_T(fi, state, \
+			     lchan_rtp_fsm_timeouts, \
+			     ((struct gsm_lchan*)(fi->priv))->ts->trx->bts->network->T_defs, \
+			     5)
+
+/* Set a failure message, trigger the common actions to take on failure, transition to a state to
+ * continue with (using state timeouts from lchan_rtp_fsm_timeouts[]). Assumes local variable fi exists. */
+#define lchan_rtp_fail(fmt, args...) do { \
+		struct gsm_lchan *_lchan = fi->priv; \
+		uint32_t state_was = fi->state; \
+		lchan_set_last_error(_lchan, "lchan-rtp failure in state %s: " fmt, \
+				     osmo_fsm_state_name(fi->fsm, state_was), ## args); \
+		osmo_fsm_inst_dispatch(_lchan->fi, LCHAN_EV_RTP_ERROR, 0); \
+	} while(0)
+
+/* Called from lchan_fsm_init(), does not need to be visible in lchan_rtp_fsm.h */
+void lchan_rtp_fsm_init()
+{
+	OSMO_ASSERT(osmo_fsm_register(&lchan_rtp_fsm) == 0);
+}
+
+static void lchan_rtp_fsm_update_id(struct gsm_lchan *lchan)
+{
+	OSMO_ASSERT(lchan->fi);
+	OSMO_ASSERT(lchan->fi_rtp);
+	osmo_fsm_inst_update_id_f(lchan->fi_rtp, lchan->fi->id);
+}
+
+bool lchan_rtp_established(struct gsm_lchan *lchan)
+{
+	if (!lchan->fi_rtp)
+		return false;
+	switch (lchan->fi_rtp->state) {
+	case LCHAN_RTP_ST_READY:
+	case LCHAN_RTP_ST_ESTABLISHED:
+	case LCHAN_RTP_ST_ROLLBACK:
+		return true;
+	default:
+		return false;
+	}
+}
+
+void lchan_rtp_fsm_start(struct gsm_lchan *lchan)
+{
+	struct osmo_fsm_inst *fi;
+
+	OSMO_ASSERT(lchan->ts);
+	OSMO_ASSERT(lchan->ts->fi);
+	OSMO_ASSERT(lchan->fi);
+	OSMO_ASSERT(!lchan->fi_rtp);
+
+	fi = osmo_fsm_inst_alloc_child(&lchan_rtp_fsm, lchan->fi, LCHAN_EV_RTP_RELEASED);
+	OSMO_ASSERT(fi);
+	fi->priv = lchan;
+	lchan->fi_rtp = fi;
+	lchan_rtp_fsm_update_id(lchan);
+
+	/* Use old lchan only if there is an MGW endpoint present. Otherwise, on ROLLBACK, we might put
+	 * an endpoint "back" to an lchan that never had one to begin with. */
+	if (lchan->activate.re_use_mgw_endpoint_from_lchan
+	    && !lchan->activate.re_use_mgw_endpoint_from_lchan->mgw_endpoint_ci_bts)
+		lchan->activate.re_use_mgw_endpoint_from_lchan = NULL;
+
+	lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_AVAILABLE);
+}
+
+/* While activating an lchan, for example for Handover, we may want to re-use another lchan's MGW
+ * endpoint CI. If Handover fails half way, the old lchan must keep its MGW endpoint CI, and we must not
+ * clean it up. Hence keep another lchan's mgw_endpoint_ci_bts out of lchan until all is done. */
+struct mgwep_ci *lchan_use_mgw_endpoint_ci_bts(struct gsm_lchan *lchan)
+{
+	if (lchan->mgw_endpoint_ci_bts)
+		return lchan->mgw_endpoint_ci_bts;
+	if (lchan_state_is(lchan, LCHAN_ST_ESTABLISHED))
+		return NULL;
+	if (lchan->activate.re_use_mgw_endpoint_from_lchan)
+		return lchan->activate.re_use_mgw_endpoint_from_lchan->mgw_endpoint_ci_bts;
+	return NULL;
+}
+
+static void lchan_rtp_fsm_wait_mgw_endpoint_available_onenter(struct osmo_fsm_inst *fi,
+							      uint32_t prev_state)
+{
+	struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+	struct mgw_endpoint *mgwep;
+	struct mgwep_ci *use_mgwep_ci = lchan_use_mgw_endpoint_ci_bts(lchan);
+	struct mgcp_conn_peer crcx_info = {};
+
+	if (use_mgwep_ci) {
+		LOG_LCHAN_RTP(lchan, LOGL_DEBUG, "MGW endpoint already available: %s",
+			      mgwep_ci_name(use_mgwep_ci));
+		lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_LCHAN_READY);
+		return;
+	}
+
+	mgwep = gscon_ensure_mgw_endpoint(lchan->conn, lchan->activate.msc_assigned_cic);
+	if (!mgwep) {
+		lchan_rtp_fail("Internal error: cannot obtain MGW endpoint handle for conn");
+		return;
+	}
+
+	lchan->mgw_endpoint_ci_bts = mgw_endpoint_ci_add(mgwep, "to-BTS");
+
+	if (lchan->conn)
+		crcx_info.call_id = lchan->conn->sccp.conn_id;
+	crcx_info.ptime = 20;
+	mgcp_pick_codec(&crcx_info, lchan);
+
+	mgw_endpoint_ci_request(lchan->mgw_endpoint_ci_bts, MGCP_VERB_CRCX, &crcx_info,
+				fi, LCHAN_RTP_EV_MGW_ENDPOINT_AVAILABLE, LCHAN_RTP_EV_MGW_ENDPOINT_ERROR,
+				0);
+}
+
+static void lchan_rtp_fsm_wait_mgw_endpoint_available(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+	switch (event) {
+
+	case LCHAN_RTP_EV_MGW_ENDPOINT_AVAILABLE:
+		LOG_LCHAN_RTP(lchan, LOGL_DEBUG, "MGW endpoint: %s",
+			      mgwep_ci_name(lchan_use_mgw_endpoint_ci_bts(lchan)));
+		lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_LCHAN_READY);
+		return;
+
+	case LCHAN_RTP_EV_LCHAN_READY:
+		/* will notice lchan->activate.activ_ack == true in
+		 * lchan_rtp_fsm_wait_lchan_ready_onenter() */
+		return;
+
+	case LCHAN_RTP_EV_MGW_ENDPOINT_ERROR:
+		lchan_rtp_fail("Failure to create MGW endpoint");
+		return;
+
+	case LCHAN_RTP_EV_ROLLBACK:
+	case LCHAN_RTP_EV_RELEASE:
+		osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, 0);
+		return;
+
+	default:
+		OSMO_ASSERT(false);
+	}
+}
+
+static void lchan_rtp_fsm_post_lchan_ready(struct osmo_fsm_inst *fi);
+
+static void lchan_rtp_fsm_wait_lchan_ready_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+
+	if (lchan->activate.activ_ack) {
+		LOG_LCHAN_RTP(lchan, LOGL_DEBUG, "Activ Ack received earlier, no need to wait");
+		lchan_rtp_fsm_post_lchan_ready(fi);
+	}
+}
+
+static void lchan_rtp_fsm_wait_lchan_ready(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	switch (event) {
+
+	case LCHAN_RTP_EV_LCHAN_READY:
+		lchan_rtp_fsm_post_lchan_ready(fi);
+		return;
+
+	case LCHAN_RTP_EV_ROLLBACK:
+	case LCHAN_RTP_EV_RELEASE:
+		osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, 0);
+		return;
+
+	default:
+		OSMO_ASSERT(false);
+	}
+}
+
+static void lchan_rtp_fsm_switch_rtp(struct osmo_fsm_inst *fi)
+{
+	struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+
+	if (lchan->activate.wait_before_switching_rtp) {
+		LOG_LCHAN_RTP(lchan, LOGL_DEBUG,
+			      "Waiting for an event by caller before switching RTP\n");
+		lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_READY_TO_SWITCH_RTP);
+	} else
+		lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_CONFIGURED);
+}
+
+static void lchan_rtp_fsm_post_lchan_ready(struct osmo_fsm_inst *fi)
+{
+	struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+
+	if (is_ipaccess_bts(lchan->ts->trx->bts))
+		lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_IPACC_CRCX_ACK);
+	else
+		lchan_rtp_fsm_switch_rtp(fi);
+}
+
+static void lchan_rtp_fsm_wait_ipacc_crcx_ack_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	int rc;
+	int val;
+	struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+
+	if (lchan->release_requested) {
+		lchan_rtp_fail("Release requested while activating");
+		return;
+	}
+
+	val = ipacc_speech_mode(lchan->tch_mode, lchan->type);
+	if (val < 0) {
+		lchan_rtp_fail("Cannot determine Abis/IP speech mode for tch_mode=%s type=%s\n",
+			   get_value_string(gsm48_chan_mode_names, lchan->tch_mode),
+			   gsm_lchant_name(lchan->type));
+		return;
+	}
+	lchan->abis_ip.speech_mode = val;
+
+	val = ipacc_payload_type(lchan->tch_mode, lchan->type);
+	if (val < 0) {
+		lchan_rtp_fail("Cannot determine Abis/IP payload type for tch_mode=%s type=%s\n",
+			   get_value_string(gsm48_chan_mode_names, lchan->tch_mode),
+			   gsm_lchant_name(lchan->type));
+		return;
+	}
+	lchan->abis_ip.rtp_payload = val;
+
+	/* recv-only */
+	ipacc_speech_mode_set_direction(&lchan->abis_ip.speech_mode, false);
+
+	rc = rsl_tx_ipacc_crcx(lchan);
+	if (rc)
+		lchan_rtp_fail("Failure to transmit IPACC CRCX to BTS (rc=%d, %s)",
+			   rc, strerror(-rc));
+}
+
+static void lchan_rtp_fsm_wait_ipacc_crcx_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+	switch (event) {
+
+	case LCHAN_RTP_EV_IPACC_CRCX_ACK:
+		/* the CRCX ACK parsing has already noted the RTP port information at
+		 * lchan->abis_ip.bound_*, see ipac_parse_rtp(). We'll use that in
+		 * lchan_rtp_fsm_wait_mgw_endpoint_configured_onenter(). */
+		lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_IPACC_MDCX_ACK);
+		return;
+
+	case LCHAN_RTP_EV_IPACC_CRCX_NACK:
+		lchan_rtp_fail("Received NACK on IPACC CRCX");
+		return;
+
+	case LCHAN_RTP_EV_READY_TO_SWITCH_RTP:
+		lchan->activate.wait_before_switching_rtp = false;
+		return;
+
+	case LCHAN_RTP_EV_RELEASE:
+	case LCHAN_RTP_EV_ROLLBACK:
+		osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, 0);
+		return;
+
+	default:
+		OSMO_ASSERT(false);
+	}
+}
+
+static void lchan_rtp_fsm_wait_ipacc_mdcx_ack_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	int rc;
+	struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+	const struct mgcp_conn_peer *mgw_rtp;
+
+	if (lchan->release_requested) {
+		lchan_rtp_fail("Release requested while activating");
+		return;
+	}
+
+	mgw_rtp = mgwep_ci_get_rtp_info(lchan_use_mgw_endpoint_ci_bts(lchan));
+
+	if (!mgw_rtp) {
+		lchan_rtp_fail("Cannot send IPACC MDCX to BTS:"
+			   " there is no RTP IP+port set that the BTS should send RTP to.");
+		return;
+	}
+
+	/* Other RTP settings were already setup in lchan_rtp_fsm_wait_ipacc_crcx_ack_onenter() */
+	lchan->abis_ip.connect_ip = ntohl(inet_addr(mgw_rtp->addr));
+	lchan->abis_ip.connect_port = mgw_rtp->port;
+
+	/* send-recv */
+	ipacc_speech_mode_set_direction(&lchan->abis_ip.speech_mode, true);
+
+	rc = rsl_tx_ipacc_mdcx(lchan);
+	if (rc)
+		lchan_rtp_fail("Failure to transmit IPACC MDCX to BTS (rc=%d, %s)",
+			   rc, strerror(-rc));
+
+}
+
+static void lchan_rtp_fsm_wait_ipacc_mdcx_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+	switch (event) {
+
+	case LCHAN_RTP_EV_IPACC_MDCX_ACK:
+		lchan_rtp_fsm_switch_rtp(fi);
+		return;
+
+	case LCHAN_RTP_EV_IPACC_MDCX_NACK:
+		lchan_rtp_fail("Received NACK on IPACC MDCX");
+		return;
+
+	case LCHAN_RTP_EV_READY_TO_SWITCH_RTP:
+		lchan->activate.wait_before_switching_rtp = false;
+		return;
+
+	case LCHAN_RTP_EV_RELEASE:
+	case LCHAN_RTP_EV_ROLLBACK:
+		osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, 0);
+		return;
+
+	default:
+		OSMO_ASSERT(false);
+	}
+}
+
+static void lchan_rtp_fsm_wait_ready_to_switch_rtp(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	switch (event) {
+
+	case LCHAN_RTP_EV_READY_TO_SWITCH_RTP:
+		lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_CONFIGURED);
+		return;
+
+	case LCHAN_RTP_EV_RELEASE:
+	case LCHAN_RTP_EV_ROLLBACK:
+		osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, 0);
+		return;
+
+	default:
+		OSMO_ASSERT(false);
+	}
+}
+
+static void connect_mgw_endpoint_to_lchan(struct osmo_fsm_inst *fi,
+					  struct mgwep_ci *ci,
+					  struct gsm_lchan *to_lchan)
+{
+	int rc;
+	struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+	struct mgcp_conn_peer mdcx_info;
+	struct in_addr addr;
+	const char *addr_str;
+
+	mdcx_info = (struct mgcp_conn_peer){
+		.port = to_lchan->abis_ip.bound_port,
+		.ptime = 20,
+	};
+	mgcp_pick_codec(&mdcx_info, to_lchan);
+
+	addr.s_addr = ntohl(to_lchan->abis_ip.bound_ip);
+	addr_str = inet_ntoa(addr);
+	rc = osmo_strlcpy(mdcx_info.addr, addr_str, sizeof(mdcx_info.addr));
+	if (rc <= 0 || rc >= sizeof(mdcx_info.addr)) {
+		lchan_rtp_fail("Cannot compose BTS side RTP IP address to send to MGW: '%s'",
+			   addr_str);
+		return;
+	}
+
+	if (!ci) {
+		lchan_rtp_fail("No MGW endpoint ci configured");
+		return;
+	}
+
+	LOG_LCHAN_RTP(lchan, LOGL_DEBUG, "Sending BTS side RTP port info %s:%u to MGW %s",
+		      mdcx_info.addr, mdcx_info.port, mgwep_ci_name(ci));
+	mgw_endpoint_ci_request(ci, MGCP_VERB_MDCX, &mdcx_info,
+				fi, LCHAN_RTP_EV_MGW_ENDPOINT_CONFIGURED,
+				LCHAN_RTP_EV_MGW_ENDPOINT_ERROR, 0);
+}
+
+static void lchan_rtp_fsm_wait_mgw_endpoint_configured_onenter(struct osmo_fsm_inst *fi,
+							       uint32_t prev_state)
+{
+	struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+	struct gsm_lchan *old_lchan = lchan->activate.re_use_mgw_endpoint_from_lchan;
+
+	if (lchan->release_requested) {
+		lchan_rtp_fail("Release requested while activating");
+		return;
+	}
+
+	/* At this point, we are taking over an old lchan's MGW endpoint (if any). */
+	if (!lchan->mgw_endpoint_ci_bts && old_lchan) {
+		/* The old lchan shall forget the enpoint now. We might put it back upon ROLLBACK */
+		lchan->mgw_endpoint_ci_bts = old_lchan->mgw_endpoint_ci_bts;
+		old_lchan->mgw_endpoint_ci_bts = NULL;
+	}
+
+	if (!lchan->mgw_endpoint_ci_bts) {
+		lchan_rtp_fail("No MGW endpoint ci configured");
+		return;
+	}
+
+	connect_mgw_endpoint_to_lchan(fi, lchan->mgw_endpoint_ci_bts, lchan);
+}
+
+static void lchan_rtp_fsm_wait_mgw_endpoint_configured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	switch (event) {
+
+	case LCHAN_RTP_EV_MGW_ENDPOINT_CONFIGURED:
+		lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_READY);
+		return;
+
+	case LCHAN_RTP_EV_MGW_ENDPOINT_ERROR:
+		lchan_rtp_fail("Error while redirecting the MGW to the lchan's RTP port");
+		return;
+
+	default:
+		OSMO_ASSERT(false);
+	}
+}
+
+static void lchan_rtp_fsm_ready_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+	osmo_fsm_inst_dispatch(lchan->fi, LCHAN_EV_RTP_READY, 0);
+}
+
+static void lchan_rtp_fsm_ready(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	switch (event) {
+
+	case LCHAN_RTP_EV_ESTABLISHED:
+		lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_ESTABLISHED);
+		return;
+
+	case LCHAN_RTP_EV_RELEASE:
+		osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, 0);
+		return;
+
+	case LCHAN_RTP_EV_ROLLBACK:
+		lchan_rtp_fsm_state_chg(LCHAN_RTP_ST_ROLLBACK);
+		return;
+
+	default:
+		OSMO_ASSERT(false);
+	}
+}
+
+static void lchan_rtp_fsm_rollback_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+	struct gsm_lchan *old_lchan = lchan->activate.re_use_mgw_endpoint_from_lchan;
+
+	if (!lchan->mgw_endpoint_ci_bts || !old_lchan) {
+		osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, 0);
+		return;
+	}
+	connect_mgw_endpoint_to_lchan(fi, lchan->mgw_endpoint_ci_bts, old_lchan);
+}
+
+static void lchan_rtp_fsm_rollback(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+	struct gsm_lchan *old_lchan = lchan->activate.re_use_mgw_endpoint_from_lchan;
+
+	switch (event) {
+
+	case LCHAN_RTP_EV_MGW_ENDPOINT_CONFIGURED:
+		old_lchan->mgw_endpoint_ci_bts = lchan->mgw_endpoint_ci_bts;
+		lchan->mgw_endpoint_ci_bts = NULL;
+		osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, 0);
+		return;
+
+	case LCHAN_RTP_EV_MGW_ENDPOINT_ERROR:
+		LOG_LCHAN_RTP(lchan, LOGL_ERROR,
+			      "Error while connecting the MGW back to the old lchan's RTP port:"
+			      " %s %s\n",
+			      mgwep_ci_name(lchan->mgw_endpoint_ci_bts),
+			      gsm_lchan_name(old_lchan));
+		osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, 0);
+		return;
+
+	default:
+		OSMO_ASSERT(false);
+	}
+}
+
+static void lchan_rtp_fsm_established_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+
+	/* Make sure that we will not hand back the MGW endpoint to any old lchan from here on. */
+	lchan->activate.re_use_mgw_endpoint_from_lchan = NULL;
+}
+
+static void lchan_rtp_fsm_established(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	switch (event) {
+
+	case LCHAN_RTP_EV_RELEASE:
+	case LCHAN_RTP_EV_ROLLBACK:
+		osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, 0);
+		return;
+
+	default:
+		OSMO_ASSERT(false);
+	}
+}
+
+#define S(x)	(1 << (x))
+
+static const struct osmo_fsm_state lchan_rtp_fsm_states[] = {
+	[LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_AVAILABLE] = {
+		.name = "WAIT_MGW_ENDPOINT_AVAILABLE",
+		.onenter = lchan_rtp_fsm_wait_mgw_endpoint_available_onenter,
+		.action = lchan_rtp_fsm_wait_mgw_endpoint_available,
+		.in_event_mask = 0
+			| S(LCHAN_RTP_EV_MGW_ENDPOINT_AVAILABLE)
+			| S(LCHAN_RTP_EV_MGW_ENDPOINT_ERROR)
+			| S(LCHAN_RTP_EV_LCHAN_READY)
+			| S(LCHAN_RTP_EV_RELEASE)
+			| S(LCHAN_RTP_EV_ROLLBACK)
+			,
+		.out_state_mask = 0
+			| S(LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_AVAILABLE) /* for init */
+			| S(LCHAN_RTP_ST_WAIT_LCHAN_READY)
+			,
+	},
+	[LCHAN_RTP_ST_WAIT_LCHAN_READY] = {
+		.name = "WAIT_LCHAN_READY",
+		.onenter = lchan_rtp_fsm_wait_lchan_ready_onenter,
+		.action = lchan_rtp_fsm_wait_lchan_ready,
+		.in_event_mask = 0
+			| S(LCHAN_RTP_EV_LCHAN_READY)
+			| S(LCHAN_RTP_EV_RELEASE)
+			| S(LCHAN_RTP_EV_ROLLBACK)
+			,
+		.out_state_mask = 0
+			| S(LCHAN_RTP_ST_WAIT_IPACC_CRCX_ACK)
+			| S(LCHAN_RTP_ST_WAIT_READY_TO_SWITCH_RTP)
+			| S(LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_CONFIGURED)
+			,
+	},
+	[LCHAN_RTP_ST_WAIT_IPACC_CRCX_ACK] = {
+		.name = "WAIT_IPACC_CRCX_ACK",
+		.onenter = lchan_rtp_fsm_wait_ipacc_crcx_ack_onenter,
+		.action = lchan_rtp_fsm_wait_ipacc_crcx_ack,
+		.in_event_mask = 0
+			| S(LCHAN_RTP_EV_READY_TO_SWITCH_RTP)
+			| S(LCHAN_RTP_EV_IPACC_CRCX_ACK)
+			| S(LCHAN_RTP_EV_IPACC_CRCX_NACK)
+			| S(LCHAN_RTP_EV_RELEASE)
+			| S(LCHAN_RTP_EV_ROLLBACK)
+			,
+		.out_state_mask = 0
+			| S(LCHAN_RTP_ST_WAIT_IPACC_MDCX_ACK)
+			,
+	},
+	[LCHAN_RTP_ST_WAIT_IPACC_MDCX_ACK] = {
+		.name = "WAIT_IPACC_MDCX_ACK",
+		.onenter = lchan_rtp_fsm_wait_ipacc_mdcx_ack_onenter,
+		.action = lchan_rtp_fsm_wait_ipacc_mdcx_ack,
+		.in_event_mask = 0
+			| S(LCHAN_RTP_EV_READY_TO_SWITCH_RTP)
+			| S(LCHAN_RTP_EV_IPACC_MDCX_ACK)
+			| S(LCHAN_RTP_EV_IPACC_MDCX_NACK)
+			| S(LCHAN_RTP_EV_RELEASE)
+			| S(LCHAN_RTP_EV_ROLLBACK)
+			,
+		.out_state_mask = 0
+			| S(LCHAN_RTP_ST_WAIT_READY_TO_SWITCH_RTP)
+			| S(LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_CONFIGURED)
+			,
+	},
+	[LCHAN_RTP_ST_WAIT_READY_TO_SWITCH_RTP] = {
+		.name = "WAIT_READY_TO_SWITCH_RTP",
+		.action = lchan_rtp_fsm_wait_ready_to_switch_rtp,
+		.in_event_mask = 0
+			| S(LCHAN_RTP_EV_READY_TO_SWITCH_RTP)
+			| S(LCHAN_RTP_EV_RELEASE)
+			| S(LCHAN_RTP_EV_ROLLBACK)
+			,
+		.out_state_mask = 0
+			| S(LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_CONFIGURED)
+			,
+	},
+	[LCHAN_RTP_ST_WAIT_MGW_ENDPOINT_CONFIGURED] = {
+		.name = "WAIT_MGW_ENDPOINT_CONFIGURED",
+		.onenter = lchan_rtp_fsm_wait_mgw_endpoint_configured_onenter,
+		.action = lchan_rtp_fsm_wait_mgw_endpoint_configured,
+		.in_event_mask = 0
+			| S(LCHAN_RTP_EV_MGW_ENDPOINT_CONFIGURED)
+			| S(LCHAN_RTP_EV_MGW_ENDPOINT_ERROR)
+			| S(LCHAN_RTP_EV_RELEASE)
+			| S(LCHAN_RTP_EV_ROLLBACK)
+			,
+		.out_state_mask = 0
+			| S(LCHAN_RTP_ST_READY)
+			,
+	},
+	[LCHAN_RTP_ST_READY] = {
+		.name = "READY",
+		.onenter = lchan_rtp_fsm_ready_onenter,
+		.action = lchan_rtp_fsm_ready,
+		.in_event_mask = 0
+			| S(LCHAN_RTP_EV_ESTABLISHED)
+			| S(LCHAN_RTP_EV_RELEASE)
+			| S(LCHAN_RTP_EV_ROLLBACK)
+			,
+		.out_state_mask = 0
+			| S(LCHAN_RTP_ST_ESTABLISHED)
+			| S(LCHAN_RTP_ST_ROLLBACK)
+			,
+	},
+	[LCHAN_RTP_ST_ESTABLISHED] = {
+		.name = "ESTABLISHED",
+		.onenter = lchan_rtp_fsm_established_onenter,
+		.action = lchan_rtp_fsm_established,
+		.in_event_mask = 0
+			| S(LCHAN_RTP_EV_RELEASE)
+			| S(LCHAN_RTP_EV_ROLLBACK)
+			,
+	},
+	[LCHAN_RTP_ST_ROLLBACK] = {
+		.name = "ROLLBACK",
+		.onenter = lchan_rtp_fsm_rollback_onenter,
+		.action = lchan_rtp_fsm_rollback,
+		.in_event_mask = 0
+			| S(LCHAN_RTP_EV_MGW_ENDPOINT_CONFIGURED)
+			| S(LCHAN_RTP_EV_MGW_ENDPOINT_ERROR)
+			| S(LCHAN_RTP_EV_RELEASE)
+			| S(LCHAN_RTP_EV_ROLLBACK)
+			,
+	},
+};
+
+static const struct value_string lchan_rtp_fsm_event_names[] = {
+	OSMO_VALUE_STRING(LCHAN_RTP_EV_LCHAN_READY),
+	OSMO_VALUE_STRING(LCHAN_RTP_EV_READY_TO_SWITCH_RTP),
+	OSMO_VALUE_STRING(LCHAN_RTP_EV_MGW_ENDPOINT_AVAILABLE),
+	OSMO_VALUE_STRING(LCHAN_RTP_EV_MGW_ENDPOINT_ERROR),
+	OSMO_VALUE_STRING(LCHAN_RTP_EV_IPACC_CRCX_ACK),
+	OSMO_VALUE_STRING(LCHAN_RTP_EV_IPACC_CRCX_NACK),
+	OSMO_VALUE_STRING(LCHAN_RTP_EV_IPACC_MDCX_ACK),
+	OSMO_VALUE_STRING(LCHAN_RTP_EV_IPACC_MDCX_NACK),
+	OSMO_VALUE_STRING(LCHAN_RTP_EV_READY_TO_SWITCH),
+	OSMO_VALUE_STRING(LCHAN_RTP_EV_MGW_ENDPOINT_CONFIGURED),
+	OSMO_VALUE_STRING(LCHAN_RTP_EV_ROLLBACK),
+	OSMO_VALUE_STRING(LCHAN_RTP_EV_ESTABLISHED),
+	OSMO_VALUE_STRING(LCHAN_RTP_EV_RELEASE),
+	{}
+};
+
+int lchan_rtp_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+	struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+	lchan->release_in_error = true;
+	lchan_rtp_fail("Timeout");
+	return 0;
+}
+
+void lchan_rtp_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+	struct gsm_lchan *lchan = lchan_rtp_fi_lchan(fi);
+	if (lchan->mgw_endpoint_ci_bts) {
+		mgw_endpoint_ci_dlcx(lchan->mgw_endpoint_ci_bts);
+		lchan->mgw_endpoint_ci_bts = NULL;
+	}
+	lchan->fi_rtp = NULL;
+	if (lchan->fi)
+		osmo_fsm_inst_dispatch(lchan->fi, LCHAN_EV_RTP_RELEASED, 0);
+}
+
+/* The mgw_endpoint was invalidated, just and simply forget the pointer without cleanup. */
+void lchan_forget_mgw_endpoint(struct gsm_lchan *lchan)
+{
+	if (!lchan)
+		return;
+	lchan->mgw_endpoint_ci_bts = NULL;
+}
+
+static struct osmo_fsm lchan_rtp_fsm = {
+	.name = "lchan_rtp",
+	.states = lchan_rtp_fsm_states,
+	.num_states = ARRAY_SIZE(lchan_rtp_fsm_states),
+	.log_subsys = DRSL,
+	.event_names = lchan_rtp_fsm_event_names,
+	.timer_cb = lchan_rtp_fsm_timer_cb,
+	.cleanup = lchan_rtp_fsm_cleanup,
+};
diff --git a/tests/handover/Makefile.am b/tests/handover/Makefile.am
index 709a87b..c28c417 100644
--- a/tests/handover/Makefile.am
+++ b/tests/handover/Makefile.am
@@ -63,6 +63,7 @@
 	$(top_builddir)/src/osmo-bsc/handover_fsm.o \
 	$(top_builddir)/src/osmo-bsc/handover_logic.o \
 	$(top_builddir)/src/osmo-bsc/lchan_fsm.o \
+	$(top_builddir)/src/osmo-bsc/lchan_rtp_fsm.o \
 	$(top_builddir)/src/osmo-bsc/lchan_select.o \
 	$(top_builddir)/src/osmo-bsc/meas_rep.o \
 	$(top_builddir)/src/osmo-bsc/mgw_endpoint_fsm.o \
diff --git a/tests/handover/handover_test.c b/tests/handover/handover_test.c
index 3a5748e..e76cc85 100644
--- a/tests/handover/handover_test.c
+++ b/tests/handover/handover_test.c
@@ -393,6 +393,7 @@
 	struct gsm48_ho_cpl *hc;
 
 	send_est_ind(lchan);
+	osmo_fsm_inst_dispatch(lchan->fi, LCHAN_EV_RTP_READY, 0);
 
 	rh = (struct abis_rsl_rll_hdr *) msgb_put(msg, sizeof(*rh));
 	rh->c.msg_discr = ABIS_RSL_MDISC_RLL;

-- 
To view, visit https://gerrit.osmocom.org/10103
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings

Gerrit-Project: osmo-bsc
Gerrit-Branch: master
Gerrit-MessageType: newchange
Gerrit-Change-Id: Id7a4407d9b63be05ce63f5f2768b7d7e3d5c86fb
Gerrit-Change-Number: 10103
Gerrit-PatchSet: 1
Gerrit-Owner: Neels Hofmeyr <nhofmeyr at sysmocom.de>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20180723/9269be40/attachment.htm>


More information about the gerrit-log mailing list