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