From 958f259f9592e3729a2ebf4084d8f37bdba6bf3c Mon Sep 17 00:00:00 2001 From: Neels Hofmeyr Date: Sun, 27 May 2018 01:26:31 +0200 Subject: dissolve libbsc: move all to src/osmo-bsc, link .o files Move all of libbsc/ into osmo-bsc/, and separate/move some implementations to allow linking from utils/* and ipaccess/* without pulling in unccessary dependencies. Some utilities use gsm_network and gsm_bts structs, which already include data structures for fairly advanced uses. Move initialization that only osmo-bsc needs into new bsc_network_init() and bsc_bts_alloc_register() functions, so that the leaner tools can use the old gsm_* versions without the need to link everything (e.g. handover and lchan alloc code). In some instances, there need to be stubs if to cut off linking "just before the RSL level" and prevent dependencies from creeping in. - abis_rsl_rcvmsg(): the only program currently interpreting RSL messages is osmo-bsc, the utils are merely concerned with OML, if at all. - paging_flush_bts(): ip.access nanobts models call this when the RSL link is dropped. Only osmo-bsc actually needs to do anything there. - on_gsm_ts_init(): the mechanism to trigger timeslot initialization is related to OML, while this action to take on init would pull in RSL dependencies. utils/ and ipaccess/ each have a stubs.c file to implement these stubs. Tests implement stubs inline where required. From src/utils/, src/ipaccess/ and tests/*/, link in .o files from osmo-bsc/. In order for this to work, the osmo-bsc subdir must be built before the other source trees. (An alternative would be to include the .c files as sources, but that would re-compile them in every source tree. Not a large burden really, but unless linking .o files gives problems, let's have the quicker build.) Minor obvious cleanups creep in with this patch, I will not bother to name them individually now unless code review asks me to. Rationale: 1) libbsc has been separate to use it for osmo-nitb and osmo-bsc in the old openbsc.git. This is no longer required, and spreading over libbsc and osmo-bsc is distracting. 2) Recently, ridiculous linking requirements have made adding new functions cumbersome, because libbsc has started depending on osmo-bsc/*.c implementations: on gscon FSM and bssap functions. For example, neither bs11_config nor ipaccess-config nor bts_test need handover_cfg or BSSMAP message composition. It makes no sense to link the entire osmo-bsc to it, nor do we want to keep adding stubs to each linking realm. Change-Id: I36a586726f5818121abe54d25654819fc451d3bf --- src/osmo-bsc/osmo_bsc_lcls.c | 766 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 766 insertions(+) create mode 100644 src/osmo-bsc/osmo_bsc_lcls.c (limited to 'src/osmo-bsc/osmo_bsc_lcls.c') diff --git a/src/osmo-bsc/osmo_bsc_lcls.c b/src/osmo-bsc/osmo_bsc_lcls.c new file mode 100644 index 000000000..c2b076090 --- /dev/null +++ b/src/osmo-bsc/osmo_bsc_lcls.c @@ -0,0 +1,766 @@ +/* (C) 2018 by Harald Welte + * 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 . + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 } +}; + + +/*********************************************************************** + * Utility functions + ***********************************************************************/ + +enum gsm0808_lcls_status lcls_get_status(struct gsm_subscriber_connection *conn) +{ + if (!conn->lcls.fi) + return 0xff; + + switch (conn->lcls.fi->state) { + case ST_NO_LCLS: + return 0xff; + 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 == 0xff) + 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(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; +} + + +struct lcls_cfg_csc { + enum gsm0808_lcls_config config; + enum gsm0808_lcls_control control; +}; + +/* Update the connections LCLS configuration and return old/previous configuration. + * \returns (staticallly allocated) old configuration; NULL if new config not supported */ +static struct lcls_cfg_csc *update_lcls_cfg_csc(struct gsm_subscriber_connection *conn, + struct lcls_cfg_csc *new_cfg_csc) +{ + static struct lcls_cfg_csc old_cfg_csc; + old_cfg_csc.config = conn->lcls.config; + old_cfg_csc.control = conn->lcls.control; + + if (new_cfg_csc->config != 0xff) { + if (!lcls_is_supported_config(new_cfg_csc->config)) + return NULL; + if (conn->lcls.config != new_cfg_csc->config) { + /* TODO: logging */ + conn->lcls.config = new_cfg_csc->config; + } + } + if (new_cfg_csc->control != 0xff) { + if (conn->lcls.control != new_cfg_csc->control) { + /* TODO: logging */ + 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 lcls_cfg_csc *new_cfg_csc, *old_cfg_csc; + + new_cfg_csc = (struct lcls_cfg_csc *) 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 lcls_cfg_csc new_cfg = { + .config = 0xff, + .control = 0xff, + }; + /* 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); +} + +static void lcls_break_local_switching(struct gsm_subscriber_connection *conn) +{ + struct mgcp_conn_peer peer; + struct sockaddr_in *sin; + + LOGPFSM(conn->lcls.fi, "=== HERE IS WHERE WE DISABLE LCLS\n"); + if (!conn->user_plane.fi_msc) { + /* the MGCP FSM has died, e.g. due to some MGCP/SDP parsing error */ + LOGPFSML(conn->lcls.fi, LOGL_NOTICE, "Cannot disable LCLS without MSC-side MGCP FSM\n"); + return; + } + + sin = (struct sockaddr_in *)&conn->user_plane.aoip_rtp_addr_remote; + OSMO_ASSERT(sin->sin_family == AF_INET); + + memset(&peer, 0, sizeof(peer)); + peer.port = htons(sin->sin_port); + osmo_strlcpy(peer.addr, inet_ntoa(sin->sin_addr), sizeof(peer.addr)); + mgcp_conn_modify(conn->user_plane.fi_msc, 0, &peer); +} + +static bool lcls_enable_possible(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; + } + + 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; + return; + case LCLS_EV_APPLY_CFG_CSC: + if (conn->lcls.config == 0xff) + 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; + return; + 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; + return; + 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; + struct mgcp_conn_peer peer; + struct sockaddr_in *sin; + + OSMO_ASSERT(conn_other); + + LOGPFSM(fi, "=== HERE IS WHERE WE ENABLE LCLS\n"); + if (!conn->user_plane.fi_msc) { + LOGPFSML(fi, LOGL_ERROR, "Cannot enable LCLS without MSC-side MGCP FSM. FIXME\n"); + return; + } + + sin = (struct sockaddr_in *)&conn_other->user_plane.aoip_rtp_addr_local; + OSMO_ASSERT(sin->sin_family == AF_INET); + + memset(&peer, 0, sizeof(peer)); + peer.port = htons(sin->sin_port); + osmo_strlcpy(peer.addr, inet_ntoa(sin->sin_addr), sizeof(peer.addr)); + mgcp_conn_modify(conn->user_plane.fi_msc, 0, &peer); + +} + +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 disabe 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, +}; -- cgit v1.2.3