aboutsummaryrefslogtreecommitdiffstats
path: root/src/osmo-bsc/handover_fsm.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/osmo-bsc/handover_fsm.c')
-rw-r--r--src/osmo-bsc/handover_fsm.c271
1 files changed, 208 insertions, 63 deletions
diff --git a/src/osmo-bsc/handover_fsm.c b/src/osmo-bsc/handover_fsm.c
index 573f249a1..e8581c587 100644
--- a/src/osmo-bsc/handover_fsm.c
+++ b/src/osmo-bsc/handover_fsm.c
@@ -45,6 +45,8 @@
#include <osmocom/bsc/gsm_08_08.h>
#include <osmocom/bsc/bts.h>
#include <osmocom/bsc/lcs_loc_req.h>
+#include <osmocom/bsc/bsc_stats.h>
+#include <osmocom/bsc/lchan.h>
#define LOG_FMT_BTS "bts %u lac-ci %u-%u arfcn-bsic %d-%d"
#define LOG_ARGS_BTS(bts) \
@@ -59,9 +61,9 @@
lchan ? lchan->ts->trx->bts->nr : 0, \
lchan ? lchan->ts->trx->nr : 0, \
lchan ? lchan->ts->nr : 0, \
- lchan ? gsm_lchant_name(lchan->type) : "?", \
+ lchan ? gsm_chan_t_name(lchan->type) : "?", \
lchan ? lchan->nr : 0, \
- lchan ? gsm48_chan_mode_name(lchan->tch_mode) : "?"
+ lchan ? gsm48_chan_mode_name(lchan->current_ch_mode_rate.chan_mode) : "?"
#define LOG_FMT_TO_LCHAN "%u-%u-%u-%s%s%s-%u"
#define LOG_ARGS_TO_LCHAN(lchan) \
@@ -88,8 +90,8 @@
LOG_HO(conn, LOGL_DEBUG, "(BSC) 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)
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->network->bsc_ctrs, counter)); \
+ } while (0)
/* Assume presence of local var 'conn' as struct gsm_subscriber_connection.
* Handles bts == NULL gracefully
@@ -102,15 +104,18 @@
bts_ctr_description[counter].name, \
bts_ctr_description[counter].description); \
if (bts) \
- rate_ctr_inc(&bts->bts_ctrs->ctr[counter]); \
+ rate_ctr_inc(rate_ctr_group_get_ctr(bts->bts_ctrs, counter)); \
else \
- rate_ctr_inc(&conn->network->bts_unknown_ctrs->ctr[counter]); \
- } while(0)
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->network->bts_unknown_ctrs, counter)); \
+ } while (0)
+/* Count handover result on both bts and bsc level.
+ * Call with 'counter' being the counter name without the "BSC_"/"BTS_" part,
+ * e.g. ho_count(conn_get_bts(conn), CTR_HANDOVER_ATTEMPTED); */
#define ho_count(bts, counter) do { \
- ho_count_bsc(BSC_##counter); \
- ho_count_bts(bts, BTS_##counter); \
-} while(0)
+ ho_count_bsc(BSC_##counter); \
+ ho_count_bts(bts, BTS_##counter); \
+ } while (0)
static uint8_t g_next_ho_ref = 1;
@@ -134,7 +139,7 @@ const char *handover_status(struct gsm_subscriber_connection *conn)
"("LOG_FMT_FROM_LCHAN") --HO-> ("LOG_FMT_BTS",%s) " LOG_FMT_HO_SCOPE,
LOG_ARGS_FROM_LCHAN(conn->lchan),
LOG_ARGS_BTS(ho->new_bts),
- gsm_lchant_name(ho->new_lchan_type),
+ gsm_chan_t_name(ho->new_lchan_type),
LOG_ARGS_HO_SCOPE(conn));
else
snprintf(buf, sizeof(buf),
@@ -146,7 +151,7 @@ const char *handover_status(struct gsm_subscriber_connection *conn)
snprintf(buf, sizeof(buf),
"("LOG_FMT_FROM_LCHAN") --HO-> (%s) " LOG_FMT_HO_SCOPE,
LOG_ARGS_FROM_LCHAN(conn->lchan),
- neighbor_ident_key_name(&ho->target_cell),
+ cell_ab_to_str_c(OTC_SELECT, &ho->target_cell_ab),
LOG_ARGS_HO_SCOPE(conn));
else if (ho->scope & HO_INTER_BSC_IN) {
@@ -163,14 +168,14 @@ const char *handover_status(struct gsm_subscriber_connection *conn)
ho->inter_bsc_in.cell_id_serving_name,
ho->inter_bsc_in.cell_id_target_name,
LOG_ARGS_BTS(ho->new_bts),
- gsm_lchant_name(ho->new_lchan_type),
+ gsm_chan_t_name(ho->new_lchan_type),
LOG_ARGS_HO_SCOPE(conn));
else
snprintf(buf, sizeof(buf),
"(remote:%s) --HO-> (local:%s,%s) " LOG_FMT_HO_SCOPE,
ho->inter_bsc_in.cell_id_serving_name,
ho->inter_bsc_in.cell_id_target_name,
- gsm_lchant_name(ho->new_lchan_type),
+ gsm_chan_t_name(ho->new_lchan_type),
LOG_ARGS_HO_SCOPE(conn));
} else
snprintf(buf, sizeof(buf), LOG_FMT_HO_SCOPE, LOG_ARGS_HO_SCOPE(conn));
@@ -188,11 +193,11 @@ struct gsm_subscriber_connection *ho_fi_conn(struct osmo_fsm_inst *fi)
}
static const struct osmo_tdef_state_timeout ho_fsm_timeouts[32] = {
- [HO_ST_WAIT_LCHAN_ACTIVE] = { .T = 23042 },
- [HO_ST_WAIT_MGW_ENDPOINT_TO_MSC] = { .T = 23042 },
- [HO_ST_WAIT_RR_HO_DETECT] = { .T = 23042 },
- [HO_ST_WAIT_RR_HO_COMPLETE] = { .T = 23042 },
- [HO_ST_WAIT_LCHAN_ESTABLISHED] = { .T = 23042 },
+ [HO_ST_WAIT_LCHAN_ACTIVE] = { /* Guarded by X5 + X6 in lchan_fsm_timeouts */ },
+ [HO_ST_WAIT_MGW_ENDPOINT_TO_MSC] = { .T = -9 },
+ [HO_ST_WAIT_RR_HO_DETECT] = { .T = 3103 },
+ [HO_ST_WAIT_RR_HO_COMPLETE] = { .keep_timer = true }, /* Keep T3103 */
+ [HO_ST_WAIT_LCHAN_ESTABLISHED] = { /* Guarded by T3101 in lchan_fsm_timeouts */ },
[HO_OUT_ST_WAIT_HO_COMMAND] = { .T = 7 },
[HO_OUT_ST_WAIT_CLEAR] = { .T = 8 },
};
@@ -211,15 +216,15 @@ static const struct osmo_tdef_state_timeout ho_fsm_timeouts[32] = {
LOG_HO(conn, LOGL_ERROR, "Handover failed in state %s, %s: " fmt "\n", \
osmo_fsm_inst_state_name(conn->fi), handover_result_name(result), ## args); \
handover_end(conn, result); \
- } while(0)
+ } while (0)
#define ho_success() do { \
LOG_HO(conn, LOGL_DEBUG, "Handover succeeded\n"); \
handover_end(conn, HO_RESULT_OK); \
- } while(0)
+ } while (0)
/* issue handover to a cell identified by ARFCN and BSIC */
-void handover_request(struct handover_out_req *req)
+int handover_request(struct handover_out_req *req)
{
struct gsm_subscriber_connection *conn;
OSMO_ASSERT(req->old_lchan);
@@ -227,12 +232,9 @@ void handover_request(struct handover_out_req *req)
conn = req->old_lchan->conn;
OSMO_ASSERT(conn && conn->fi);
- /* Make sure the handover target neighbor_ident_key contains the correct source bts nr */
- req->target_nik.from_bts = req->old_lchan->ts->trx->bts->nr;
-
/* To make sure we're allowed to start a handover, go through a gscon event dispatch. If that is accepted, the
* same req is passed to handover_start(). */
- osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_HANDOVER_START, req);
+ return osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_HANDOVER_START, req);
}
/* Check that ho has old_lchan and/or new_lchan and conn pointers match.
@@ -275,7 +277,8 @@ static void handover_reset(struct gsm_subscriber_connection *conn)
struct osmo_mgcpc_ep_ci *ci;
if (conn->ho.new_lchan)
/* New lchan was activated but never passed to a conn */
- lchan_release(conn->ho.new_lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
+ lchan_release(conn->ho.new_lchan, false, true, RSL_ERR_EQUIPMENT_FAIL,
+ NULL);
ci = conn->ho.created_ci_for_msc;
if (ci) {
@@ -290,7 +293,7 @@ static void handover_reset(struct gsm_subscriber_connection *conn)
};
}
-void handover_fsm_init()
+static __attribute__((constructor)) void handover_fsm_init(void)
{
OSMO_ASSERT(osmo_fsm_register(&ho_fsm) == 0);
}
@@ -315,10 +318,10 @@ void handover_start(struct handover_out_req *req)
OSMO_ASSERT(req && req->old_lchan && req->old_lchan->conn);
struct gsm_subscriber_connection *conn = req->old_lchan->conn;
- const struct neighbor_ident_key *search_for = &req->target_nik;
+ const struct cell_ab *search_for = &req->target_cell_ab;
struct handover *ho = &conn->ho;
struct gsm_bts *local_target_cell = NULL;
- const struct gsm0808_cell_id_list2 *remote_target_cell = NULL;
+ struct gsm0808_cell_id_list2 remote_target_cells = {};
if (conn->ho.fi) {
LOG_HO(conn, LOGL_ERROR, "Handover requested while another handover is ongoing; Ignore\n");
@@ -335,9 +338,9 @@ void handover_start(struct handover_out_req *req)
ho->from_hodec_id = req->from_hodec_id;
ho->new_lchan_type = req->new_lchan_type == GSM_LCHAN_NONE ?
req->old_lchan->type : req->new_lchan_type;
- ho->target_cell = req->target_nik;
+ ho->target_cell_ab = req->target_cell_ab;
- if (find_handover_target_cell(&local_target_cell, &remote_target_cell,
+ if (find_handover_target_cell(&local_target_cell, &remote_target_cells,
conn, search_for, true)) {
handover_end(conn, HO_RESULT_ERROR);
return;
@@ -349,8 +352,8 @@ void handover_start(struct handover_out_req *req)
return;
}
- if (remote_target_cell) {
- handover_start_inter_bsc_out(conn, remote_target_cell);
+ if (remote_target_cells.id_list_len) {
+ handover_start_inter_bsc_out(conn, &remote_target_cells);
return;
}
@@ -375,8 +378,12 @@ static void handover_start_intra_bsc(struct gsm_subscriber_connection *conn)
ho->scope = (ho->new_bts == bts) ? HO_INTRA_CELL : HO_INTRA_BSC;
ho->ho_ref = g_next_ho_ref++;
ho->async = true;
+ gsm_bts_cell_id_list(&ho->target_cell_ids, ho->new_bts);
- ho->new_lchan = lchan_select_by_type(ho->new_bts, ho->new_lchan_type);
+ ho->new_lchan = lchan_select_by_type(ho->new_bts,
+ ho->new_lchan_type,
+ SELECT_FOR_HANDOVER,
+ NULL);
if (ho->scope & HO_INTRA_CELL) {
ho_count(bts, CTR_INTRA_CELL_HO_ATTEMPTED);
@@ -384,12 +391,13 @@ static void handover_start_intra_bsc(struct gsm_subscriber_connection *conn)
} else {
ho_count(bts, CTR_INTRA_BSC_HO_ATTEMPTED);
ho_fsm_update_id(fi, "intraBSC");
+ ho_count_bts(ho->new_bts, BTS_CTR_INCOMING_INTRA_BSC_HO_ATTEMPTED);
}
if (!ho->new_lchan) {
ho_fail(HO_RESULT_FAIL_NO_CHANNEL,
"No %s lchan available on BTS %u",
- gsm_lchant_name(ho->new_lchan_type), ho->new_bts->nr);
+ gsm_chan_t_name(ho->new_lchan_type), ho->new_bts->nr);
return;
}
LOG_HO(conn, LOGL_DEBUG, "Selected lchan %s\n", gsm_lchan_name(ho->new_lchan));
@@ -397,20 +405,53 @@ static void handover_start_intra_bsc(struct gsm_subscriber_connection *conn)
ho_fsm_state_chg(HO_ST_WAIT_LCHAN_ACTIVE);
info = (struct lchan_activate_info){
- .activ_for = FOR_HANDOVER,
+ .activ_for = ACTIVATE_FOR_HANDOVER,
.for_conn = conn,
- .chan_mode = conn->lchan->tch_mode,
+ .ch_mode_rate = conn->lchan->current_ch_mode_rate,
.encr = conn->lchan->encr,
.requires_voice_stream = conn->lchan->mgw_endpoint_ci_bts ? true : false,
.msc_assigned_cic = conn->ho.inter_bsc_in.msc_assigned_cic,
.re_use_mgw_endpoint_from_lchan = conn->lchan,
.wait_before_switching_rtp = true,
- .s15_s0 = conn->lchan->activate.info.s15_s0,
};
+ info.ch_mode_rate.chan_rate = chan_t_to_chan_rate(ho->new_lchan->type);
+
+ /* For intra-cell handover, we know the accurate Timing Advance from the previous lchan. For inter-cell
+ * handover, no Timing Advance for the new cell is known, so leave it unset. */
+ if (ho->new_bts == bts) {
+ info.ta = conn->lchan->last_ta;
+ info.ta_known = true;
+ }
lchan_activate(ho->new_lchan, &info);
}
+/* 3GPP TS 48.008 § 3.2.2.58 Old BSS to New BSS Information */
+static void parse_old2new_bss_info(struct gsm_subscriber_connection *conn,
+ const uint8_t* data, uint16_t len,
+ struct handover_in_req *req)
+{
+ /* § 3.2.2.58: Information contained here shall take precedence over
+ duplicate information from Information Elements in the HANDOVER
+ REQUEST as long as the coding is understood by the new BSS */
+ /* § 3.2.2.58: <<Reception of an erroneous "Old BSS to New BSS
+ information" shall not cause a rejection of the HANDOVER REQUEST
+ message; the "Old BSS to New BSS information" information element
+ shall be discarded and the handover resource allocation procedure
+ shall continue>>. See also 3.1.19.7. */
+ struct tlv_parsed tp;
+ if (tlv_parse(&tp, &gsm0808_old_bss_to_new_bss_info_att_tlvdef, data, len, 0, 0) <= 0) {
+ LOG_HO(conn, LOGL_NOTICE, "Failed to parse IE \"Old BSS to New BSS information\"\n");
+ return;
+ }
+
+ if (TLVP_VAL(&tp, GSM0808_FE_IE_LAST_USED_EUTRAN_PLMN_ID)) {
+ req->last_eutran_plmn_valid = true;
+ osmo_plmn_from_bcd(TLVP_VAL(&tp, GSM0808_FE_IE_LAST_USED_EUTRAN_PLMN_ID),
+ &req->last_eutran_plmn);
+ }
+}
+
/* 3GPP TS 48.008 § 3.2.1.8 Handover Request */
static bool parse_ho_request(struct gsm_subscriber_connection *conn, const struct msgb *msg,
struct handover_in_req *req)
@@ -422,6 +463,7 @@ static bool parse_ho_request(struct gsm_subscriber_connection *conn, const struc
int payload_length;
bool aoip = gscon_is_aoip(conn);
bool sccplite = gscon_is_sccplite(conn);
+ bool has_a54 = false;
if ((aoip && sccplite) || !(aoip || sccplite)) {
LOG_HO(conn, LOGL_ERROR, "Received BSSMAP Handover Request, but conn is not"
@@ -452,6 +494,16 @@ static bool parse_ho_request(struct gsm_subscriber_connection *conn, const struc
LOG_HO(conn, LOGL_ERROR, "Failed to parse Encryption Information IE\n");
return false;
}
+ req->ei_as_bitmask = *e->val;
+
+ if ((e = TLVP_GET(tp, GSM0808_IE_KC_128))) {
+ if (e->len != 16) {
+ LOG_HO(conn, LOGL_ERROR, "Invalid length in Kc128 IE: %u bytes (expected 16)\n", e->len);
+ return false;
+ }
+ memcpy(req->kc128, e->val, 16);
+ req->kc128_present = true;
+ }
if ((e = TLVP_GET(tp, GSM0808_IE_CLASSMARK_INFORMATION_TYPE_1))) {
if (e->len != sizeof(req->classmark.classmark1)) {
@@ -481,9 +533,10 @@ static bool parse_ho_request(struct gsm_subscriber_connection *conn, const struc
req->chosen_encr_alg);
}
- LOG_HO(conn, LOGL_DEBUG, "Handover Request encryption info: chosen=A5/%u key=%s\n",
- (req->chosen_encr_alg ? : 1) - 1, req->ei.key_len?
- osmo_hexdump_nospc(req->ei.key, req->ei.key_len) : "none");
+ LOG_HO(conn, LOGL_DEBUG, "Handover Request encryption info: chosen=A5/%u key=%s kc128=%s\n",
+ (req->chosen_encr_alg ? : 1) - 1,
+ req->ei.key_len ? osmo_hexdump_nospc(req->ei.key, req->ei.key_len) : "none",
+ has_a54 ? osmo_hexdump_nospc(req->kc128, 16) : "none");
if (TLVP_PRESENT(tp, GSM0808_IE_AOIP_TRASP_ADDR)) {
int rc;
@@ -551,6 +604,27 @@ static bool parse_ho_request(struct gsm_subscriber_connection *conn, const struc
return false;
}
+ if ((e = TLVP_GET(tp, GSM0808_IE_OLD_BSS_TO_NEW_BSS_INFORMATION))) {
+ parse_old2new_bss_info(conn, e->val, e->len, req);
+ }
+
+ /* Decode "Codec List (MSC Preferred)". First set len = 0 to empty the list. (For inter-BSC incoming handover,
+ * there can't possibly be a list here already, because the conn has just now been created; just do ensure
+ * sanity.) */
+ conn->codec_list = (struct gsm0808_speech_codec_list){};
+ if ((e = TLVP_GET(tp, GSM0808_IE_SPEECH_CODEC_LIST))) {
+ if (gsm0808_dec_speech_codec_list(&conn->codec_list, e->val, e->len) < 0) {
+ LOG_HO(conn, LOGL_ERROR, "incoming inter-BSC Handover: HO Request:"
+ " Unable to decode Codec List (MSC Preferred)\n");
+ return false;
+ }
+ }
+ if (aoip && !conn->codec_list.len) {
+ LOG_HO(conn, LOGL_ERROR, "incoming inter-BSC Handover: HO Request:"
+ " Invalid or empty Codec List (MSC Preferred)\n");
+ return false;
+ }
+
/* A lot of IEs remain ignored... */
return true;
@@ -558,7 +632,7 @@ static bool parse_ho_request(struct gsm_subscriber_connection *conn, const struc
static bool chan_mode_is_tch(enum gsm48_chan_mode mode)
{
- switch (mode) {
+ switch (gsm48_chan_mode_to_non_vamos(mode)) {
case GSM48_CMODE_SPEECH_V1:
case GSM48_CMODE_SPEECH_EFR:
case GSM48_CMODE_SPEECH_AMR:
@@ -578,6 +652,7 @@ void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn,
int match_idx;
struct osmo_fsm_inst *fi;
struct channel_mode_and_rate ch_mode_rate = {};
+ int chosen_a5_n;
handover_fsm_alloc(conn);
@@ -593,7 +668,7 @@ void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn,
ho_fsm_update_id(fi, "interBSCin");
if (!parse_ho_request(conn, ho_request_msg, req)) {
- ho_fail(HO_RESULT_ERROR, "Invalid Handover Request message from MSC\n");
+ ho_fail(HO_RESULT_ERROR, "Invalid Handover Request message from MSC");
return;
}
@@ -625,7 +700,10 @@ void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn,
ch_mode_rate.chan_rate == CH_RATE_FULL ? "full-rate" : "half-rate",
gsm0808_channel_type_name(&req->ct));
- lchan = lchan_select_by_chan_mode(bts, ch_mode_rate.chan_mode, ch_mode_rate.chan_rate);
+ lchan = lchan_select_by_chan_mode(bts,
+ ch_mode_rate.chan_mode,
+ ch_mode_rate.chan_rate,
+ SELECT_FOR_HANDOVER, NULL);
if (!lchan) {
LOG_HO(conn, LOGL_DEBUG, "BTS %u has no matching free channels\n", bts->nr);
continue;
@@ -658,26 +736,37 @@ void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn,
ho_fsm_state_chg(HO_ST_WAIT_LCHAN_ACTIVE);
info = (struct lchan_activate_info){
- .activ_for = FOR_HANDOVER,
+ .activ_for = ACTIVATE_FOR_HANDOVER,
.for_conn = conn,
- .chan_mode = ch_mode_rate.chan_mode,
- .s15_s0 = ch_mode_rate.s15_s0,
+ .ch_mode_rate = ch_mode_rate,
.requires_voice_stream = chan_mode_is_tch(ch_mode_rate.chan_mode),
.msc_assigned_cic = req->msc_assigned_cic,
};
- if (req->chosen_encr_alg) {
- info.encr.alg_id = req->chosen_encr_alg;
- if (info.encr.alg_id > 1 && !req->ei.key_len) {
- ho_fail(HO_RESULT_ERROR, "Chosen Encryption Algorithm (Serving) reflects A5/%u"
- " but there is no key (Encryption Information)", info.encr.alg_id - 1);
+ /* Figure out the encryption algorithm */
+ chosen_a5_n = select_best_cipher(req->ei_as_bitmask, bsc_gsmnet->a5_encryption_mask);
+ if (chosen_a5_n < 0) {
+ ho_fail(HO_RESULT_FAIL_RR_HO_FAIL,
+ "There is no A5 encryption mode that both BSC and MSC permit: MSC 0x%x & BSC 0x%x = 0",
+ req->ei_as_bitmask, bsc_gsmnet->a5_encryption_mask);
+ return;
+ }
+ if (chosen_a5_n > 0 && !req->ei.key_len) {
+ /* There is no key. Is A5/0 permitted? */
+ if ((req->ei_as_bitmask & bsc_gsmnet->a5_encryption_mask & 0x1) == 0x1) {
+ chosen_a5_n = 0;
+ } else {
+ ho_fail(HO_RESULT_ERROR,
+ "Encryption is required, but there is no key (Encryption Information)");
return;
}
}
- if (req->ei.key_len) {
+ /* Put encryption info in the chan activation info */
+ info.encr.alg_a5_n = chosen_a5_n;
+ if (chosen_a5_n > 0) {
if (req->ei.key_len > sizeof(info.encr.key)) {
- ho_fail(HO_RESULT_ERROR, "Encryption Information IE key length is too large: %u\n",
+ ho_fail(HO_RESULT_ERROR, "Encryption Information IE key length is too large: %u",
req->ei.key_len);
return;
}
@@ -685,9 +774,39 @@ void handover_start_inter_bsc_in(struct gsm_subscriber_connection *conn,
info.encr.key_len = req->ei.key_len;
}
+ if (req->kc128_present) {
+ memcpy(info.encr.kc128, req->kc128, 16);
+ info.encr.kc128_present = true;
+ }
+
+ if (req->last_eutran_plmn_valid) {
+ conn->fast_return.allowed = ho->new_bts->srvcc_fast_return_allowed;
+ conn->fast_return.last_eutran_plmn_valid = true;
+ memcpy(&conn->fast_return.last_eutran_plmn, &req->last_eutran_plmn,
+ sizeof(conn->fast_return.last_eutran_plmn));
+ ho_count(ho->new_bts, CTR_SRVCC_ATTEMPTED);
+ }
+
lchan_activate(ho->new_lchan, &info);
}
+/* Create functions result_counter_{BSC,BTS}_{HANDOVER,...}(), to evaluate the handover result and return
+ * BSC_CTR_HANDOVER_ATTEMPTED,
+ * BSC_CTR_HANDOVER_COMPLETED,
+ * BSC_CTR_HANDOVER_STOPPED,
+ * BSC_CTR_HANDOVER_NO_CHANNEL,
+ * BSC_CTR_HANDOVER_TIMEOUT,
+ * BSC_CTR_HANDOVER_FAILED,
+ * BSC_CTR_HANDOVER_ERROR,
+ * or
+ * BTS_CTR_HANDOVER_ATTEMPTED,
+ * BTS_CTR_HANDOVER_COMPLETED,
+ * BTS_CTR_HANDOVER_STOPPED,
+ * BTS_CTR_HANDOVER_NO_CHANNEL,
+ * BTS_CTR_HANDOVER_TIMEOUT,
+ * BTS_CTR_HANDOVER_FAILED,
+ * BTS_CTR_HANDOVER_ERROR,
+ */
#define FUNC_RESULT_COUNTER(obj, name) \
static int result_counter_##obj##_##name(enum handover_result result) \
{ \
@@ -713,6 +832,7 @@ FUNC_RESULT_COUNTER(BSC, INTRA_CELL_HO)
FUNC_RESULT_COUNTER(BSC, INTRA_BSC_HO)
FUNC_RESULT_COUNTER(BSC, INTER_BSC_HO_IN)
+/* INTRA_BSC_HO_OUT does not have a NO_CHANNEL result, so can't do this with FUNC_RESULT_COUNTER() macro. */
static int result_counter_BSC_INTER_BSC_HO_OUT(enum handover_result result) {
switch (result) {
case HO_RESULT_OK:
@@ -748,8 +868,10 @@ static int result_counter_bsc(enum handover_scope scope, enum handover_result re
FUNC_RESULT_COUNTER(BTS, HANDOVER)
FUNC_RESULT_COUNTER(BTS, INTRA_CELL_HO)
FUNC_RESULT_COUNTER(BTS, INTRA_BSC_HO)
+FUNC_RESULT_COUNTER(BTS, INCOMING_INTRA_BSC_HO)
FUNC_RESULT_COUNTER(BTS, INTER_BSC_HO_IN)
+/* INTRA_BSC_HO_OUT does not have a NO_CHANNEL result, so can't do this with FUNC_RESULT_COUNTER() macro. */
static int result_counter_BTS_INTER_BSC_HO_OUT(enum handover_result result) {
switch (result) {
case HO_RESULT_OK:
@@ -782,6 +904,9 @@ static int result_counter_bts(enum handover_scope scope, enum handover_result re
}
}
+FUNC_RESULT_COUNTER(BSC, SRVCC)
+FUNC_RESULT_COUNTER(BTS, SRVCC)
+
static void send_handover_performed(struct gsm_subscriber_connection *conn)
{
struct gsm_lchan *lchan = conn->lchan;
@@ -807,7 +932,7 @@ static void send_handover_performed(struct gsm_subscriber_connection *conn)
};
/* Chosen Channel 3.2.2.33 */
- ho_perf_params.chosen_channel = gsm0808_chosen_channel(lchan->type, lchan->tch_mode);
+ ho_perf_params.chosen_channel = gsm0808_chosen_channel(lchan->type, lchan->current_ch_mode_rate.chan_mode);
if (!ho_perf_params.chosen_channel) {
LOG_HO(conn, LOGL_ERROR, "Failed to generate Chosen Channel IE, can't send HANDOVER PERFORMED!\n");
return;
@@ -815,19 +940,20 @@ static void send_handover_performed(struct gsm_subscriber_connection *conn)
ho_perf_params.chosen_channel_present = true;
/* Chosen Encryption Algorithm 3.2.2.44 */
- ho_perf_params.chosen_encr_alg = lchan->encr.alg_id;
+ ho_perf_params.chosen_encr_alg = ALG_A5_NR_TO_BSSAP(lchan->encr.alg_a5_n);
ho_perf_params.chosen_encr_alg_present = true;
if (ho->new_lchan->activate.info.requires_voice_stream) {
/* Speech Version (chosen) 3.2.2.51 */
- ho_perf_params.speech_version_chosen = gsm0808_permitted_speech(lchan->type, lchan->tch_mode);
+ ho_perf_params.speech_version_chosen = gsm0808_permitted_speech(lchan->type,
+ lchan->current_ch_mode_rate.chan_mode);
ho_perf_params.speech_version_chosen_present = true;
/* Speech Codec (chosen) 3.2.2.104 */
if (gscon_is_aoip(conn)) {
/* Extrapolate speech codec from speech mode */
gsm0808_speech_codec_from_chan_type(&sc, ho_perf_params.speech_version_chosen);
- sc.cfg = conn->lchan->ch_mode_rate.s15_s0;
+ sc.cfg = conn->lchan->current_ch_mode_rate.s15_s0;
memcpy(&ho_perf_params.speech_codec_chosen, &sc, sizeof(sc));
ho_perf_params.speech_codec_chosen_present = true;
}
@@ -839,7 +965,7 @@ static void send_handover_performed(struct gsm_subscriber_connection *conn)
return;
}
- rate_ctr_inc(&conn->sccp.msc->msc_ctrs->ctr[MSC_CTR_BSSMAP_TX_DT1_HANDOVER_PERFORMED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(conn->sccp.msc->msc_ctrs, MSC_CTR_BSSMAP_TX_DT1_HANDOVER_PERFORMED));
rc = gscon_sigtran_send(conn, msg);
if (rc < 0) {
LOG_HO(conn, LOGL_ERROR, "message sending failed, can't send HANDOVER PERFORMED!\n");
@@ -877,7 +1003,7 @@ void handover_end(struct gsm_subscriber_connection *conn, enum handover_result r
result = bsc_tx_bssmap_ho_complete(conn, ho->new_lchan);
}
/* Not 'else': above checks may still result in HO_RESULT_ERROR. */
- if (result == HO_RESULT_ERROR) {
+ if (result != HO_RESULT_OK) {
/* Return a BSSMAP Handover Failure, as described in 3GPP TS 48.008 3.1.5.2.2
* "Handover Resource Allocation Failure" */
bsc_tx_bssmap_ho_failure(conn);
@@ -903,7 +1029,8 @@ void handover_end(struct gsm_subscriber_connection *conn, enum handover_result r
/* 3GPP TS 48.008 3.1.5.3.3 "Abnormal Conditions": if neither MS reports
* HO Failure nor the MSC sends a Clear Command, we should release the
* dedicated radio resources and send a Clear Request to the MSC. */
- lchan_release(conn->lchan, true, true, GSM48_RR_CAUSE_ABNORMAL_TIMER);
+ lchan_release(conn->lchan, true, true, GSM48_RR_CAUSE_ABNORMAL_TIMER,
+ gscon_last_eutran_plmn(conn));
/* Once the channel release is through, the BSSMAP Clear will follow. */
break;
}
@@ -933,13 +1060,30 @@ void handover_end(struct gsm_subscriber_connection *conn, enum handover_result r
ho_count_bsc(result_counter_bsc(ho->scope, result));
ho_count_bts(bts, result_counter_BTS_HANDOVER(result));
ho_count_bts(bts, result_counter_bts(ho->scope, result));
+ /* For inter-cell HO, also increment the "INCOMING" counters on the target BTS. */
+ if (ho->scope & HO_INTRA_BSC)
+ ho_count_bts(ho->new_bts, result_counter_BTS_INCOMING_INTRA_BSC_HO(result));
+ if (ho->scope & HO_INTER_BSC_IN && conn->fast_return.last_eutran_plmn_valid) {
+ /* From outside local BSC and with Last EUTRAN PLMN Id => SRVCC */
+ ho_count_bsc(result_counter_BSC_SRVCC(result));
+ ho_count_bts(bts, result_counter_BTS_SRVCC(result));
+ }
LOG_HO(conn, LOGL_INFO, "Result: %s\n", handover_result_name(result));
if (ho->new_lchan && result == HO_RESULT_OK) {
+ struct gsm_bts *bts;
+
gscon_change_primary_lchan(conn, conn->ho.new_lchan);
ho->new_lchan = NULL;
+ bts = conn_get_bts(conn);
+ 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);
+ }
+
/* If a Perform Location Request (LCS) is busy, inform the SMLC that there is a new lchan */
if (conn->lcs.loc_req)
osmo_fsm_inst_dispatch(conn->lcs.loc_req->fi, LCS_LOC_REQ_EV_HANDOVER_PERFORMED, NULL);
@@ -950,7 +1094,7 @@ void handover_end(struct gsm_subscriber_connection *conn, enum handover_result r
/* Detach the new_lchan last, so we can still see it in above logging */
if (ho->new_lchan) {
/* Release new lchan, it didn't work out */
- lchan_release(ho->new_lchan, false, true, RSL_ERR_EQUIPMENT_FAIL);
+ lchan_release(ho->new_lchan, false, true, RSL_ERR_EQUIPMENT_FAIL, NULL);
ho->new_lchan = NULL;
}
@@ -1079,6 +1223,7 @@ static void ho_fsm_wait_rr_ho_detect_onenter(struct osmo_fsm_inst *fi, uint32_t
struct handover *ho = &conn->ho;
struct msgb *rr_ho_cmd = gsm48_make_ho_cmd(ho->new_lchan,
+ ho->scope, ho->async,
ho->new_lchan->ms_power,
ho->ho_ref);
if (!rr_ho_cmd) {