osmo-bsc/src/osmo-bsc/osmo_bsc_lcls.c

878 lines
27 KiB
C
Raw Normal View History

/* (C) 2018 by Harald Welte <laforge@gnumonks.org>
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU 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/utils.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/fsm.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/gsm/gsm0808.h>
#include <osmocom/core/msgb.h>
#include <osmocom/bsc/abis_rsl.h>
#include <osmocom/bsc/bsc_msc_data.h>
#include <osmocom/bsc/debug.h>
#include <osmocom/bsc/osmo_bsc.h>
#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
#include <osmocom/bsc/gsm_data.h>
#include <osmocom/bsc/osmo_bsc_lcls.h>
#include <osmocom/bsc/lchan_rtp_fsm.h>
#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
struct value_string lcls_event_names[] = {
{ LCLS_EV_UPDATE_CFG_CSC, "UPDATE_CFG_CSC" },
{ LCLS_EV_APPLY_CFG_CSC, "APPLY_CFG_CSC" },
{ LCLS_EV_CORRELATED, "CORRELATED" },
{ LCLS_EV_OTHER_ENABLED, "OTHER_ENABLED" },
{ LCLS_EV_OTHER_BREAK, "OTHER_BREAK" },
{ LCLS_EV_OTHER_DEAD, "OTHER_DEAD" },
{ 0, NULL }
};
const struct value_string bsc_lcls_mode_names[] = {
{ BSC_LCLS_MODE_DISABLED, "disabled" },
{ BSC_LCLS_MODE_MGW_LOOP, "mgw-loop" },
{ BSC_LCLS_MODE_BTS_LOOP, "bts-loop" },
{ 0, NULL }
};
/***********************************************************************
* Utility functions
***********************************************************************/
enum gsm0808_lcls_status lcls_get_status(const struct gsm_subscriber_connection *conn)
{
if (!conn->lcls.fi)
return GSM0808_LCLS_STS_NA;
switch (conn->lcls.fi->state) {
case ST_NO_LCLS:
return GSM0808_LCLS_STS_NA;
case ST_NOT_YET_LS:
return GSM0808_LCLS_STS_NOT_YET_LS;
case ST_NOT_POSSIBLE_LS:
return GSM0808_LCLS_STS_NOT_POSSIBLE_LS;
case ST_NO_LONGER_LS:
return GSM0808_LCLS_STS_NO_LONGER_LS;
case ST_REQ_LCLS_NOT_SUPP:
return GSM0808_LCLS_STS_REQ_LCLS_NOT_SUPP;
case ST_LOCALLY_SWITCHED:
case ST_LOCALLY_SWITCHED_WAIT_BREAK:
case ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK:
return GSM0808_LCLS_STS_LOCALLY_SWITCHED;
}
OSMO_ASSERT(0);
}
static void lcls_send_notify(struct gsm_subscriber_connection *conn)
{
enum gsm0808_lcls_status status = lcls_get_status(conn);
struct msgb *msg;
if (status == GSM0808_LCLS_STS_NA)
return;
LOGPFSM(conn->lcls.fi, "Sending BSSMAP LCLS NOTIFICATION (%s)\n",
gsm0808_lcls_status_name(status));
msg = gsm0808_create_lcls_notification(status, false);
osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_TX_SCCP, msg);
}
static struct gsm_subscriber_connection *
find_conn_with_same_gcr(const struct gsm_subscriber_connection *conn_local)
{
struct gsm_network *net = conn_local->network;
struct gsm_subscriber_connection *conn_other;
llist_for_each_entry(conn_other, &net->subscr_conns, entry) {
/* don't report back the same connection */
if (conn_other == conn_local)
continue;
/* don't consider any conn where GCR length is not the same as before */
if (conn_other->lcls.global_call_ref_len != conn_local->lcls.global_call_ref_len)
continue;
if (!memcmp(conn_other->lcls.global_call_ref, conn_local->lcls.global_call_ref,
conn_local->lcls.global_call_ref_len))
return conn_other;
}
return NULL;
}
static bool lcls_is_supported_config(enum gsm0808_lcls_config cfg)
{
/* this is the only configuration that we support for now */
if (cfg == GSM0808_LCLS_CFG_BOTH_WAY)
return true;
else
return false;
}
/* LCLS Call Leg Correlation as per 23.284 4.3 / 48.008 3.1.33.2.1 */
static int lcls_perform_correlation(struct gsm_subscriber_connection *conn_local)
{
struct gsm_subscriber_connection *conn_other;
/* We can only correlate if a GCR is present */
OSMO_ASSERT(conn_local->lcls.global_call_ref_len);
/* We can only correlate if we're not in active LS */
OSMO_ASSERT(conn_local->lcls.fi->state != ST_LOCALLY_SWITCHED &&
conn_local->lcls.fi->state != ST_LOCALLY_SWITCHED_WAIT_BREAK &&
conn_local->lcls.fi->state != ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK);
conn_other = conn_local->lcls.other;
if (conn_other) {
LOGPFSM(conn_local->lcls.fi, "Breaking previous correlation with %s\n",
osmo_fsm_inst_name(conn_other->lcls.fi));
OSMO_ASSERT(conn_other->lcls.fi->state != ST_LOCALLY_SWITCHED &&
conn_other->lcls.fi->state != ST_LOCALLY_SWITCHED_WAIT_BREAK &&
conn_other->lcls.fi->state != ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK);
conn_local->lcls.other->lcls.other = NULL;
conn_local->lcls.other = NULL;
}
conn_other = find_conn_with_same_gcr(conn_local);
if (!conn_other) {
/* we found no other call with same GCR: not possible */
LOGPFSM(conn_local->lcls.fi, "Unsuccessful correlation\n");
return -ENODEV;
}
/* store pointer to "other" in "local" */
conn_local->lcls.other = conn_other;
LOGPFSM(conn_local->lcls.fi, "Successfully correlated with %s\n",
osmo_fsm_inst_name(conn_other->lcls.fi));
/* notify other conn about our correlation */
osmo_fsm_inst_dispatch(conn_other->lcls.fi, LCLS_EV_CORRELATED, conn_local);
return 0;
}
/* Update the connections LCLS configuration and return old/previous configuration.
* \returns (staticallly allocated) old configuration; NULL if new config not supported */
static struct osmo_lcls *update_lcls_cfg_csc(struct gsm_subscriber_connection *conn,
const struct osmo_lcls *new_cfg_csc)
{
static struct osmo_lcls old_cfg_csc = { 0 };
old_cfg_csc.config = conn->lcls.config;
old_cfg_csc.control = conn->lcls.control;
if (new_cfg_csc->config != GSM0808_LCLS_CFG_NA) {
if (!lcls_is_supported_config(new_cfg_csc->config))
return NULL;
if (conn->lcls.config != new_cfg_csc->config) {
LOGPFSM(conn->lcls.fi, "LCLS update Config %s -> %s\n",
gsm0808_lcls_config_name(conn->lcls.config),
gsm0808_lcls_config_name(new_cfg_csc->config));
conn->lcls.config = new_cfg_csc->config;
}
}
if (new_cfg_csc->control != GSM0808_LCLS_CSC_NA) {
if (conn->lcls.control != new_cfg_csc->control) {
LOGPFSM(conn->lcls.fi, "LCLS update Control %s -> %s\n",
gsm0808_lcls_control_name(conn->lcls.control),
gsm0808_lcls_control_name(new_cfg_csc->control));
conn->lcls.control = new_cfg_csc->control;
}
}
return &old_cfg_csc;
}
/* Attempt to update conn->lcls with the new config/csc provided. If new config is
* unsupported, change into LCLS NOT SUPPORTED state and return -EINVAL. */
static int lcls_handle_cfg_update(struct gsm_subscriber_connection *conn, void *data)
{
struct osmo_lcls *new_cfg_csc, *old_cfg_csc;
new_cfg_csc = (struct osmo_lcls *) data;
old_cfg_csc = update_lcls_cfg_csc(conn, new_cfg_csc);
if (!old_cfg_csc) {
osmo_fsm_inst_state_chg(conn->lcls.fi, ST_REQ_LCLS_NOT_SUPP, 0, 0);
return -EINVAL;
}
return 0;
}
/* notify the LCLS FSM about new LCLS Config and/or CSC */
void lcls_update_config(struct gsm_subscriber_connection *conn,
const uint8_t *config, const uint8_t *control)
{
struct osmo_lcls new_cfg = {
.config = GSM0808_LCLS_CFG_NA,
.control = GSM0808_LCLS_CSC_NA,
};
/* nothing to update, skip it */
if (!config && !control)
return;
if (config)
new_cfg.config = *config;
if (control)
new_cfg.control = *control;
osmo_fsm_inst_dispatch(conn->lcls.fi, LCLS_EV_UPDATE_CFG_CSC, &new_cfg);
}
/* apply the configuration, may be changed before by lcls_update_config */
void lcls_apply_config(struct gsm_subscriber_connection *conn)
{
osmo_fsm_inst_dispatch(conn->lcls.fi, LCLS_EV_APPLY_CFG_CSC, NULL);
}
/* Redirect BTS's RTP traffic via RSL MDCX command:
* when enable == true, redirect to remote BTS's IP:port
* when enable == false, redirect back to the MGW's IP:port
*/
static inline void lcls_rsl(const struct gsm_subscriber_connection *conn, bool enable)
{
const struct gsm_lchan *lchan = conn->lchan;
/* RSL_IE_IPAC_REMOTE_IP */
uint32_t ip = enable ? conn->lcls.other->lchan->abis_ip.bound_ip : lchan->abis_ip.connect_ip;
/* RSL_IE_IPAC_REMOTE_PORT */
uint16_t port = enable ? conn->lcls.other->lchan->abis_ip.bound_port : lchan->abis_ip.connect_port;
struct msgb *msg;
if (!conn->lcls.other) {
LOGPFSM(conn->lcls.fi, "%s LCLS: other conn is not available!\n", enable ? "enable" : "disable");
return;
}
msg = rsl_make_ipacc_mdcx(lchan, ip, port);
if (!msg) {
LOGPFSML(conn->lcls.fi, LOGL_ERROR, "Error encoding IPACC MDCX\n");
return;
}
abis_rsl_sendmsg(msg);
}
static inline bool lcls_check_toggle_allowed(const struct gsm_subscriber_connection *conn, bool enable)
{
if (conn->lcls.other &&
conn->sccp.msc->lcls_mode != conn->lcls.other->sccp.msc->lcls_mode) {
LOGPFSM(conn->lcls.fi, "FIXME: LCLS connection mode mismatch: %s != %s\n",
get_value_string(bsc_lcls_mode_names, conn->sccp.msc->lcls_mode),
get_value_string(bsc_lcls_mode_names, conn->lcls.other->sccp.msc->lcls_mode));
return false;
}
if (conn->sccp.msc->lcls_mode == BSC_LCLS_MODE_MGW_LOOP && !conn->user_plane.mgw_endpoint_ci_msc) {
/* the MGCP FSM has died, e.g. due to some MGCP/SDP parsing error */
LOGPFSML(conn->lcls.fi, LOGL_NOTICE, "Cannot %s LCLS without MSC-side MGCP FSM\n",
enable ? "enable" : "disable");
return false;
}
return true;
}
/* Close the loop for LCLS using MGCP */
static inline void lcls_mdcx(const struct gsm_subscriber_connection *conn, struct mgcp_conn_peer *mdcx_info)
{
mgcp_pick_codec(mdcx_info, conn->lchan, false);
osmo_mgcpc_ep_ci_request(conn->user_plane.mgw_endpoint_ci_msc, MGCP_VERB_MDCX, mdcx_info,
NULL, 0, 0, NULL);
}
static void lcls_break_local_switching(struct gsm_subscriber_connection *conn)
{
struct mgcp_conn_peer mdcx_info = {};
LOGPFSM(conn->lcls.fi, "=== HERE IS WHERE WE DISABLE LCLS(%s)\n",
bsc_lcls_mode_name(conn->sccp.msc->lcls_mode));
if (!lcls_check_toggle_allowed(conn, false))
return;
switch(conn->sccp.msc->lcls_mode) {
case BSC_LCLS_MODE_MGW_LOOP:
mdcx_info.port = conn->user_plane.msc_assigned_rtp_port;
osmo_strlcpy(mdcx_info.addr, conn->user_plane.msc_assigned_rtp_addr, sizeof(mdcx_info.addr));
lcls_mdcx(conn, &mdcx_info);
break;
case BSC_LCLS_MODE_BTS_LOOP:
lcls_rsl(conn, false);
break;
case BSC_LCLS_MODE_DISABLED:
LOGPFSM(conn->lcls.fi, "FIXME: attempt to break LCLS loop while LCLS is disabled?!\n");
break;
default:
LOGPFSM(conn->lcls.fi, "FIXME: unknown LCLS mode %s\n",
bsc_lcls_mode_name(conn->sccp.msc->lcls_mode));
}
}
static bool lcls_enable_possible(const struct gsm_subscriber_connection *conn)
{
struct gsm_subscriber_connection *other_conn = conn->lcls.other;
OSMO_ASSERT(other_conn);
if (!lcls_is_supported_config(conn->lcls.config)) {
LOGPFSM(conn->lcls.fi, "Not enabling LS due to unsupported local config\n");
return false;
}
if (!lcls_is_supported_config(other_conn->lcls.config)) {
LOGPFSM(conn->lcls.fi, "Not enabling LS due to unsupported other config\n");
return false;
}
if (conn->lcls.control != GSM0808_LCLS_CSC_CONNECT) {
LOGPFSM(conn->lcls.fi, "Not enabling LS due to insufficient local control\n");
return false;
}
if (other_conn->lcls.control != GSM0808_LCLS_CSC_CONNECT) {
LOGPFSM(conn->lcls.fi, "Not enabling LS due to insufficient other control\n");
return false;
}
if (conn->lchan->type != conn->lcls.other->lchan->type
&& conn->sccp.msc->lcls_codec_mismatch_allow == false) {
LOGPFSM(conn->lcls.fi,
"Not enabling LS due to channel type mismatch: %s:%s != %s:%s\n",
gsm_lchan_name(conn->lchan),
gsm_chan_t_name(conn->lchan->type),
gsm_lchan_name(conn->lcls.other->lchan),
gsm_chan_t_name(conn->lcls.other->lchan->type));
return false;
}
if (conn->lchan->current_ch_mode_rate.chan_mode != conn->lcls.other->lchan->current_ch_mode_rate.chan_mode
&& conn->sccp.msc->lcls_codec_mismatch_allow == false) {
LOGPFSM(conn->lcls.fi,
"Not enabling LS due to TCH-mode mismatch: %s:%s != %s:%s\n",
gsm_lchan_name(conn->lchan),
gsm48_chan_mode_name(conn->lchan->current_ch_mode_rate.chan_mode),
gsm_lchan_name(conn->lcls.other->lchan),
gsm48_chan_mode_name(conn->lcls.other->lchan->current_ch_mode_rate.chan_mode));
return false;
}
return true;
}
/***********************************************************************
* State callback functions
***********************************************************************/
static void lcls_no_lcls_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_subscriber_connection *conn = fi->priv;
/* we're just starting and cannot yet have a correlated call */
OSMO_ASSERT(conn->lcls.other == NULL);
if (conn->sccp.msc->lcls_mode == BSC_LCLS_MODE_DISABLED) {
LOGPFSML(fi, LOGL_DEBUG, "LCLS disabled for this MSC, ignoring %s\n",
osmo_fsm_event_name(fi->fsm, event));
return;
}
/* If there's no GCR set, we can never leave this state */
if (conn->lcls.global_call_ref_len == 0) {
LOGPFSML(fi, LOGL_NOTICE, "No GCR set, ignoring %s\n",
osmo_fsm_event_name(fi->fsm, event));
return;
}
switch (event) {
case LCLS_EV_UPDATE_CFG_CSC:
if (lcls_handle_cfg_update(conn, data) != 0)
return;
break;
case LCLS_EV_APPLY_CFG_CSC:
if (conn->lcls.config == GSM0808_LCLS_CFG_NA)
return;
if (lcls_perform_correlation(conn) != 0) {
/* Correlation leads to no result: Not Possible to LS */
osmo_fsm_inst_state_chg(fi, ST_NOT_POSSIBLE_LS, 0, 0);
return;
}
/* we now have two correlated calls */
OSMO_ASSERT(conn->lcls.other);
if (lcls_enable_possible(conn)) {
/* Local Switching now active */
osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0);
osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_ENABLED, conn);
} else {
/* Couldn't be enabled: Not yet LS */
osmo_fsm_inst_state_chg(fi, ST_NOT_YET_LS, 0, 0);
}
break;
default:
OSMO_ASSERT(0);
break;
}
}
static void lcls_not_yet_ls_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_subscriber_connection *conn = fi->priv;
/* not yet locally switched means that we have correlation but no instruction
* to actually connect them yet */
OSMO_ASSERT(conn->lcls.other);
switch (event) {
case LCLS_EV_UPDATE_CFG_CSC:
if (lcls_handle_cfg_update(conn, data) != 0)
return;
break;
case LCLS_EV_APPLY_CFG_CSC:
if (lcls_enable_possible(conn)) {
osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0);
osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_ENABLED, conn);
}
break;
case LCLS_EV_OTHER_ENABLED:
OSMO_ASSERT(conn->lcls.other == data);
if (lcls_enable_possible(conn)) {
osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0);
/* Send LCLS-NOTIFY to inform MSC */
lcls_send_notify(conn);
} else {
/* we couldn't enable our side, so ask other side to break */
osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_BREAK, conn);
}
break;
case LCLS_EV_CORRELATED:
/* other call informs us that he correlated with us */
conn->lcls.other = data;
break;
case LCLS_EV_OTHER_DEAD:
OSMO_ASSERT(conn->lcls.other == data);
conn->lcls.other = NULL;
osmo_fsm_inst_state_chg(fi, ST_NOT_POSSIBLE_LS, 0, 0);
/* Send LCLS-NOTIFY to inform MSC */
lcls_send_notify(conn);
break;
default:
OSMO_ASSERT(0);
break;
}
}
static void lcls_not_possible_ls_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_subscriber_connection *conn = fi->priv;
OSMO_ASSERT(conn->lcls.other == NULL);
switch (event) {
case LCLS_EV_UPDATE_CFG_CSC:
if (lcls_handle_cfg_update(conn, data) != 0)
return;
break;
case LCLS_EV_APPLY_CFG_CSC:
if (lcls_perform_correlation(conn) != 0) {
/* no correlation result: Remain in NOT_POSSIBLE_LS */
return;
}
/* we now have two correlated calls */
OSMO_ASSERT(conn->lcls.other);
if (lcls_enable_possible(conn)) {
osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0);
osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_ENABLED, conn);
} else {
osmo_fsm_inst_state_chg(fi, ST_NOT_YET_LS, 0, 0);
}
break;
case LCLS_EV_CORRELATED:
/* other call informs us that he correlated with us */
conn->lcls.other = data;
osmo_fsm_inst_state_chg(fi, ST_NOT_YET_LS, 0, 0);
/* Send NOTIFY about the fact that correlation happened */
lcls_send_notify(conn);
break;
case LCLS_EV_OTHER_DEAD:
OSMO_ASSERT(conn->lcls.other == data);
conn->lcls.other = NULL;
break;
default:
OSMO_ASSERT(0);
break;
}
}
static void lcls_no_longer_ls_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_subscriber_connection *conn = fi->priv;
OSMO_ASSERT(conn->lcls.other);
switch (event) {
case LCLS_EV_UPDATE_CFG_CSC:
if (lcls_handle_cfg_update(conn, data) != 0)
return;
if (lcls_enable_possible(conn)) {
osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0);
osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_ENABLED, conn);
}
break;
case LCLS_EV_OTHER_ENABLED:
OSMO_ASSERT(conn->lcls.other == data);
if (lcls_enable_possible(conn)) {
osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0);
/* Send LCLS-NOTIFY to inform MSC */
lcls_send_notify(conn);
} else {
/* we couldn't enable our side, so ask other side to break */
osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_BREAK, conn);
}
break;
case LCLS_EV_CORRELATED:
/* other call informs us that he correlated with us */
conn->lcls.other = data;
break;
case LCLS_EV_OTHER_DEAD:
OSMO_ASSERT(conn->lcls.other == data);
conn->lcls.other = NULL;
osmo_fsm_inst_state_chg(fi, ST_NOT_POSSIBLE_LS, 0, 0);
/* Send LCLS-NOTIFY to inform MSC */
lcls_send_notify(conn);
break;
default:
OSMO_ASSERT(0);
break;
}
}
static void lcls_req_lcls_not_supp_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_subscriber_connection *conn = fi->priv;
/* we could have a correlated other call or not */
switch (event) {
case LCLS_EV_UPDATE_CFG_CSC:
if (lcls_handle_cfg_update(conn, data) != 0)
return;
//FIXME osmo_fsm_inst_state_chg(fi,
return;
case LCLS_EV_APPLY_CFG_CSC:
if (lcls_perform_correlation(conn) != 0) {
osmo_fsm_inst_state_chg(fi, ST_NOT_POSSIBLE_LS, 0, 0);
return;
}
/* we now have two correlated calls */
OSMO_ASSERT(conn->lcls.other);
if (!lcls_is_supported_config(conn->lcls.config))
return;
if (lcls_enable_possible(conn))
osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED, 0, 0);
else
osmo_fsm_inst_state_chg(fi, ST_NOT_YET_LS, 0, 0);
break;
case LCLS_EV_CORRELATED:
/* other call informs us that he correlated with us */
conn->lcls.other = data;
break;
case LCLS_EV_OTHER_DEAD:
OSMO_ASSERT(conn->lcls.other == data);
conn->lcls.other = NULL;
break;
default:
OSMO_ASSERT(0);
break;
}
}
static void lcls_locally_switched_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_subscriber_connection *conn = fi->priv;
OSMO_ASSERT(conn->lcls.other);
switch (event) {
case LCLS_EV_UPDATE_CFG_CSC:
if (lcls_handle_cfg_update(conn, data) != 0) {
lcls_break_local_switching(conn);
return;
}
break;
case LCLS_EV_APPLY_CFG_CSC:
if (conn->lcls.control == GSM0808_LCLS_CSC_RELEASE_LCLS) {
osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK, 0, 0);
osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_BREAK, conn);
/* FIXME: what if there's a new config included? */
return;
}
/* TODO: Handle any changes of "config" once we support bi-casting etc. */
break;
case LCLS_EV_OTHER_BREAK:
OSMO_ASSERT(conn->lcls.other == data);
osmo_fsm_inst_state_chg(fi, ST_LOCALLY_SWITCHED_WAIT_BREAK, 0, 0);
break;
case LCLS_EV_OTHER_DEAD:
OSMO_ASSERT(conn->lcls.other == data);
conn->lcls.other = NULL;
osmo_fsm_inst_state_chg(fi, ST_NOT_POSSIBLE_LS, 0, 0);
/* Send LCLS-NOTIFY to inform MSC */
lcls_send_notify(conn);
break;
default:
OSMO_ASSERT(0);
break;
}
}
static void lcls_locally_switched_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
{
struct gsm_subscriber_connection *conn = fi->priv;
struct gsm_subscriber_connection *conn_other = conn->lcls.other;
large refactoring: use FSMs for lchans; add inter-BSC HO Add FSMs: - timeslot_fsm: handle dynamic timeslots and OML+RSL availability. - lchan_fsm: handle an individual lchan activation, RTP stream and release, signal the appropriate calling FSMs on success, failure, release. - mgw_endpoint_fsm: handle one entire endpoint with several CI. - assignment_fsm: BSSMAP Assignment Request. - handover_fsm: all of intra, inter-MO and inter-MT handover. Above FSMs absorb large parts of the gscon FSM. The gscon FSM was surpassing the maximum amount events (32), and it is more logical to treat assignment, handover and MGW procedures in separate FSMs. - Add logging macros for each FSM type: - LOG_TS() - LOG_LCHAN() - LOG_MGWEP(), LOG_CI() - LOG_ASSIGNMENT() - LOG_HO() These log with the osmo_fsm_inst where present. New style decision: logging without a final newline char is awkward, especially for gsmtap logging and when other logs interleave LOGPC() calls; we have various cases where the final \n goes missing, and also this invokes the log category checking N times instead of once. So I decided to make these macros *always* append a newline, but only if there is no final newline yet. I hope that the compiler optimizes the strlen() of the constant format strings away. Thus I can log with or without typing "\n" and always get an \n termination anyway. General: - replace osmo_timers, state enums and program-wide osmo_signal_dispatch() with dedicated FSM timeouts, states and events. - introduce a common way to handle Tnnn timers: gsm_timers.h/.c: struct T_def. These can be used (with some macro magic) to define a state's timeout once, and not make mistakes for each osmo_fsm_inst_state_chg(). Details: bsc_subscr_conn_fsm.c: - move most states of this FSM to lchan_fsm, assignment_fsm, handover_fsm and mgw_endpoint_fsm. - There is exactly one state for an ongoing Assignment, with all details handled in conn->assignment.fi. The state relies on the assignment_fsm's timeout. - There is one state for an ongoing Handover; except for an incoming Handover from a remote BSS, the gscon remains in ST_INIT until the new lchan and conn are both established. - move bssmap_add_lcls_status() to osmo_bsc_lcls.c abis_rsl.c: - move all dynamic timeslot logic away into timeslot_fsm. Only keep plain send/receive functions in abis_rsl.c - reduce some rsl functions to merely send a message, rename to "_tx_". - rsl_ipacc_mdcx(): add '_tx_' in the name; move parts that change the lchan state out into the lchan_fsm, the lchan->abis_ip.* are now set there prior to invoking this function. - move all timers and error/release handling away into various FSMs. - tweak ipa_smod_s_for_lchan() and ipa_rtp_pt_for_lchan() to not require an lchan passed, but just mode,type that they require. Rename to ipacc_speech_mode*() and ipacc_payload_type(). - add rsl_forward_layer3_info, used for inter-BSC HO MO, to just send the RR message received during BSSMAP Handover Command. - move various logging to LOG_LCHAN() in order to log with the lchan FSM instance. One drawback is that the lchan FSM is limited to one logging category, i.e. this moves some logging from DRR to DRSL. It might actually make sense to combine those categories. - lose LOGP...LOGPC logging cascades: they are bad for gsmtap logging and for performance. - handle_classmark_chg(): change logging, move cm2 len check out of the cm3 condition (I hope that's correct). - gsm48_send_ho_cmd(): split off gsm48_make_ho_cmd() which doesn't send right away, so that during inter-bsc HO we can make an RR Handover Command to send via the MSC to the remote BSS. assignment_fsm.c: - the Chan Mode Modify in case of re-using the same lchan is not implemented yet, because this was also missing in the previous implementation (OS#3357). osmo_bsc_api.c: - simplify bsc_mr_config() and move to lchan_fsm.c, the only caller; rename to lchan_mr_config(). (bsc_mr_config() used to copy the values to mr_bts_lv twice, once by member assignment and then again with a memcpy.) - During handover, we used to copy the MR config from the old lchan. Since we may handover between FR and HR, rather set the MR Config anew every time, so that FR rates are always available on FR lchans, and never on HR lchans. Depends: I03ee7ce840ecfa0b6a33358e7385528aabd4873f (libosmocore), I1f2918418c38918c5ac70acaa51a47adfca12b5e (libosmocore) Change-Id: I82e3f918295daa83274a4cf803f046979f284366
2018-05-14 16:14:15 +00:00
const struct mgcp_conn_peer *other_mgw_info;
struct mgcp_conn_peer mdcx_info;
OSMO_ASSERT(conn_other);
if (!lcls_check_toggle_allowed(conn, true))
return;
LOGPFSM(fi, "=== HERE IS WHERE WE ENABLE LCLS(%s)\n",
bsc_lcls_mode_name(conn->sccp.msc->lcls_mode));
large refactoring: use FSMs for lchans; add inter-BSC HO Add FSMs: - timeslot_fsm: handle dynamic timeslots and OML+RSL availability. - lchan_fsm: handle an individual lchan activation, RTP stream and release, signal the appropriate calling FSMs on success, failure, release. - mgw_endpoint_fsm: handle one entire endpoint with several CI. - assignment_fsm: BSSMAP Assignment Request. - handover_fsm: all of intra, inter-MO and inter-MT handover. Above FSMs absorb large parts of the gscon FSM. The gscon FSM was surpassing the maximum amount events (32), and it is more logical to treat assignment, handover and MGW procedures in separate FSMs. - Add logging macros for each FSM type: - LOG_TS() - LOG_LCHAN() - LOG_MGWEP(), LOG_CI() - LOG_ASSIGNMENT() - LOG_HO() These log with the osmo_fsm_inst where present. New style decision: logging without a final newline char is awkward, especially for gsmtap logging and when other logs interleave LOGPC() calls; we have various cases where the final \n goes missing, and also this invokes the log category checking N times instead of once. So I decided to make these macros *always* append a newline, but only if there is no final newline yet. I hope that the compiler optimizes the strlen() of the constant format strings away. Thus I can log with or without typing "\n" and always get an \n termination anyway. General: - replace osmo_timers, state enums and program-wide osmo_signal_dispatch() with dedicated FSM timeouts, states and events. - introduce a common way to handle Tnnn timers: gsm_timers.h/.c: struct T_def. These can be used (with some macro magic) to define a state's timeout once, and not make mistakes for each osmo_fsm_inst_state_chg(). Details: bsc_subscr_conn_fsm.c: - move most states of this FSM to lchan_fsm, assignment_fsm, handover_fsm and mgw_endpoint_fsm. - There is exactly one state for an ongoing Assignment, with all details handled in conn->assignment.fi. The state relies on the assignment_fsm's timeout. - There is one state for an ongoing Handover; except for an incoming Handover from a remote BSS, the gscon remains in ST_INIT until the new lchan and conn are both established. - move bssmap_add_lcls_status() to osmo_bsc_lcls.c abis_rsl.c: - move all dynamic timeslot logic away into timeslot_fsm. Only keep plain send/receive functions in abis_rsl.c - reduce some rsl functions to merely send a message, rename to "_tx_". - rsl_ipacc_mdcx(): add '_tx_' in the name; move parts that change the lchan state out into the lchan_fsm, the lchan->abis_ip.* are now set there prior to invoking this function. - move all timers and error/release handling away into various FSMs. - tweak ipa_smod_s_for_lchan() and ipa_rtp_pt_for_lchan() to not require an lchan passed, but just mode,type that they require. Rename to ipacc_speech_mode*() and ipacc_payload_type(). - add rsl_forward_layer3_info, used for inter-BSC HO MO, to just send the RR message received during BSSMAP Handover Command. - move various logging to LOG_LCHAN() in order to log with the lchan FSM instance. One drawback is that the lchan FSM is limited to one logging category, i.e. this moves some logging from DRR to DRSL. It might actually make sense to combine those categories. - lose LOGP...LOGPC logging cascades: they are bad for gsmtap logging and for performance. - handle_classmark_chg(): change logging, move cm2 len check out of the cm3 condition (I hope that's correct). - gsm48_send_ho_cmd(): split off gsm48_make_ho_cmd() which doesn't send right away, so that during inter-bsc HO we can make an RR Handover Command to send via the MSC to the remote BSS. assignment_fsm.c: - the Chan Mode Modify in case of re-using the same lchan is not implemented yet, because this was also missing in the previous implementation (OS#3357). osmo_bsc_api.c: - simplify bsc_mr_config() and move to lchan_fsm.c, the only caller; rename to lchan_mr_config(). (bsc_mr_config() used to copy the values to mr_bts_lv twice, once by member assignment and then again with a memcpy.) - During handover, we used to copy the MR config from the old lchan. Since we may handover between FR and HR, rather set the MR Config anew every time, so that FR rates are always available on FR lchans, and never on HR lchans. Depends: I03ee7ce840ecfa0b6a33358e7385528aabd4873f (libosmocore), I1f2918418c38918c5ac70acaa51a47adfca12b5e (libosmocore) Change-Id: I82e3f918295daa83274a4cf803f046979f284366
2018-05-14 16:14:15 +00:00
if (!conn_other->user_plane.mgw_endpoint_ci_msc) {
LOGPFSML(fi, LOGL_ERROR, "Cannot enable LCLS without MSC-side MGCP FSM. FIXME\n");
return;
}
other_mgw_info = osmo_mgcpc_ep_ci_get_rtp_info(conn_other->user_plane.mgw_endpoint_ci_msc);
large refactoring: use FSMs for lchans; add inter-BSC HO Add FSMs: - timeslot_fsm: handle dynamic timeslots and OML+RSL availability. - lchan_fsm: handle an individual lchan activation, RTP stream and release, signal the appropriate calling FSMs on success, failure, release. - mgw_endpoint_fsm: handle one entire endpoint with several CI. - assignment_fsm: BSSMAP Assignment Request. - handover_fsm: all of intra, inter-MO and inter-MT handover. Above FSMs absorb large parts of the gscon FSM. The gscon FSM was surpassing the maximum amount events (32), and it is more logical to treat assignment, handover and MGW procedures in separate FSMs. - Add logging macros for each FSM type: - LOG_TS() - LOG_LCHAN() - LOG_MGWEP(), LOG_CI() - LOG_ASSIGNMENT() - LOG_HO() These log with the osmo_fsm_inst where present. New style decision: logging without a final newline char is awkward, especially for gsmtap logging and when other logs interleave LOGPC() calls; we have various cases where the final \n goes missing, and also this invokes the log category checking N times instead of once. So I decided to make these macros *always* append a newline, but only if there is no final newline yet. I hope that the compiler optimizes the strlen() of the constant format strings away. Thus I can log with or without typing "\n" and always get an \n termination anyway. General: - replace osmo_timers, state enums and program-wide osmo_signal_dispatch() with dedicated FSM timeouts, states and events. - introduce a common way to handle Tnnn timers: gsm_timers.h/.c: struct T_def. These can be used (with some macro magic) to define a state's timeout once, and not make mistakes for each osmo_fsm_inst_state_chg(). Details: bsc_subscr_conn_fsm.c: - move most states of this FSM to lchan_fsm, assignment_fsm, handover_fsm and mgw_endpoint_fsm. - There is exactly one state for an ongoing Assignment, with all details handled in conn->assignment.fi. The state relies on the assignment_fsm's timeout. - There is one state for an ongoing Handover; except for an incoming Handover from a remote BSS, the gscon remains in ST_INIT until the new lchan and conn are both established. - move bssmap_add_lcls_status() to osmo_bsc_lcls.c abis_rsl.c: - move all dynamic timeslot logic away into timeslot_fsm. Only keep plain send/receive functions in abis_rsl.c - reduce some rsl functions to merely send a message, rename to "_tx_". - rsl_ipacc_mdcx(): add '_tx_' in the name; move parts that change the lchan state out into the lchan_fsm, the lchan->abis_ip.* are now set there prior to invoking this function. - move all timers and error/release handling away into various FSMs. - tweak ipa_smod_s_for_lchan() and ipa_rtp_pt_for_lchan() to not require an lchan passed, but just mode,type that they require. Rename to ipacc_speech_mode*() and ipacc_payload_type(). - add rsl_forward_layer3_info, used for inter-BSC HO MO, to just send the RR message received during BSSMAP Handover Command. - move various logging to LOG_LCHAN() in order to log with the lchan FSM instance. One drawback is that the lchan FSM is limited to one logging category, i.e. this moves some logging from DRR to DRSL. It might actually make sense to combine those categories. - lose LOGP...LOGPC logging cascades: they are bad for gsmtap logging and for performance. - handle_classmark_chg(): change logging, move cm2 len check out of the cm3 condition (I hope that's correct). - gsm48_send_ho_cmd(): split off gsm48_make_ho_cmd() which doesn't send right away, so that during inter-bsc HO we can make an RR Handover Command to send via the MSC to the remote BSS. assignment_fsm.c: - the Chan Mode Modify in case of re-using the same lchan is not implemented yet, because this was also missing in the previous implementation (OS#3357). osmo_bsc_api.c: - simplify bsc_mr_config() and move to lchan_fsm.c, the only caller; rename to lchan_mr_config(). (bsc_mr_config() used to copy the values to mr_bts_lv twice, once by member assignment and then again with a memcpy.) - During handover, we used to copy the MR config from the old lchan. Since we may handover between FR and HR, rather set the MR Config anew every time, so that FR rates are always available on FR lchans, and never on HR lchans. Depends: I03ee7ce840ecfa0b6a33358e7385528aabd4873f (libosmocore), I1f2918418c38918c5ac70acaa51a47adfca12b5e (libosmocore) Change-Id: I82e3f918295daa83274a4cf803f046979f284366
2018-05-14 16:14:15 +00:00
if (!other_mgw_info) {
LOGPFSML(fi, LOGL_ERROR, "Cannot enable LCLS without RTP port info of MSC-side"
" -- missing CRCX?\n");
return;
}
switch(conn->sccp.msc->lcls_mode) {
case BSC_LCLS_MODE_MGW_LOOP:
mdcx_info = *other_mgw_info;
/* Make sure the request doesn't want to use the other side's endpoint string. */
mdcx_info.endpoint[0] = 0;
lcls_mdcx(conn, &mdcx_info);
break;
case BSC_LCLS_MODE_BTS_LOOP:
lcls_rsl(conn, true);
break;
case BSC_LCLS_MODE_DISABLED:
LOGPFSM(conn->lcls.fi, "FIXME: attempt to close LCLS loop while LCLS is disabled?!\n");
break;
default:
LOGPFSM(conn->lcls.fi, "FIXME: unknown LCLS mode %s\n",
bsc_lcls_mode_name(conn->sccp.msc->lcls_mode));
}
}
static void lcls_locally_switched_wait_break_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_subscriber_connection *conn = fi->priv;
OSMO_ASSERT(conn->lcls.other);
switch (event) {
case LCLS_EV_UPDATE_CFG_CSC:
if (lcls_handle_cfg_update(conn, data) != 0) {
lcls_break_local_switching(conn);
return;
}
break;
case LCLS_EV_APPLY_CFG_CSC:
if (conn->lcls.control == GSM0808_LCLS_CSC_RELEASE_LCLS) {
lcls_break_local_switching(conn);
osmo_fsm_inst_state_chg(fi, ST_NO_LONGER_LS, 0, 0);
osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_BREAK, conn);
/* no NOTIFY here, as the caller will be returning status in LCLS-CTRL-ACK */
/* FIXME: what if there's a new config included? */
return;
}
/* TODO: Handle any changes of "config" once we support bi-casting etc. */
break;
case LCLS_EV_OTHER_BREAK:
/* we simply ignore it, must be a re-transmission */
break;
case LCLS_EV_OTHER_DEAD:
OSMO_ASSERT(conn->lcls.other == data);
conn->lcls.other = NULL;
break;
default:
lcls_locally_switched_fn(fi, event, data);
break;
}
}
static void lcls_locally_switched_wait_other_break_fn(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gsm_subscriber_connection *conn = fi->priv;
OSMO_ASSERT(conn->lcls.other);
switch (event) {
case LCLS_EV_UPDATE_CFG_CSC:
if (lcls_handle_cfg_update(conn, data) != 0) {
lcls_break_local_switching(conn);
return;
}
/* TODO: Handle any changes of "config" once we support bi-casting etc. */
break;
case LCLS_EV_OTHER_BREAK:
case LCLS_EV_OTHER_DEAD:
OSMO_ASSERT(conn->lcls.other == data);
lcls_break_local_switching(conn);
osmo_fsm_inst_state_chg(fi, ST_NO_LONGER_LS, 0, 0);
/* Send LCLS-NOTIFY to inform MSC */
lcls_send_notify(conn);
break;
default:
lcls_locally_switched_fn(fi, event, data);
break;
}
}
static void lcls_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
{
struct gsm_subscriber_connection *conn = fi->priv;
if (conn->lcls.other) {
/* inform the "other" side that we're dead, so it can disable LS and send NOTIFY */
if (conn->lcls.other->fi)
osmo_fsm_inst_dispatch(conn->lcls.other->lcls.fi, LCLS_EV_OTHER_DEAD, conn);
conn->lcls.other = NULL;
}
}
/***********************************************************************
* FSM Definition
***********************************************************************/
#define S(x) (1 << (x))
static const struct osmo_fsm_state lcls_fsm_states[] = {
[ST_NO_LCLS] = {
.in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) |
S(LCLS_EV_APPLY_CFG_CSC),
.out_state_mask = S(ST_NO_LCLS) |
S(ST_NOT_YET_LS) |
S(ST_NOT_POSSIBLE_LS) |
S(ST_REQ_LCLS_NOT_SUPP) |
S(ST_LOCALLY_SWITCHED),
.name = "NO_LCLS",
.action = lcls_no_lcls_fn,
},
[ST_NOT_YET_LS] = {
.in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) |
S(LCLS_EV_APPLY_CFG_CSC) |
S(LCLS_EV_CORRELATED) |
S(LCLS_EV_OTHER_ENABLED) |
S(LCLS_EV_OTHER_DEAD),
.out_state_mask = S(ST_NOT_YET_LS) |
S(ST_REQ_LCLS_NOT_SUPP) |
S(ST_LOCALLY_SWITCHED),
.name = "NOT_YET_LS",
.action = lcls_not_yet_ls_fn,
},
[ST_NOT_POSSIBLE_LS] = {
.in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) |
S(LCLS_EV_APPLY_CFG_CSC) |
S(LCLS_EV_CORRELATED),
.out_state_mask = S(ST_NOT_YET_LS) |
S(ST_NOT_POSSIBLE_LS) |
S(ST_REQ_LCLS_NOT_SUPP) |
S(ST_LOCALLY_SWITCHED),
.name = "NOT_POSSIBLE_LS",
.action = lcls_not_possible_ls_fn,
},
[ST_NO_LONGER_LS] = {
.in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) |
S(LCLS_EV_APPLY_CFG_CSC) |
S(LCLS_EV_CORRELATED) |
S(LCLS_EV_OTHER_ENABLED) |
S(LCLS_EV_OTHER_DEAD),
.out_state_mask = S(ST_NO_LONGER_LS) |
S(ST_REQ_LCLS_NOT_SUPP) |
S(ST_LOCALLY_SWITCHED),
.name = "NO_LONGER_LS",
.action = lcls_no_longer_ls_fn,
},
[ST_REQ_LCLS_NOT_SUPP] = {
.in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) |
S(LCLS_EV_APPLY_CFG_CSC) |
S(LCLS_EV_CORRELATED) |
S(LCLS_EV_OTHER_DEAD),
.out_state_mask = S(ST_NOT_YET_LS) |
S(ST_REQ_LCLS_NOT_SUPP) |
S(ST_LOCALLY_SWITCHED),
.name = "REQ_LCLS_NOT_SUPP",
.action = lcls_req_lcls_not_supp_fn,
},
[ST_LOCALLY_SWITCHED] = {
.in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) |
S(LCLS_EV_APPLY_CFG_CSC) |
S(LCLS_EV_OTHER_BREAK) |
S(LCLS_EV_OTHER_DEAD),
.out_state_mask = S(ST_NO_LONGER_LS) |
S(ST_NOT_POSSIBLE_LS) |
S(ST_REQ_LCLS_NOT_SUPP) |
S(ST_LOCALLY_SWITCHED_WAIT_BREAK) |
S(ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK) |
S(ST_LOCALLY_SWITCHED),
.name = "LOCALLY_SWITCHED",
.action = lcls_locally_switched_fn,
.onenter = lcls_locally_switched_onenter,
},
/* received an "other" break, waiting for the local break */
[ST_LOCALLY_SWITCHED_WAIT_BREAK] = {
.in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) |
S(LCLS_EV_APPLY_CFG_CSC) |
S(LCLS_EV_OTHER_BREAK) |
S(LCLS_EV_OTHER_DEAD),
.out_state_mask = S(ST_NO_LONGER_LS) |
S(ST_REQ_LCLS_NOT_SUPP) |
S(ST_LOCALLY_SWITCHED) |
S(ST_LOCALLY_SWITCHED_WAIT_BREAK),
.name = "LOCALLY_SWITCHED_WAIT_BREAK",
.action = lcls_locally_switched_wait_break_fn,
},
/* received a local break, waiting for the "other" break */
[ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK] = {
.in_event_mask = S(LCLS_EV_UPDATE_CFG_CSC) |
S(LCLS_EV_OTHER_BREAK) |
S(LCLS_EV_OTHER_DEAD),
.out_state_mask = S(ST_NO_LONGER_LS) |
S(ST_REQ_LCLS_NOT_SUPP) |
S(ST_LOCALLY_SWITCHED) |
S(ST_LOCALLY_SWITCHED_WAIT_OTHER_BREAK),
.name = "LOCALLY_SWITCHED_WAIT_OTHER_BREAK",
.action = lcls_locally_switched_wait_other_break_fn,
},
};
struct osmo_fsm lcls_fsm = {
.name = "LCLS",
.states = lcls_fsm_states,
.num_states = ARRAY_SIZE(lcls_fsm_states),
.allstate_event_mask = 0,
.allstate_action = NULL,
.cleanup = lcls_fsm_cleanup,
.timer_cb = NULL,
.log_subsys = DLCLS,
.event_names = lcls_event_names,
};