diff options
Diffstat (limited to 'src/osmo-bsc/handover_fsm.c')
-rw-r--r-- | src/osmo-bsc/handover_fsm.c | 271 |
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) { |