aboutsummaryrefslogtreecommitdiffstats
path: root/src/osmo-bsc/assignment_fsm.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/osmo-bsc/assignment_fsm.c')
-rw-r--r--src/osmo-bsc/assignment_fsm.c465
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 = &sc;
}
}
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);