diff options
Diffstat (limited to 'src/osmo-bsc/assignment_fsm.c')
-rw-r--r-- | src/osmo-bsc/assignment_fsm.c | 650 |
1 files changed, 650 insertions, 0 deletions
diff --git a/src/osmo-bsc/assignment_fsm.c b/src/osmo-bsc/assignment_fsm.c new file mode 100644 index 000000000..e73f90281 --- /dev/null +++ b/src/osmo-bsc/assignment_fsm.c @@ -0,0 +1,650 @@ +/* osmo-bsc BSSMAP Assignment procedure implementation. + * + * (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * All Rights Reserved + * + * Author: Neels Hofmeyr <neels@hofmeyr.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <osmocom/gsm/gsm0808.h> + +#include <osmocom/mgcp_client/mgcp_client_fsm.h> + +#include <osmocom/bsc/debug.h> +#include <osmocom/bsc/gsm_data.h> +#include <osmocom/bsc/gsm_timers.h> +#include <osmocom/bsc/lchan_fsm.h> +#include <osmocom/bsc/mgw_endpoint_fsm.h> +#include <osmocom/bsc/bsc_subscr_conn_fsm.h> +#include <osmocom/bsc/osmo_bsc_lcls.h> +#include <osmocom/bsc/bsc_msc_data.h> +#include <osmocom/bsc/bsc_api.h> +#include <osmocom/bsc/lchan_select.h> +#include <osmocom/bsc/abis_rsl.h> + +#include <osmocom/bsc/assignment_fsm.h> + +static struct osmo_fsm assignment_fsm; + +struct gsm_subscriber_connection *assignment_fi_conn(struct osmo_fsm_inst *fi) +{ + OSMO_ASSERT(fi); + OSMO_ASSERT(fi->fsm == &assignment_fsm); + OSMO_ASSERT(fi->priv); + return fi->priv; +} + +static const struct state_timeout assignment_fsm_timeouts[32] = { + [ASSIGNMENT_ST_WAIT_LCHAN_ACTIVE] = { .T=10 }, + [ASSIGNMENT_ST_WAIT_RR_ASS_COMPLETE] = { .keep_timer=true }, + [ASSIGNMENT_ST_WAIT_LCHAN_ESTABLISHED] = { .keep_timer=true }, + [ASSIGNMENT_ST_WAIT_MGW_ENDPOINT_TO_MSC] = { .T=23042 }, +}; + +/* Transition to a state, using the T timer defined in assignment_fsm_timeouts. + * The actual timeout value is in turn obtained from network->T_defs. + * Assumes local variable fi exists. */ +#define assignment_fsm_state_chg(state) \ + fsm_inst_state_chg_T(fi, state, \ + assignment_fsm_timeouts, \ + ((struct gsm_subscriber_connection*)(fi->priv))->network->T_defs, \ + 5) + +/* Log failure and transition to ASSIGNMENT_ST_FAILURE, which triggers the appropriate actions. */ +#define assignment_fail(cause, fmt, args...) do { \ + struct gsm_subscriber_connection *_conn = fi->priv; \ + _conn->assignment.failure_cause = cause; \ + LOG_ASSIGNMENT(_conn, LOGL_ERROR, "Assignment failed in state %s, cause %s: " fmt "\n", \ + osmo_fsm_inst_state_name(fi), gsm0808_cause_name(cause), ## args); \ + assignment_count_result(BSC_CTR_ASSIGNMENT_ERROR); \ + on_assignment_failure(_conn); \ + } while(0) + +/* Assume presence of local var 'conn' as struct gsm_subscriber_connection */ +#define assignment_count(counter) do { \ + LOG_ASSIGNMENT(conn, LOGL_DEBUG, "incrementing rate counter: %s %s\n", \ + bsc_ctr_description[counter].name, \ + bsc_ctr_description[counter].description); \ + rate_ctr_inc(&conn->network->bsc_ctrs->ctr[counter]); \ + } while(0) + +#define assignment_count_result(counter) do { \ + if (!conn->assignment.result_rate_ctr_done) { \ + assignment_count(counter); \ + conn->assignment.result_rate_ctr_done = true; \ + } else \ + LOG_ASSIGNMENT(conn, LOGL_DEBUG, \ + "result rate counter already recorded, NOT counting as: %s %s", \ + bsc_ctr_description[counter].name, \ + bsc_ctr_description[counter].description); \ + } while(0) + +void assignment_reset(struct gsm_subscriber_connection *conn) +{ + if (conn->assignment.new_lchan) { + struct gsm_lchan *lchan = conn->assignment.new_lchan; + conn->assignment.new_lchan = NULL; + lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL); + } + + if (conn->assignment.created_ci_for_msc) { + gscon_forget_mgw_endpoint_ci(conn, conn->assignment.created_ci_for_msc); + /* If this is the last endpoint released, the mgw_endpoint_fsm will terminate and tell + * the gscon about it. */ + mgw_endpoint_ci_dlcx(conn->assignment.created_ci_for_msc); + } + + conn->assignment = (struct assignment_fsm_data){ + .fi = conn->assignment.fi, /* The FSM shall clear itself when it's done. */ + }; +} + +static void on_assignment_failure(struct gsm_subscriber_connection *conn) +{ + struct msgb *resp = gsm0808_create_assignment_failure(conn->assignment.failure_cause, NULL); + + if (!resp) + LOG_ASSIGNMENT(conn, LOGL_ERROR, "Unable to compose BSSMAP Assignment Failure message\n"); + else + gscon_sigtran_send(conn, resp); + + /* If assignment failed as early as in assignment_fsm_start(), there may not be an fi yet. */ + if (conn->assignment.fi) { + LOG_ASSIGNMENT(conn, LOGL_ERROR, "Assignment failed\n"); + osmo_fsm_inst_term(conn->assignment.fi, OSMO_FSM_TERM_ERROR, 0); + } +} + +static void send_assignment_complete(struct gsm_subscriber_connection *conn, struct gsm_lchan *new_lchan) +{ + int rc; + struct gsm0808_speech_codec sc; + struct gsm0808_speech_codec *sc_ptr = NULL; + struct sockaddr_storage addr_local; + struct sockaddr_storage *addr_local_p = NULL; + int perm_spch = 0; + uint8_t chosen_channel; + struct msgb *resp; + struct gsm_lchan *lchan = new_lchan; + struct osmo_fsm_inst *fi = conn->fi; + + chosen_channel = gsm0808_chosen_channel(lchan->type, lchan->tch_mode); + if (!chosen_channel) { + assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE, + "Unable to compose Chosen Channel for mode=%s type=%s", + get_value_string(gsm48_chan_mode_names, lchan->tch_mode), + gsm_lchant_name(lchan->type)); + return; + } + + /* Generate voice related fields */ + if (conn->assignment.requires_voice_stream) { + perm_spch = gsm0808_permitted_speech(lchan->type, lchan->tch_mode); + + if (gscon_is_aoip(conn)) { + if (!mgwep_ci_get_crcx_info_to_sockaddr(conn->user_plane.mgw_endpoint_ci_msc, + &addr_local)) { + assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE, + "Unable to compose RTP address of MGW -> MSC"); + return; + } + addr_local_p = &addr_local; + + /* Extrapolate speech codec from speech mode */ + gsm0808_speech_codec_from_chan_type(&sc, perm_spch); + sc_ptr = ≻ + } + /* FIXME: AMR codec configuration must be derived from lchan1! */ + } + + gsm0808_speech_codec_from_chan_type(&sc, perm_spch); + + resp = gsm0808_create_ass_compl(lchan->abis_ip.ass_compl.rr_cause, + chosen_channel, + lchan->encr.alg_id, perm_spch, + addr_local_p, sc_ptr, NULL); + + if (!resp) { + assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE, + "Unable to compose Assignment Complete message"); + return; + } + + /* Add LCLS BSS-Status IE in case there is any LCLS status for this connection */ + bssmap_add_lcls_status_if_needed(conn, resp); + + rc = gscon_sigtran_send(conn, resp); + if (rc) { + assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE, + "Unable send Assignment Complete message: rc=%d %s", + rc, strerror(-rc)); + return; + } +} + +static void assignment_success(struct gsm_subscriber_connection *conn) +{ + /* apply LCLS configuration (if any) */ + lcls_apply_config(conn); + + send_assignment_complete(conn, conn->assignment.new_lchan); + /* If something went wrong during send_assignment_complete(), the fi will be gone from + * error handling in there. Almost a success, but then again the whole thing failed. */ + if (!conn->assignment.fi) + return; + + /* Take on the new lchan */ + gscon_change_primary_lchan(conn, conn->assignment.new_lchan); + conn->assignment.new_lchan = NULL; + + /* Rembered this only for error handling: should assignment fail, assignment_reset() will release + * the MGW endpoint right away. If successful, the conn continues to use the endpoint. */ + conn->assignment.created_ci_for_msc = NULL; + + /* New RTP information is now accepted */ + conn->user_plane.msc_assigned_cic = conn->assignment.req.msc_assigned_cic; + osmo_strlcpy(conn->user_plane.msc_assigned_rtp_addr, conn->assignment.req.msc_rtp_addr, + sizeof(conn->user_plane.msc_assigned_rtp_addr)); + conn->user_plane.msc_assigned_rtp_port = conn->assignment.req.msc_rtp_port; + + LOG_ASSIGNMENT(conn, LOGL_DEBUG, "Assignment successful\n"); + osmo_fsm_inst_term(conn->assignment.fi, OSMO_FSM_TERM_REGULAR, 0); + + assignment_count_result(BSC_CTR_ASSIGNMENT_COMPLETED); +} + +static void assignment_fsm_update_id(struct gsm_subscriber_connection *conn) +{ + struct gsm_lchan *new_lchan = conn->assignment.new_lchan; + if (!new_lchan) { + osmo_fsm_inst_update_id(conn->assignment.fi, conn->fi->id); + return; + } + + osmo_fsm_inst_update_id_f(conn->assignment.fi, "%s_%u-%u-%u-%s%s%s-%u", + conn->fi->id, + new_lchan->ts->trx->bts->nr, new_lchan->ts->trx->nr, new_lchan->ts->nr, + gsm_pchan_id(new_lchan->ts->pchan_on_init), + (new_lchan->ts->pchan_on_init == new_lchan->ts->pchan_is)? "" : "as", + (new_lchan->ts->pchan_on_init == new_lchan->ts->pchan_is)? "" + : gsm_pchan_id(new_lchan->ts->pchan_is), + new_lchan->nr); +} + +static bool lchan_type_compat_with_mode(enum gsm_chan_t type, + enum gsm48_chan_mode chan_mode, int full_rate) +{ + switch (chan_mode) { + case GSM48_CMODE_SIGN: + switch (type) { + case GSM_LCHAN_TCH_F: + case GSM_LCHAN_TCH_H: + case GSM_LCHAN_SDCCH: + return true; + default: + return false; + } + + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_AMR: + case GSM48_CMODE_DATA_3k6: + case GSM48_CMODE_DATA_6k0: + /* these services can all run on TCH/H, but we may have + * an explicit override by the 'full_rate' argument */ + switch (type) { + case GSM_LCHAN_TCH_F: + return full_rate; + case GSM_LCHAN_TCH_H: + return !full_rate; + default: + return false; + } + + case GSM48_CMODE_DATA_12k0: + case GSM48_CMODE_DATA_14k5: + case GSM48_CMODE_SPEECH_EFR: + /* these services all explicitly require a TCH/F */ + return type == GSM_LCHAN_TCH_F; + + default: + return false; + } +} + +void assignment_fsm_init() +{ + OSMO_ASSERT(osmo_fsm_register(&assignment_fsm) == 0); +} + +void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts *bts, + struct assignment_request *req) +{ + struct osmo_fsm_inst *fi; + struct lchan_activate_info info; + + OSMO_ASSERT(conn); + OSMO_ASSERT(conn->fi); + OSMO_ASSERT(!conn->assignment.fi); + OSMO_ASSERT(!conn->assignment.new_lchan); + + assignment_count(BSC_CTR_ASSIGNMENT_ATTEMPTED); + + fi = osmo_fsm_inst_alloc_child(&assignment_fsm, conn->fi, GSCON_EV_ASSIGNMENT_END); + OSMO_ASSERT(fi); + conn->assignment.fi = fi; + fi->priv = conn; + + conn->assignment.req = *req; + + switch (req->chan_mode) { + + case GSM48_CMODE_SPEECH_V1: + case GSM48_CMODE_SPEECH_EFR: + case GSM48_CMODE_SPEECH_AMR: + conn->assignment.requires_voice_stream = true; + /* Select an lchan below. */ + break; + + case GSM48_CMODE_SIGN: + conn->assignment.requires_voice_stream = false; + /* Select an lchan below. */ + break; + + default: + assignment_fail(GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_NOT_SUPP, + "Channel mode not supported: %s", + gsm48_chan_mode_name(req->chan_mode)); + return; + } + + if (conn->lchan + && lchan_type_compat_with_mode(conn->lchan->type, req->chan_mode, req->full_rate)) { + + if (conn->lchan->tch_mode == req->chan_mode) { + /* current lchan suffices and already is in the right mode. We're done. */ + LOG_ASSIGNMENT(conn, LOGL_DEBUG, + "Current lchan is compatible with requested chan_mode," + " sending BSSMAP Assignment Complete directly." + " requested chan_mode=%s; current lchan is %s\n", + gsm48_chan_mode_name(req->chan_mode), + gsm_lchan_name(conn->lchan)); + send_assignment_complete(conn, conn->lchan); + return; + } + + /* FIXME: send Channel Mode Modify to put the current lchan in the right mode, and kick + * off its RTP stream setup code path. See gsm48_lchan_modify() and + * gsm48_rx_rr_modif_ack(), and see lchan_fsm.h LCHAN_EV_CHAN_MODE_MODIF_* */ + LOG_ASSIGNMENT(conn, LOGL_ERROR, + "NOT IMPLEMENTED:" + " Current lchan would be compatible, we should send Channel Mode Modify\n"); + } + + conn->assignment.new_lchan = lchan_select_by_chan_mode(bts, req->chan_mode, req->full_rate); + + if (!conn->assignment.new_lchan) { + assignment_count_result(BSC_CTR_ASSIGNMENT_NO_CHANNEL); + assignment_fail(GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, + "BSSMAP Assignment Command:" + " No lchan available for: chan_mode=%s, full_rate=%i\n", + get_value_string(gsm48_chan_mode_names, req->chan_mode), req->full_rate); + return; + } + + assignment_fsm_update_id(conn); + LOG_ASSIGNMENT(conn, LOGL_INFO, "Starting Assignment: chan_mode=%s, full_rate=%d," + " aoip=%s MSC-rtp=%s:%u\n", + gsm48_chan_mode_name(req->chan_mode), req->full_rate, + req->aoip ? "yes" : "no", req->msc_rtp_addr, req->msc_rtp_port); + + assignment_fsm_state_chg(ASSIGNMENT_ST_WAIT_LCHAN_ACTIVE); + info = (struct lchan_activate_info){ + .activ_for = FOR_ASSIGNMENT, + .for_conn = conn, + .chan_mode = req->chan_mode, + .requires_voice_stream = conn->assignment.requires_voice_stream, + .msc_assigned_cic = req->msc_assigned_cic, + .old_lchan = conn->lchan, + }; + lchan_activate(conn->assignment.new_lchan, &info); +} + +static void assignment_fsm_wait_lchan(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = assignment_fi_conn(fi); + switch (event) { + + case ASSIGNMENT_EV_LCHAN_ACTIVE: + if (data != conn->assignment.new_lchan) + return; + + /* The TS may have changed its pchan_is */ + assignment_fsm_update_id(conn); + + assignment_fsm_state_chg(ASSIGNMENT_ST_WAIT_RR_ASS_COMPLETE); + return; + + default: + OSMO_ASSERT(false); + } +} + +static void assignment_fsm_wait_rr_ass_complete_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + int rc; + struct gsm_subscriber_connection *conn = assignment_fi_conn(fi); + + rc = gsm48_send_rr_ass_cmd(conn->lchan, conn->assignment.new_lchan, + conn->lchan->ms_power); + + if (rc) + assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE, "Unable to send RR Assignment Command"); +} + +static uint8_t get_cause(void *data) +{ + if (data) + return *(uint8_t*)data; + return GSM0808_CAUSE_EQUIPMENT_FAILURE; +} + +static void assignment_fsm_wait_rr_ass_complete(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = assignment_fi_conn(fi); + switch (event) { + + case ASSIGNMENT_EV_RR_ASSIGNMENT_COMPLETE: + assignment_fsm_state_chg(ASSIGNMENT_ST_WAIT_LCHAN_ESTABLISHED); + return; + + case ASSIGNMENT_EV_LCHAN_ESTABLISHED: + /* The lchan is already done with all of its RTP setup. We will notice the lchan state to + * be established in assignment_fsm_wait_lchan_established_onenter(). */ + return; + + case ASSIGNMENT_EV_RR_ASSIGNMENT_FAIL: + assignment_count_result(BSC_CTR_ASSIGNMENT_FAILED); + assignment_fail(get_cause(data), "Rx RR Assignment Failure"); + return; + + default: + OSMO_ASSERT(false); + } +} + +static void assignment_fsm_post_lchan_established(struct osmo_fsm_inst *fi); + +static void assignment_fsm_wait_lchan_established_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_subscriber_connection *conn = assignment_fi_conn(fi); + /* Do we still need to wait for the RTP stream at all? */ + if (lchan_state_is(conn->assignment.new_lchan, LCHAN_ST_ESTABLISHED)) + assignment_fsm_post_lchan_established(fi); +} + +static void assignment_fsm_wait_lchan_established(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + + case ASSIGNMENT_EV_LCHAN_ESTABLISHED: + assignment_fsm_post_lchan_established(fi); + return; + + default: + OSMO_ASSERT(false); + } +} + +static void assignment_fsm_post_lchan_established(struct osmo_fsm_inst *fi) +{ + struct gsm_subscriber_connection *conn = assignment_fi_conn(fi); + if (conn->assignment.requires_voice_stream) + assignment_fsm_state_chg(ASSIGNMENT_ST_WAIT_MGW_ENDPOINT_TO_MSC); + else + assignment_success(conn); +} + +static void assignment_fsm_wait_mgw_endpoint_to_msc_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_subscriber_connection *conn = assignment_fi_conn(fi); + + OSMO_ASSERT(conn->assignment.requires_voice_stream); + + LOG_ASSIGNMENT(conn, LOGL_DEBUG, + "Connecting MGW endpoint to the MSC's RTP port: %s:%u\n", + conn->assignment.req.msc_rtp_addr, + conn->assignment.req.msc_rtp_port); + + if (!gscon_connect_mgw_to_msc(conn, + conn->assignment.new_lchan, + conn->assignment.req.msc_rtp_addr, + conn->assignment.req.msc_rtp_port, + fi, + ASSIGNMENT_EV_MSC_MGW_OK, + ASSIGNMENT_EV_MSC_MGW_FAIL, + NULL, + &conn->assignment.created_ci_for_msc)) { + assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE, + "Unable to connect MGW endpoint to the MSC side"); + return; + } +} + +static void assignment_fsm_wait_mgw_endpoint_to_msc(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = assignment_fi_conn(fi); + switch (event) { + + case ASSIGNMENT_EV_MSC_MGW_OK: + /* For AoIP, we created the MGW endpoint. Ensure it is really there, and log it. */ + if (gscon_is_aoip(conn)) { + const struct mgcp_conn_peer *mgw_info; + mgw_info = mgwep_ci_get_rtp_info(conn->user_plane.mgw_endpoint_ci_msc); + if (!mgw_info) { + assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE, + "Unable to retrieve RTP port info allocated by MGW for" + " the MSC side."); + return; + } + LOG_ASSIGNMENT(conn, LOGL_DEBUG, "MGW's MSC side CI: %s:%u\n", + mgw_info->addr, mgw_info->port); + } + assignment_success(conn); + return; + + case ASSIGNMENT_EV_MSC_MGW_FAIL: + assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE, + "Unable to connect MGW endpoint to the MSC side"); + return; + + default: + OSMO_ASSERT(false); + } +} + +#define S(x) (1 << (x)) + +static const struct osmo_fsm_state assignment_fsm_states[] = { + [ASSIGNMENT_ST_WAIT_LCHAN_ACTIVE] = { + .name = "WAIT_LCHAN_ACTIVE", + .action = assignment_fsm_wait_lchan, + .in_event_mask = 0 + | S(ASSIGNMENT_EV_LCHAN_ACTIVE) + , + .out_state_mask = 0 + | S(ASSIGNMENT_ST_WAIT_LCHAN_ACTIVE) + | S(ASSIGNMENT_ST_WAIT_RR_ASS_COMPLETE) + , + }, + [ASSIGNMENT_ST_WAIT_RR_ASS_COMPLETE] = { + .name = "WAIT_RR_ASS_COMPLETE", + .onenter = assignment_fsm_wait_rr_ass_complete_onenter, + .action = assignment_fsm_wait_rr_ass_complete, + .in_event_mask = 0 + | S(ASSIGNMENT_EV_RR_ASSIGNMENT_COMPLETE) + | S(ASSIGNMENT_EV_RR_ASSIGNMENT_FAIL) + , + .out_state_mask = 0 + | S(ASSIGNMENT_ST_WAIT_LCHAN_ESTABLISHED) + , + }, + [ASSIGNMENT_ST_WAIT_LCHAN_ESTABLISHED] = { + .name = "WAIT_LCHAN_ESTABLISHED", + .onenter = assignment_fsm_wait_lchan_established_onenter, + .action = assignment_fsm_wait_lchan_established, + .in_event_mask = 0 + | S(ASSIGNMENT_EV_LCHAN_ESTABLISHED) + , + .out_state_mask = 0 + | S(ASSIGNMENT_ST_WAIT_MGW_ENDPOINT_TO_MSC) + , + }, + [ASSIGNMENT_ST_WAIT_MGW_ENDPOINT_TO_MSC] = { + .name = "WAIT_MGW_ENDPOINT_TO_MSC", + .onenter = assignment_fsm_wait_mgw_endpoint_to_msc_onenter, + .action = assignment_fsm_wait_mgw_endpoint_to_msc, + .in_event_mask = 0 + | S(ASSIGNMENT_EV_MSC_MGW_OK) + | S(ASSIGNMENT_EV_MSC_MGW_FAIL) + , + }, +}; + +static const struct value_string assignment_fsm_event_names[] = { + OSMO_VALUE_STRING(ASSIGNMENT_EV_LCHAN_ACTIVE), + OSMO_VALUE_STRING(ASSIGNMENT_EV_LCHAN_ESTABLISHED), + OSMO_VALUE_STRING(ASSIGNMENT_EV_LCHAN_ERROR), + OSMO_VALUE_STRING(ASSIGNMENT_EV_MSC_MGW_OK), + OSMO_VALUE_STRING(ASSIGNMENT_EV_MSC_MGW_FAIL), + OSMO_VALUE_STRING(ASSIGNMENT_EV_RR_ASSIGNMENT_COMPLETE), + OSMO_VALUE_STRING(ASSIGNMENT_EV_RR_ASSIGNMENT_FAIL), + OSMO_VALUE_STRING(ASSIGNMENT_EV_CONN_RELEASING), + {} +}; + +void assignment_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct gsm_subscriber_connection *conn = assignment_fi_conn(fi); + switch (event) { + + case ASSIGNMENT_EV_CONN_RELEASING: + assignment_count_result(BSC_CTR_ASSIGNMENT_STOPPED); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, 0); + return; + + case ASSIGNMENT_EV_LCHAN_ERROR: + if (data != conn->assignment.new_lchan) + return; + assignment_fail(conn->assignment.new_lchan->activate.gsm0808_error_cause, + "Failed to activate lchan %s", + gsm_lchan_name(conn->assignment.new_lchan)); + return; + + default: + return; + } +} + +int assignment_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + struct gsm_subscriber_connection *conn = assignment_fi_conn(fi); + assignment_count_result(BSC_CTR_ASSIGNMENT_TIMEOUT); + assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE, "Timeout"); + return 0; +} + +void assignment_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) +{ + struct gsm_subscriber_connection *conn = assignment_fi_conn(fi); + assignment_reset(conn); + conn->assignment.fi = NULL; +} + +static struct osmo_fsm assignment_fsm = { + .name = "assignment", + .states = assignment_fsm_states, + .num_states = ARRAY_SIZE(assignment_fsm_states), + .log_subsys = DRSL, + .event_names = assignment_fsm_event_names, + .allstate_action = assignment_fsm_allstate_action, + .allstate_event_mask = 0 + | S(ASSIGNMENT_EV_CONN_RELEASING) + | S(ASSIGNMENT_EV_LCHAN_ERROR) + , + .timer_cb = assignment_fsm_timer_cb, + .cleanup = assignment_fsm_cleanup, +}; |