diff options
Diffstat (limited to 'src/osmo-bsc/assignment_fsm.c')
-rw-r--r-- | src/osmo-bsc/assignment_fsm.c | 465 |
1 files changed, 334 insertions, 131 deletions
diff --git a/src/osmo-bsc/assignment_fsm.c b/src/osmo-bsc/assignment_fsm.c index fde028ed9..7f6a7ea85 100644 --- a/src/osmo-bsc/assignment_fsm.c +++ b/src/osmo-bsc/assignment_fsm.c @@ -32,15 +32,17 @@ #include <osmocom/bsc/osmo_bsc_lcls.h> #include <osmocom/bsc/bsc_msc_data.h> #include <osmocom/bsc/gsm_08_08.h> +#include <osmocom/bsc/gsm_04_08_rr.h> #include <osmocom/bsc/lchan_select.h> #include <osmocom/bsc/abis_rsl.h> #include <osmocom/bsc/bts.h> - +#include <osmocom/bsc/bsc_stats.h> +#include <osmocom/bsc/lchan.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) +static struct gsm_subscriber_connection *assignment_fi_conn(struct osmo_fsm_inst *fi) { OSMO_ASSERT(fi); OSMO_ASSERT(fi->fsm == &assignment_fsm); @@ -49,10 +51,10 @@ struct gsm_subscriber_connection *assignment_fi_conn(struct osmo_fsm_inst *fi) } static const struct osmo_tdef_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 }, + [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 = -9 }, }; /* Transition to a state, using the T timer defined in assignment_fsm_timeouts. @@ -72,7 +74,7 @@ static const struct osmo_tdef_state_timeout assignment_fsm_timeouts[32] = { osmo_fsm_inst_state_name(fi), gsm0808_cause_name(cause), ## args); \ assignment_count_result(CTR_ASSIGNMENT_ERROR); \ on_assignment_failure(_conn); \ - } while(0) + } while (0) /* Assume presence of local var 'conn' as struct gsm_subscriber_connection */ #define assignment_count(counter) do { \ @@ -80,10 +82,31 @@ static const struct osmo_tdef_state_timeout assignment_fsm_timeouts[32] = { LOG_ASSIGNMENT(conn, LOGL_DEBUG, "incrementing rate counter: %s %s\n", \ bsc_ctr_description[BSC_##counter].name, \ bsc_ctr_description[BSC_##counter].description); \ - rate_ctr_inc(&conn->network->bsc_ctrs->ctr[BSC_##counter]); \ - if (bts) \ - rate_ctr_inc(&bts->bts_ctrs->ctr[BTS_##counter]); \ - } while(0) + rate_ctr_inc(rate_ctr_group_get_ctr(conn->network->bsc_ctrs, BSC_##counter)); \ + if (bts) { \ + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_##counter)); \ + switch (gsm48_chan_mode_to_non_vamos(conn->assignment.req.ch_mode_rate_list[0].chan_mode)) { \ + case GSM48_CMODE_SIGN: \ + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_##counter##_SIGN)); \ + LOG_ASSIGNMENT(conn, LOGL_DEBUG, "incrementing rate counter: bts%u %s %s\n", \ + bts->nr, \ + bts_ctr_description[BTS_##counter##_SIGN].name, \ + bts_ctr_description[BTS_##counter##_SIGN].description); \ + break; \ + case GSM48_CMODE_SPEECH_V1: \ + case GSM48_CMODE_SPEECH_EFR: \ + case GSM48_CMODE_SPEECH_AMR: \ + rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, BTS_##counter##_SPEECH)); \ + LOG_ASSIGNMENT(conn, LOGL_DEBUG, "incrementing rate counter: bts%u %s %s\n", \ + bts->nr, \ + bts_ctr_description[BTS_##counter##_SPEECH].name, \ + bts_ctr_description[BTS_##counter##_SPEECH].description); \ + break; \ + default: \ + break; \ + } \ + } \ + } while (0) #define assignment_count_result(counter) do { \ if (!conn->assignment.result_rate_ctr_done) { \ @@ -94,21 +117,24 @@ static const struct osmo_tdef_state_timeout assignment_fsm_timeouts[32] = { "result rate counter already recorded, NOT counting as: %s %s\n", \ bsc_ctr_description[BSC_##counter].name, \ bsc_ctr_description[BSC_##counter].description); \ - } while(0) + } 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); + lchan_release(lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, NULL); } if (conn->assignment.created_ci_for_msc) { - gscon_forget_mgw_endpoint_ci(conn, conn->assignment.created_ci_for_msc); + /* Store ci pointer locally, because gscon_forget_mgw_endpoint_ci() NULLs + * conn->assignment.created_ci_for_msc. */ + struct osmo_mgcpc_ep_ci *ci = conn->assignment.created_ci_for_msc; + gscon_forget_mgw_endpoint_ci(conn, ci); /* If this is the last endpoint released, the mgw_endpoint_fsm will terminate and tell * the gscon about it. */ - osmo_mgcpc_ep_ci_dlcx(conn->assignment.created_ci_for_msc); + osmo_mgcpc_ep_ci_dlcx(ci); } conn->assignment = (struct assignment_fsm_data){ @@ -118,13 +144,17 @@ void assignment_reset(struct gsm_subscriber_connection *conn) 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 { - rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_ASSIGMENT_FAILURE]); - gscon_sigtran_send(conn, resp); + /* Send Assignment Failure to MSC only when the assignment was requested via BSSAP. Do not send anything to the + * MSC if re-assignment was requested for congestion resolution, for VAMOS multiplexing, or by VTY. */ + if (conn->assignment.req.assign_for == ASSIGN_FOR_BSSMAP_REQ) { + 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 { + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_ASSIGMENT_FAILURE)); + gscon_sigtran_send(conn, resp); + } } /* If assignment failed as early as in assignment_fsm_start(), there may not be an fi yet. */ @@ -155,18 +185,18 @@ static void send_assignment_complete(struct gsm_subscriber_connection *conn) struct gsm_lchan *lchan = conn->lchan; struct osmo_fsm_inst *fi = conn->fi; - chosen_channel = gsm0808_chosen_channel(lchan->type, lchan->tch_mode); + chosen_channel = gsm0808_chosen_channel(lchan->type, lchan->current_ch_mode_rate.chan_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)); + get_value_string(gsm48_chan_mode_names, lchan->current_ch_mode_rate.chan_mode), + gsm_chan_t_name(lchan->type)); return; } /* Generate voice related fields */ if (conn->assignment.requires_voice_stream) { - perm_spch = gsm0808_permitted_speech(lchan->type, lchan->tch_mode); + perm_spch = gsm0808_permitted_speech(lchan->type, lchan->current_ch_mode_rate.chan_mode); if (gscon_is_aoip(conn)) { if (!osmo_mgcpc_ep_ci_get_crcx_info_to_sockaddr(conn->user_plane.mgw_endpoint_ci_msc, @@ -192,14 +222,14 @@ static void send_assignment_complete(struct gsm_subscriber_connection *conn) if (gscon_is_aoip(conn)) { /* Extrapolate speech codec from speech mode */ gsm0808_speech_codec_from_chan_type(&sc, perm_spch); - sc.cfg = conn->lchan->activate.info.s15_s0; + sc.cfg = conn->lchan->current_ch_mode_rate.s15_s0; sc_ptr = ≻ } } resp = gsm0808_create_ass_compl2(lchan->abis_ip.ass_compl.rr_cause, chosen_channel, - lchan->encr.alg_id, perm_spch, + ALG_A5_NR_TO_BSSAP(lchan->encr.alg_a5_n), perm_spch, addr_local_p, sc_ptr, NULL, lcls_get_status(conn)); if (!resp) { @@ -212,7 +242,7 @@ static void send_assignment_complete(struct gsm_subscriber_connection *conn) conn->assignment.req.use_osmux) _gsm0808_ass_compl_extend_osmux(resp, osmux_cid); - rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_ASSIGMENT_COMPLETE]); + rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_ASSIGMENT_COMPLETE)); rc = gscon_sigtran_send(conn, resp); if (rc) { assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE, @@ -224,55 +254,75 @@ static void send_assignment_complete(struct gsm_subscriber_connection *conn) static void assignment_success(struct gsm_subscriber_connection *conn) { - /* Take on the new lchan */ - gscon_change_primary_lchan(conn, conn->assignment.new_lchan); - conn->assignment.new_lchan = NULL; + struct gsm_bts *bts; + bool lchan_changed = (conn->assignment.new_lchan != NULL); + + /* Take on the new lchan. If there only was a Channel Mode Modify, then there is no new lchan to take on. */ + if (lchan_changed) { + gscon_change_primary_lchan(conn, conn->assignment.new_lchan); + + OSMO_ASSERT((bts = conn_get_bts(conn)) != NULL); + if (is_siemens_bts(bts) && ts_is_tch(conn->lchan->ts)) { + /* HACK: store the actual Classmark 2 LV from the subscriber and use it here! */ + uint8_t cm2_lv[] = { 0x02, 0x00, 0x00 }; + send_siemens_mrpci(conn->lchan, cm2_lv); + } - /* apply LCLS configuration (if any) */ - lcls_apply_config(conn); + /* apply LCLS configuration (if any) */ + lcls_apply_config(conn); + } + conn->assignment.new_lchan = NULL; - send_assignment_complete(conn); - /* 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) { - /* The lchan was ready, and we failed to tell the MSC about it. By releasing this lchan, - * the conn will notice that its primary lchan is gone and should clean itself up. */ - lchan_release(conn->lchan, true, true, RSL_ERR_EQUIPMENT_FAIL); - return; + if (conn->assignment.req.assign_for == ASSIGN_FOR_BSSMAP_REQ) { + send_assignment_complete(conn); + /* 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) { + /* The lchan was ready, and we failed to tell the MSC about it. By releasing this lchan, + * the conn will notice that its primary lchan is gone and should clean itself up. */ + lchan_release(conn->lchan, true, true, RSL_ERR_EQUIPMENT_FAIL, + gscon_last_eutran_plmn(conn)); + return; + } } /* 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 */ + /* New RTP information is now accepted. If there is no RTP stream, this information is zero / empty. Either way + * store the result of this assignment. */ 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; + assignment_count_result(CTR_ASSIGNMENT_COMPLETED); + LOG_ASSIGNMENT(conn, LOGL_DEBUG, "Assignment successful\n"); osmo_fsm_inst_term(conn->assignment.fi, OSMO_FSM_TERM_REGULAR, 0); - - assignment_count_result(CTR_ASSIGNMENT_COMPLETED); } -static void assignment_fsm_update_id(struct gsm_subscriber_connection *conn) +void assignment_fsm_update_id(struct gsm_subscriber_connection *conn) { - struct gsm_lchan *new_lchan = conn->assignment.new_lchan; + /* Assignment can do a new channel activation, in which case new_lchan points at the new lchan. + * Or assignment can Channel Mode Modify the already used lchan, in which case new_lchan == NULL. */ + struct gsm_lchan *new_lchan = (conn->assignment.new_lchan ? : conn->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); + osmo_fsm_inst_update_id_f_sanitize(conn->assignment.fi, '_', "%s_%u-%u-%u-%s%s%s-%s%u", + conn->fi->id, + new_lchan->ts->trx->bts->nr, new_lchan->ts->trx->nr, new_lchan->ts->nr, + gsm_pchan_name(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_name(new_lchan->ts->pchan_is), + new_lchan->vamos.is_secondary ? "shadow" : "", + new_lchan->nr - (new_lchan->vamos.is_secondary ? + new_lchan->ts->max_primary_lchans : 0)); } static bool lchan_type_compat_with_mode(enum gsm_chan_t type, const struct channel_mode_and_rate *ch_mode_rate) @@ -280,7 +330,7 @@ static bool lchan_type_compat_with_mode(enum gsm_chan_t type, const struct chann enum gsm48_chan_mode chan_mode = ch_mode_rate->chan_mode; enum channel_rate chan_rate = ch_mode_rate->chan_rate; - switch (chan_mode) { + switch (gsm48_chan_mode_to_non_vamos(chan_mode)) { case GSM48_CMODE_SIGN: switch (type) { case GSM_LCHAN_TCH_F: return chan_rate == CH_RATE_FULL; @@ -315,7 +365,7 @@ static bool lchan_type_compat_with_mode(enum gsm_chan_t type, const struct chann } } -void assignment_fsm_init() +static __attribute__((constructor)) void assignment_fsm_init(void) { OSMO_ASSERT(osmo_fsm_register(&assignment_fsm) == 0); } @@ -324,7 +374,7 @@ static int check_requires_voice(bool *requires_voice, enum gsm48_chan_mode chan_ { *requires_voice = false; - switch (chan_mode) { + switch (gsm48_chan_mode_to_non_vamos(chan_mode)) { case GSM48_CMODE_SPEECH_V1: case GSM48_CMODE_SPEECH_EFR: case GSM48_CMODE_SPEECH_AMR: @@ -340,7 +390,7 @@ static int check_requires_voice(bool *requires_voice, enum gsm48_chan_mode chan_ return 0; } -/* Check if the incoming assignment requests requires a voice stream or not, +/* Check if the incoming assignment request requires a voice stream or not, * we will look at the preferred and the alternate channel mode and also make * sure that both are consistent. */ static int check_requires_voice_stream(struct gsm_subscriber_connection *conn) @@ -356,11 +406,11 @@ static int check_requires_voice_stream(struct gsm_subscriber_connection *conn) * a mismatch is not permitted */ for (i = 0; i < req->n_ch_mode_rate; i++) { - rc = check_requires_voice(&requires_voice_alt, req->ch_mode_rate[i].chan_mode); + rc = check_requires_voice(&requires_voice_alt, req->ch_mode_rate_list[i].chan_mode); if (rc < 0) { assignment_fail(GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_NOT_SUPP, "Channel mode not supported (prev level %d): %s", i, - gsm48_chan_mode_name(req->ch_mode_rate[i].chan_mode)); + gsm48_chan_mode_name(req->ch_mode_rate_list[i].chan_mode)); return -EINVAL; } @@ -368,9 +418,9 @@ static int check_requires_voice_stream(struct gsm_subscriber_connection *conn) requires_voice_pref = requires_voice_alt; else if (requires_voice_alt != requires_voice_pref) { assignment_fail(GSM0808_CAUSE_REQ_CODEC_TYPE_OR_CONFIG_NOT_SUPP, - "Inconsistent channel modes: %s != %s", - gsm48_chan_mode_name(req->ch_mode_rate[0].chan_mode), - gsm48_chan_mode_name(req->ch_mode_rate[i].chan_mode)); + "Requested a mix of Signalling and non-Signalling channel modes: %s != %s", + gsm48_chan_mode_name(req->ch_mode_rate_list[0].chan_mode), + gsm48_chan_mode_name(req->ch_mode_rate_list[i].chan_mode)); return -EINVAL; } } @@ -391,15 +441,67 @@ static bool reuse_existing_lchan(struct gsm_subscriber_connection *conn) /* Check if the currently existing lchan is compatible with the * preferred rate/codec. */ - for (i = 0; i < req->n_ch_mode_rate; i++) - if (lchan_type_compat_with_mode(conn->lchan->type, &req->ch_mode_rate[i])) { - conn->lchan->ch_mode_rate = req->ch_mode_rate[i]; - return true; - } + for (i = 0; i < req->n_ch_mode_rate; i++) { + if (!lchan_type_compat_with_mode(conn->lchan->type, &req->ch_mode_rate_list[i])) + continue; + conn->assignment.selected_ch_mode_rate = req->ch_mode_rate_list[i]; + return true; + } return false; } +static int _reassignment_request(enum assign_for assign_for, struct gsm_lchan *lchan, struct gsm_lchan *to_lchan, + enum gsm_chan_t new_lchan_type, int tsc_set, int tsc) +{ + struct gsm_subscriber_connection *conn = lchan->conn; + struct assignment_request req = { + .assign_for = assign_for, + .aoip = gscon_is_aoip(conn), + .msc_assigned_cic = conn->user_plane.msc_assigned_cic, + .msc_rtp_port = conn->user_plane.msc_assigned_rtp_port, + .n_ch_mode_rate = 1, + .ch_mode_rate_list = { lchan->current_ch_mode_rate }, + .target_lchan = to_lchan, + .tsc_set = { + .present = (tsc_set >= 0), + .val = tsc_set, + }, + .tsc = { + .present = (tsc >= 0), + .val = tsc, + }, + }; + + if (to_lchan) + new_lchan_type = to_lchan->type; + req.ch_mode_rate_list[0].chan_rate = chan_t_to_chan_rate(new_lchan_type); + /* lchan activation will automatically convert chan_mode to a VAMOS equivalent if required. + * So rather always pass the plain non-VAMOS mode. */ + req.ch_mode_rate_list[0].chan_mode = gsm48_chan_mode_to_non_vamos(lchan->current_ch_mode_rate.chan_mode); + + OSMO_STRLCPY_ARRAY(req.msc_rtp_addr, conn->user_plane.msc_assigned_rtp_addr); + + if (conn->user_plane.mgw_endpoint_ci_msc) { + req.use_osmux = osmo_mgcpc_ep_ci_get_crcx_info_to_osmux_cid(conn->user_plane.mgw_endpoint_ci_msc, + &req.osmux_cid); + } + + return osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_ASSIGNMENT_START, &req); +} + +int reassignment_request_to_lchan(enum assign_for assign_for, struct gsm_lchan *lchan, struct gsm_lchan *to_lchan, + int tsc_set, int tsc) +{ + return _reassignment_request(assign_for, lchan, to_lchan, 0, tsc_set, tsc); +} + +int reassignment_request_to_chan_type(enum assign_for assign_for, struct gsm_lchan *lchan, + enum gsm_chan_t new_lchan_type) +{ + return _reassignment_request(assign_for, lchan, NULL, new_lchan_type, -1, -1); +} + void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts *bts, struct assignment_request *req) { @@ -409,7 +511,6 @@ void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts [CH_RATE_FULL] = "FR", }; struct osmo_fsm_inst *fi; - struct lchan_activate_info info; int i; OSMO_ASSERT(conn); @@ -417,8 +518,6 @@ void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts OSMO_ASSERT(!conn->assignment.fi); OSMO_ASSERT(!conn->assignment.new_lchan); - assignment_count(CTR_ASSIGNMENT_ATTEMPTED); - fi = osmo_fsm_inst_alloc_child(&assignment_fsm, conn->fi, GSCON_EV_ASSIGNMENT_END); OSMO_ASSERT(fi); conn->assignment.fi = fi; @@ -428,26 +527,29 @@ void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts conn->assignment.req = *req; req = &conn->assignment.req; + assignment_count(CTR_ASSIGNMENT_ATTEMPTED); + /* Check if we need a voice stream. If yes, set the appropriate struct * members in conn */ if (check_requires_voice_stream(conn) < 0) return; - /* There may be an already existing lchan, if yes, try to work with - * the existing lchan. */ - if (reuse_existing_lchan(conn)) { + if (!req->target_lchan && reuse_existing_lchan(conn)) { + /* The already existing lchan is suitable for this mode */ + conn->assignment.new_lchan = NULL; /* If the requested mode and the current TCH mode matches up, just send the * assignment complete directly and be done with the assignment procedure. */ - if (conn->lchan->tch_mode == conn->lchan->ch_mode_rate.chan_mode) { + if (conn->lchan->current_ch_mode_rate.chan_mode == conn->assignment.selected_ch_mode_rate.chan_mode) { LOG_ASSIGNMENT(conn, LOGL_DEBUG, "Current lchan mode is compatible with requested chan_mode," " sending BSSMAP Assignment Complete directly." " requested chan_mode=%s; current lchan is %s\n", - gsm48_chan_mode_name(conn->lchan->ch_mode_rate.chan_mode), + gsm48_chan_mode_name(conn->assignment.selected_ch_mode_rate.chan_mode), gsm_lchan_name(conn->lchan)); - send_assignment_complete(conn); + if (req->assign_for == ASSIGN_FOR_BSSMAP_REQ) + send_assignment_complete(conn); /* If something went wrong during send_assignment_complete(), * the fi will be gone from error handling in there. */ if (conn->assignment.fi) { @@ -462,39 +564,60 @@ void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts LOG_ASSIGNMENT(conn, LOGL_DEBUG, "Current lchan mode is not compatible with requested chan_mode," " so we will modify it. requested chan_mode=%s; current lchan is %s\n", - gsm48_chan_mode_name(conn->lchan->ch_mode_rate.chan_mode), + gsm48_chan_mode_name(conn->assignment.selected_ch_mode_rate.chan_mode), gsm_lchan_name(conn->lchan)); - info = (struct lchan_activate_info){ - .activ_for = FOR_ASSIGNMENT, - .for_conn = conn, - .chan_mode = conn->lchan->ch_mode_rate.chan_mode, - .encr = conn->lchan->encr, - .s15_s0 = conn->lchan->ch_mode_rate.s15_s0, - .requires_voice_stream = conn->assignment.requires_voice_stream, - .msc_assigned_cic = req->msc_assigned_cic, - .re_use_mgw_endpoint_from_lchan = conn->lchan, - }; - - osmo_fsm_inst_dispatch(conn->lchan->fi, LCHAN_EV_REQUEST_MODE_MODIFY, &info); - - /* Since we opted not to allocate a new lchan, the new lchan is still the old lchan. */ - conn->assignment.new_lchan = conn->lchan; - - /* Also we need to skip the RR assignment, so we jump forward and wait for the lchan_fsm until it - * reaches the established state again. */ - assignment_fsm_state_chg(ASSIGNMENT_ST_WAIT_LCHAN_ESTABLISHED); - + assignment_fsm_state_chg(ASSIGNMENT_ST_WAIT_LCHAN_MODIFIED); return; } - /* Try to allocate a new lchan in order of preference */ - for (i = 0; i < req->n_ch_mode_rate; i++) { - conn->assignment.new_lchan = lchan_select_by_chan_mode(bts, - req->ch_mode_rate[i].chan_mode, req->ch_mode_rate[i].chan_rate); - conn->lchan->ch_mode_rate = req->ch_mode_rate[i]; - if (conn->assignment.new_lchan) + if (req->target_lchan) { + bool matching_mode; + + /* The caller already picked a target lchan to assign to. No need to try re-using the current lchan or + * picking a new one. */ + if (!lchan_state_is(req->target_lchan, LCHAN_ST_UNUSED)) { + assignment_fail(GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, + "Assignment to lchan %s requested, but lchan is already in use (state=%s)\n", + gsm_lchan_name(req->target_lchan), + osmo_fsm_inst_state_name(req->target_lchan->fi)); + return; + } + + conn->assignment.new_lchan = req->target_lchan; + matching_mode = false; + for (i = 0; i < req->n_ch_mode_rate; i++) { + if (!lchan_type_compat_with_mode(conn->assignment.new_lchan->type, &req->ch_mode_rate_list[i])) + continue; + conn->assignment.selected_ch_mode_rate = req->ch_mode_rate_list[i]; + matching_mode = true; + } + if (!matching_mode) { + OSMO_ASSERT(conn->lchan); + assignment_fail(GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, + "Assignment of lchan %s to %s type %s requested, but lchan is not compatible", + gsm_lchan_name(conn->lchan), + gsm_lchan_name(req->target_lchan), + gsm_chan_t_name(conn->assignment.new_lchan->type)); + return; + } + } else { + /* Try to allocate a new lchan in order of preference */ + for (i = 0; i < req->n_ch_mode_rate; i++) { + conn->assignment.new_lchan = lchan_select_by_chan_mode(bts, + req->ch_mode_rate_list[i].chan_mode, + req->ch_mode_rate_list[i].chan_rate, + SELECT_FOR_ASSIGNMENT, conn->lchan); + if (!conn->assignment.new_lchan) + continue; + LOG_ASSIGNMENT(conn, LOGL_DEBUG, "selected new lchan %s for mode[%d] = %s channel_rate=%d\n", + gsm_lchan_name(conn->assignment.new_lchan), + i, gsm48_chan_mode_name(req->ch_mode_rate_list[i].chan_mode), + req->ch_mode_rate_list[i].chan_rate); + + conn->assignment.selected_ch_mode_rate = req->ch_mode_rate_list[i]; break; + } } /* Check whether the lchan allocation was successful or not and tear @@ -503,13 +626,13 @@ void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts assignment_count_result(CTR_ASSIGNMENT_NO_CHANNEL); assignment_fail(GSM0808_CAUSE_NO_RADIO_RESOURCE_AVAILABLE, "BSSMAP Assignment Command:" - " No lchan available for: pref=%s:%s / alt1=%s:%s / alt2=%s:%s\n", - gsm48_chan_mode_name(req->ch_mode_rate[0].chan_mode), - rate_names[req->ch_mode_rate[0].chan_rate], - req->n_ch_mode_rate >= 1 ? gsm48_chan_mode_name(req->ch_mode_rate[0].chan_mode) : "", - req->n_ch_mode_rate >= 1 ? rate_names[req->ch_mode_rate[0].chan_rate] : "", - req->n_ch_mode_rate >= 2 ? gsm48_chan_mode_name(req->ch_mode_rate[0].chan_mode) : "", - req->n_ch_mode_rate >= 2 ? rate_names[req->ch_mode_rate[0].chan_rate] : "" + " No lchan available for: pref=%s:%s / alt1=%s:%s / alt2=%s:%s", + gsm48_chan_mode_name(req->ch_mode_rate_list[0].chan_mode), + rate_names[req->ch_mode_rate_list[0].chan_rate], + req->n_ch_mode_rate > 1 ? gsm48_chan_mode_name(req->ch_mode_rate_list[1].chan_mode) : "", + req->n_ch_mode_rate > 1 ? rate_names[req->ch_mode_rate_list[1].chan_rate] : "", + req->n_ch_mode_rate > 2 ? gsm48_chan_mode_name(req->ch_mode_rate_list[2].chan_mode) : "", + req->n_ch_mode_rate > 2 ? rate_names[req->ch_mode_rate_list[2].chan_rate] : "" ); return; } @@ -517,33 +640,46 @@ void assignment_fsm_start(struct gsm_subscriber_connection *conn, struct gsm_bts assignment_fsm_update_id(conn); LOG_ASSIGNMENT(conn, LOGL_INFO, "Starting Assignment: chan_mode=%s, chan_type=%s," " aoip=%s MSC-rtp=%s:%u (osmux=%s)\n", - gsm48_chan_mode_name(conn->lchan->ch_mode_rate.chan_mode), - rate_names[conn->lchan->ch_mode_rate.chan_rate], + gsm48_chan_mode_name(conn->assignment.selected_ch_mode_rate.chan_mode), + rate_names[conn->assignment.selected_ch_mode_rate.chan_rate], req->aoip ? "yes" : "no", req->msc_rtp_addr, req->msc_rtp_port, req->use_osmux ? "yes" : "no"); assignment_fsm_state_chg(ASSIGNMENT_ST_WAIT_LCHAN_ACTIVE); - info = (struct lchan_activate_info){ - .activ_for = FOR_ASSIGNMENT, +} + +static void assignment_fsm_wait_lchan_active_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_subscriber_connection *conn = assignment_fi_conn(fi); + struct assignment_request *req = &conn->assignment.req; + struct lchan_activate_info activ_info = { + .activ_for = ACTIVATE_FOR_ASSIGNMENT, .for_conn = conn, - .chan_mode = conn->lchan->ch_mode_rate.chan_mode, + .ch_mode_rate = conn->assignment.selected_ch_mode_rate, .encr = conn->lchan->encr, - .s15_s0 = conn->lchan->ch_mode_rate.s15_s0, .requires_voice_stream = conn->assignment.requires_voice_stream, .msc_assigned_cic = req->msc_assigned_cic, .re_use_mgw_endpoint_from_lchan = conn->lchan, + .ta = conn->lchan->last_ta, + .ta_known = true, + .tsc_set = req->tsc_set, + .tsc = req->tsc, + .vamos = conn->assignment.new_lchan->vamos.is_secondary, }; - lchan_activate(conn->assignment.new_lchan, &info); + lchan_activate(conn->assignment.new_lchan, &activ_info); } -static void assignment_fsm_wait_lchan(struct osmo_fsm_inst *fi, uint32_t event, void *data) +static void assignment_fsm_wait_lchan_active(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) + if (data != conn->assignment.new_lchan) { + LOG_ASSIGNMENT(conn, LOGL_ERROR, "Some unrelated lchan was activated, ignoring: %s\n", + gsm_lchan_name(data)); return; + } /* The TS may have changed its pchan_is */ assignment_fsm_update_id(conn); @@ -561,6 +697,15 @@ static void assignment_fsm_wait_rr_ass_complete_onenter(struct osmo_fsm_inst *fi int rc; struct gsm_subscriber_connection *conn = assignment_fi_conn(fi); + /* There may be situations where the SDCCH gets released while the TCH is still being activated. We will then + * receive ChanActivAck message from the BTS when the TCH is ready. Since the SDCCH is already released by + * then conn->lchan will be NULL in this case. */ + if (!conn->lchan) { + assignment_fail(GSM0808_CAUSE_EQUIPMENT_FAILURE, + "Unable to send RR Assignment Command: conn without lchan"); + return; + } + rc = gsm48_send_rr_ass_cmd(conn->lchan, conn->assignment.new_lchan, conn->lchan->ms_power); @@ -645,8 +790,10 @@ static void assignment_fsm_wait_mgw_endpoint_to_msc_onenter(struct osmo_fsm_inst conn->assignment.req.msc_rtp_addr, conn->assignment.req.msc_rtp_port); + /* Assignment can do a new channel activation, in which case new_lchan points at the new lchan. + * Or assignment can Channel Mode Modify the already used lchan, in which case new_lchan == NULL. */ if (!gscon_connect_mgw_to_msc(conn, - conn->assignment.new_lchan, + conn->assignment.new_lchan ? : conn->lchan, conn->assignment.req.msc_rtp_addr, conn->assignment.req.msc_rtp_port, fi, @@ -692,19 +839,57 @@ static void assignment_fsm_wait_mgw_endpoint_to_msc(struct osmo_fsm_inst *fi, ui } } +static void assignment_fsm_wait_lchan_modified_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct gsm_subscriber_connection *conn = assignment_fi_conn(fi); + struct gsm_lchan *lchan = conn->lchan; + struct assignment_request *req = &conn->assignment.req; + struct lchan_modify_info modif_info = { + .modify_for = MODIFY_FOR_ASSIGNMENT, + .ch_mode_rate = conn->assignment.selected_ch_mode_rate, + .requires_voice_stream = conn->assignment.requires_voice_stream, + .msc_assigned_cic = req->msc_assigned_cic, + /* keep previous training sequence code. TSC is always present, TSC Set may or may not be an explicit + * value. */ + .tsc_set = { + .present = (lchan->tsc_set >= 0), + .val = lchan->tsc_set, + }, + .tsc = { + .present = true, + .val = lchan->tsc, + }, + }; + lchan_mode_modify(lchan, &modif_info); +} + +static void assignment_fsm_wait_lchan_modified(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + + case ASSIGNMENT_EV_LCHAN_MODIFIED: + assignment_fsm_post_lchan_established(fi); + 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, + .onenter = assignment_fsm_wait_lchan_active_onenter, + .action = assignment_fsm_wait_lchan_active, .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) - | S(ASSIGNMENT_ST_WAIT_LCHAN_ESTABLISHED) /* MODE MODIFY */ + | S(ASSIGNMENT_ST_WAIT_LCHAN_MODIFIED) , }, [ASSIGNMENT_ST_WAIT_RR_ASS_COMPLETE] = { @@ -740,11 +925,23 @@ static const struct osmo_fsm_state assignment_fsm_states[] = { | S(ASSIGNMENT_EV_MSC_MGW_FAIL) , }, + [ASSIGNMENT_ST_WAIT_LCHAN_MODIFIED] = { + .name = "WAIT_LCHAN_MODIFIED", + .onenter = assignment_fsm_wait_lchan_modified_onenter, + .action = assignment_fsm_wait_lchan_modified, + .in_event_mask = 0 + | S(ASSIGNMENT_EV_LCHAN_MODIFIED) + , + .out_state_mask = 0 + | S(ASSIGNMENT_ST_WAIT_MGW_ENDPOINT_TO_MSC) + , + }, }; 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_MODIFIED), OSMO_VALUE_STRING(ASSIGNMENT_EV_LCHAN_ERROR), OSMO_VALUE_STRING(ASSIGNMENT_EV_MSC_MGW_OK), OSMO_VALUE_STRING(ASSIGNMENT_EV_MSC_MGW_FAIL), @@ -754,9 +951,14 @@ static const struct value_string assignment_fsm_event_names[] = { {} }; -void assignment_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) +static void assignment_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct gsm_subscriber_connection *conn = assignment_fi_conn(fi); + + /* Assignment can do a new channel activation, in which case new_lchan points at the new lchan. + * Or assignment can Channel Mode Modify the already used lchan, in which case new_lchan == NULL. */ + struct gsm_lchan *new_lchan = conn->assignment.new_lchan ? : conn->lchan; + switch (event) { case ASSIGNMENT_EV_CONN_RELEASING: @@ -765,11 +967,12 @@ void assignment_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, vo return; case ASSIGNMENT_EV_LCHAN_ERROR: - if (data != conn->assignment.new_lchan) + if (data != 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)); + assignment_fail(new_lchan->activate.gsm0808_error_cause, + "Failed to %s lchan %s", + conn->assignment.new_lchan ? "activate" : "modify", + gsm_lchan_name(new_lchan)); return; default: @@ -777,7 +980,7 @@ void assignment_fsm_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, vo } } -int assignment_fsm_timer_cb(struct osmo_fsm_inst *fi) +static int assignment_fsm_timer_cb(struct osmo_fsm_inst *fi) { struct gsm_subscriber_connection *conn = assignment_fi_conn(fi); assignment_count_result(CTR_ASSIGNMENT_TIMEOUT); @@ -785,7 +988,7 @@ int assignment_fsm_timer_cb(struct osmo_fsm_inst *fi) return 0; } -void assignment_fsm_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause) +static 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); |