aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeels Hofmeyr <neels@hofmeyr.de>2018-07-12 21:23:26 +0200
committerNeels Hofmeyr <neels@hofmeyr.de>2018-07-28 12:18:23 +0200
commitac85b34476afc341af7e09e9eca368835cd9d68b (patch)
tree742a6682f64a132d400c440fb9835a2d044cd434
parentdbb38d532b10797aa65bfd29aa5a31ce76e6b5ea (diff)
lchan_fsm: split off lchan_rtp_fsm, establish RTP a bit earlier
-rw-r--r--include/osmocom/bsc/Makefile.am1
-rw-r--r--include/osmocom/bsc/gsm_data.h4
-rw-r--r--include/osmocom/bsc/lchan_fsm.h23
-rw-r--r--include/osmocom/bsc/lchan_rtp_fsm.h45
-rw-r--r--src/osmo-bsc/Makefile.am1
-rw-r--r--src/osmo-bsc/abis_rsl.c51
-rw-r--r--src/osmo-bsc/bsc_subscr_conn_fsm.c6
-rw-r--r--src/osmo-bsc/handover_fsm.c9
-rw-r--r--src/osmo-bsc/lchan_fsm.c620
-rw-r--r--src/osmo-bsc/lchan_rtp_fsm.c743
-rw-r--r--tests/handover/Makefile.am1
-rw-r--r--tests/handover/handover_test.c1
12 files changed, 1031 insertions, 474 deletions
diff --git a/include/osmocom/bsc/Makefile.am b/include/osmocom/bsc/Makefile.am
index f73417eb7..57837ec63 100644
--- a/include/osmocom/bsc/Makefile.am
+++ b/include/osmocom/bsc/Makefile.am
@@ -29,6 +29,7 @@ noinst_HEADERS = \
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 d8d10c95d..12bc5c354 100644
--- a/include/osmocom/bsc/gsm_data.h
+++ b/include/osmocom/bsc/gsm_data.h
@@ -499,13 +499,15 @@ struct gsm_lchan {
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 49701c1d0..35b88471e 100644
--- a/include/osmocom/bsc/lchan_fsm.h
+++ b/include/osmocom/bsc/lchan_fsm.h
@@ -16,13 +16,9 @@ enum lchan_fsm_state {
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 @@ enum lchan_fsm_event {
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 @@ struct lchan_activate_info {
* 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 @@ static inline bool lchan_state_is(struct gsm_lchan *lchan, uint32_t state)
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 000000000..fa0e74636
--- /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 d88c6a64e..e9add3641 100644
--- a/src/osmo-bsc/Makefile.am
+++ b/src/osmo-bsc/Makefile.am
@@ -67,6 +67,7 @@ osmo_bsc_SOURCES = \
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 8b944d96d..8b14b499c 100644
--- a/src/osmo-bsc/abis_rsl.c
+++ b/src/osmo-bsc/abis_rsl.c
@@ -50,6 +50,7 @@
#include <osmocom/bsc/timeslot_fsm.h>
#include <osmocom/bsc/lchan_select.h>
#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/lchan_rtp_fsm.h>
#include <osmocom/bsc/handover_fsm.h>
#define RSL_ALLOC_SIZE 1024
@@ -1878,6 +1879,11 @@ static int abis_rsl_rx_ipacc_crcx_ack(struct msgb *msg)
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 */
@@ -1892,17 +1898,37 @@ static int abis_rsl_rx_ipacc_crcx_ack(struct msgb *msg)
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 */
@@ -1910,11 +1936,26 @@ static int abis_rsl_rx_ipacc_mdcx_ack(struct msgb *msg)
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);
@@ -1957,8 +1998,7 @@ static int abis_rsl_rx_ipacc(struct msgb *msg)
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 */
@@ -1967,8 +2007,7 @@ static int abis_rsl_rx_ipacc(struct msgb *msg)
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 0ed98d838..bd4176125 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 @@ void gscon_change_primary_lchan(struct gsm_subscriber_connection *conn, struct g
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 0a6ee2471..ec2a53d2d 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_rr.h>
#include <osmocom/bsc/abis_rsl.h>
#include <osmocom/bsc/bsc_msc_data.h>
@@ -359,6 +360,7 @@ static void handover_start_intra_bsc(struct gsm_subscriber_connection *conn)
.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);
@@ -843,6 +845,9 @@ static void ho_fsm_wait_rr_ho_detect(struct osmo_fsm_inst *fi, uint32_t event, v
}
}
+ 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;
@@ -851,7 +856,9 @@ static void ho_fsm_wait_rr_ho_detect(struct osmo_fsm_inst *fi, uint32_t event, v
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 9f053ac4a..1ecec7c3a 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@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 @@ bool lchan_may_receive_data(struct gsm_lchan *lchan)
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 @@ bool lchan_may_receive_data(struct gsm_lchan *lchan)
}
}
-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_failure(struct gsm_lchan *lchan, enum lchan_act
}
}
-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 @@ static void lchan_on_activation_success(struct gsm_lchan *lchan)
}
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 @@ static void lchan_on_activation_success(struct gsm_lchan *lchan)
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 @@ static void lchan_on_activation_success(struct gsm_lchan *lchan)
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 @@ struct state_timeout lchan_fsm_timeouts[32] = {
#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 @@ uint32_t lchan_fsm_on_error[32] = {
[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 @@ static void lchan_fsm_update_id(struct gsm_lchan *lchan)
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 @@ static void lchan_reset(struct gsm_lchan *lchan)
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 @@ static void lchan_fsm_unused(struct osmo_fsm_inst *fi, uint32_t event, void *dat
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 @@ static void lchan_fsm_unused(struct osmo_fsm_inst *fi, uint32_t event, void *dat
}
}
-/* 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 @@ static void lchan_fsm_wait_ts_ready_onenter(struct osmo_fsm_inst *fi, uint32_t p
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 @@ static void lchan_fsm_wait_ts_ready(struct osmo_fsm_inst *fi, uint32_t event, vo
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 @@ static void lchan_fsm_wait_activ_ack_onenter(struct osmo_fsm_inst *fi, uint32_t
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 @@ static void lchan_fsm_wait_activ_ack(struct osmo_fsm_inst *fi, uint32_t event, v
}
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,263 +686,44 @@ static void lchan_fsm_wait_rll_establish_onenter(struct osmo_fsm_inst *fi, uint3
lchan_activate_mode_name(lchan->activate.activ_for));
break;
}
-}
-
-static void lchan_fsm_wait_rll_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
- 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. */
- return;
-
- default:
- OSMO_ASSERT(false);
- }
-}
-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);
+ lchan_fsm_state_chg(LCHAN_ST_WAIT_RLL_RTP_ESTABLISH);
}
-static void lchan_fsm_wait_ipacc_crcx_ack_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+static void lchan_fsm_wait_rll_rtp_establish_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);
- }
+ if (lchan->fi_rtp)
+ osmo_fsm_inst_dispatch(lchan->fi_rtp, LCHAN_RTP_EV_LCHAN_READY, 0);
}
-static void lchan_fsm_wait_ipacc_mdcx_ack_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+static void lchan_fsm_wait_rll_rtp_establish(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
- 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");
+ if (!lchan->activate.requires_voice_stream
+ || lchan_rtp_established(lchan))
+ lchan_fsm_state_chg(LCHAN_ST_ESTABLISHED);
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);
+ case LCHAN_EV_RTP_READY:
+ if (lchan->sapis[0] != LCHAN_SAPI_UNUSED)
+ 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_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;
+ }
- case LCHAN_EV_RLL_ESTABLISH_IND:
- /* abis_rsl.c has noticed that a SAPI was established, no need to take action here. */
+ 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:
@@ -967,7 +731,6 @@ static void lchan_fsm_wait_mgw_endpoint_configured(struct osmo_fsm_inst *fi, uin
}
}
-
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 @@ static void lchan_fsm_established_onenter(struct osmo_fsm_inst *fi, uint32_t pre
* 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 @@ static int lchan_active_sapis(struct gsm_lchan *lchan, int start)
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 @@ static void handle_rll_rel_ind_or_conf(struct osmo_fsm_inst *fi, uint32_t event,
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 @@ static void lchan_fsm_established(struct osmo_fsm_inst *fi, uint32_t event, void
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 bool should_sacch_deact(struct gsm_lchan *lchan)
}
}
-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 @@ static void lchan_fsm_wait_sapis_released_onenter(struct osmo_fsm_inst *fi, uint
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 @@ static void lchan_fsm_wait_sapis_released_onenter(struct osmo_fsm_inst *fi, uint
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 @@ static void lchan_fsm_borken(struct osmo_fsm_inst *fi, uint32_t event, void *dat
* 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 @@ static const struct osmo_fsm_state lchan_fsm_states[] = {
.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 @@ static const struct osmo_fsm_state lchan_fsm_states[] = {
.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 @@ static const struct osmo_fsm_state lchan_fsm_states[] = {
| 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 @@ static const struct osmo_fsm_state lchan_fsm_states[] = {
| 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 @@ static const struct value_string lchan_fsm_event_names[] = {
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 @@ static const struct value_string lchan_fsm_event_names[] = {
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 @@ void lchan_release(struct gsm_lchan *lchan, bool sacch_deact,
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 @@ void lchan_release(struct gsm_lchan *lchan, bool sacch_deact,
/* 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 @@ void lchan_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
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 @@ static struct osmo_fsm lchan_fsm = {
.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 000000000..3530b8a72
--- /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@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@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 107a234eb..3ce870b62 100644
--- a/tests/handover/Makefile.am
+++ b/tests/handover/Makefile.am
@@ -63,6 +63,7 @@ handover_test_LDADD = \
$(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 172e620fa..92c002dcc 100644
--- a/tests/handover/handover_test.c
+++ b/tests/handover/handover_test.c
@@ -394,6 +394,7 @@ static void send_ho_complete(struct gsm_lchan *lchan, bool success)
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;