aboutsummaryrefslogtreecommitdiffstats
path: root/src/osmo-bsc/handover_logic.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/osmo-bsc/handover_logic.c')
-rw-r--r--src/osmo-bsc/handover_logic.c504
1 files changed, 141 insertions, 363 deletions
diff --git a/src/osmo-bsc/handover_logic.c b/src/osmo-bsc/handover_logic.c
index 960bf6993..bdb73f520 100644
--- a/src/osmo-bsc/handover_logic.c
+++ b/src/osmo-bsc/handover_logic.c
@@ -33,7 +33,7 @@
#include <osmocom/bsc/gsm_data.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/bsc/abis_rsl.h>
-#include <osmocom/bsc/chan_alloc.h>
+#include <osmocom/bsc/lchan_select.h>
#include <osmocom/bsc/signal.h>
#include <osmocom/core/talloc.h>
#include <osmocom/bsc/bsc_subscriber.h>
@@ -41,355 +41,183 @@
#include <osmocom/bsc/handover.h>
#include <osmocom/bsc/handover_cfg.h>
#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
+#include <osmocom/bsc/neighbor_ident.h>
+#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/gsm/gsm0808_utils.h>
+
+const struct value_string handover_scope_names[] = {
+ { HO_NO_HANDOVER, "No Handover" },
+ { HO_INTRA_CELL, "Assignment" },
+ { HO_INTRA_BSC, "Handover" },
+ { HO_INTER_BSC_MO, "Inter-BSC-Handover (MO)" },
+ { HO_INTER_BSC_MT, "Inter-BSC-Handover (MT)" },
+ { HO_SCOPE_ALL, "Any Handover" },
+ {}
+};
+
+const struct value_string handover_result_names[] = {
+ { HO_RESULT_OK, "Complete" },
+ { HO_RESULT_FAIL_NO_CHANNEL, "Failure (no channel could be allocated)" },
+ { HO_RESULT_FAIL_RR_HO_FAIL, "Failure (MS sent RR Handover Failure)" },
+ { HO_RESULT_FAIL_TIMEOUT, "Failure (timeout)" },
+ { HO_RESULT_CONN_RELEASE, "Connection released" },
+ { HO_RESULT_ERROR, "Failure" },
+ {}
+};
-static LLIST_HEAD(bsc_handovers);
static LLIST_HEAD(handover_decision_callbacks);
-static void handover_free(struct bsc_handover *ho)
-{
- osmo_timer_del(&ho->T3103);
- llist_del(&ho->list);
- talloc_free(ho);
-}
-
-static struct bsc_handover *bsc_ho_by_new_lchan(struct gsm_lchan *new_lchan)
+void handover_decision_callbacks_register(struct handover_decision_callbacks *hdc)
{
- struct bsc_handover *ho;
-
- llist_for_each_entry(ho, &bsc_handovers, list) {
- if (ho->new_lchan == new_lchan)
- return ho;
- }
-
- return NULL;
+ llist_add_tail(&hdc->entry, &handover_decision_callbacks);
}
-static struct bsc_handover *bsc_ho_by_old_lchan(struct gsm_lchan *old_lchan)
+struct handover_decision_callbacks *handover_decision_callbacks_get(int hodec_id)
{
- struct bsc_handover *ho;
-
- llist_for_each_entry(ho, &bsc_handovers, list) {
- if (ho->old_lchan == old_lchan)
- return ho;
+ struct handover_decision_callbacks *hdc;
+ llist_for_each_entry(hdc, &handover_decision_callbacks, entry) {
+ if (hdc->hodec_id == hodec_id)
+ return hdc;
}
-
return NULL;
}
-/*! Hand over the specified logical channel to the specified new BTS and possibly change the lchan type.
- * This is the main entry point for the actual handover algorithm, after the decision whether to initiate
- * HO to a specific BTS. To not change the lchan type, pass old_lchan->type. */
-int bsc_handover_start(enum hodec_id from_hodec_id, struct gsm_lchan *old_lchan, struct gsm_bts *new_bts,
- enum gsm_chan_t new_lchan_type)
-{
- struct gsm_subscriber_connection *conn;
- struct bsc_handover *ho;
- static uint8_t ho_ref = 0;
- bool do_assignment;
-
- OSMO_ASSERT(old_lchan);
-
- /* don't attempt multiple handovers for the same lchan at
- * the same time */
- if (bsc_ho_by_old_lchan(old_lchan))
- return -EBUSY;
-
- conn = old_lchan->conn;
- if (!conn) {
- LOGP(DHO, LOGL_ERROR, "Old lchan lacks connection data.\n");
- return -ENOSPC;
- }
-
- if (!new_bts)
- new_bts = old_lchan->ts->trx->bts;
- OSMO_ASSERT(new_bts);
-
- do_assignment = (new_bts == old_lchan->ts->trx->bts);
-
- ho = talloc_zero(conn, struct bsc_handover);
- if (!ho) {
- LOGP(DHO, LOGL_FATAL, "Out of Memory\n");
- return -ENOMEM;
- }
- ho->from_hodec_id = from_hodec_id;
- ho->old_lchan = old_lchan;
- ho->new_bts = new_bts;
- ho->new_lchan_type = new_lchan_type;
- ho->ho_ref = ho_ref++;
- ho->inter_cell = !do_assignment;
- ho->async = true;
- llist_add(&ho->list, &bsc_handovers);
-
- conn->ho = ho;
-
- DEBUGP(DHO, "(BTS %u trx %u ts %u lchan %u %s)->(BTS %u lchan %s) Initiating %s...\n",
- old_lchan->ts->trx->bts->nr,
- old_lchan->ts->trx->nr,
- old_lchan->ts->nr,
- old_lchan->nr,
- gsm_pchan_name(old_lchan->ts->pchan),
- new_bts->nr,
- gsm_lchant_name(new_lchan_type),
- do_assignment ? "Assignment" : "Handover");
-
- return osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_HO_START, NULL);
-}
-
-/*! Start actual handover. Call bsc_handover_start() instead; The only legal caller is the GSCON FSM in
- * bsc_subscr_conn_fsm.c. */
-int bsc_handover_start_gscon(struct gsm_subscriber_connection *conn)
+#if 0
+static void handover_start_inter_bsc_mo(struct handover *ho,
+ const struct gsm0808_cell_id_list2 *target_cells,
+ enum gsm_chan_t new_lchan_type)
{
int rc;
- struct gsm_network *network = conn->network;
- struct bsc_handover *ho = conn->ho;
- struct gsm_lchan *old_lchan;
- struct gsm_lchan *new_lchan;
-
- if (!ho) {
- LOGP(DHO, LOGL_ERROR, "%s: Requested to start handover, but conn->ho is NULL\n",
- bsc_subscr_name(conn->bsub));
- return -EINVAL;
- }
+ struct gsm_lchan *old_lchan = ho->mo.old_lchan;
+ struct gsm0808_handover_required ho_required_params = {
+ .cause = GSM0808_CAUSE_BETTER_CELL,
+ .cil = *target_cells,
+ .current_channel_type_1_present = true,
+ .current_channel_type_1 = gsm0808_current_channel_type_1(old_lchan->type),
+ };
- OSMO_ASSERT(ho->old_lchan && ho->new_bts);
+ ho->scope = HO_INTER_BSC_MO;
- if (ho->old_lchan->conn != conn) {
- LOGP(DHO, LOGL_ERROR,
- "%s: Requested to start handover, but the lchan does not belong to this conn\n",
- bsc_subscr_name(conn->bsub));
- return -EINVAL;
- }
-
- rate_ctr_inc(&network->bsc_ctrs->ctr[BSC_CTR_HANDOVER_ATTEMPTED]);
+ LOGPHO(ho, LOGL_INFO, "Starting\n");
- ho->new_lchan = lchan_alloc(ho->new_bts, ho->new_lchan_type, 0);
- if (!ho->new_lchan) {
- LOGP(DHO, LOGL_NOTICE, "No free channel for %s\n", gsm_lchant_name(ho->new_lchan_type));
- rate_ctr_inc(&network->bsc_ctrs->ctr[BSC_CTR_HANDOVER_NO_CHANNEL]);
- return -ENOSPC;
+ if (osmo_fsm_inst_dispatch(ho->conn->fi, GSCON_EV_INTER_BSC_HO_MO_START, NULL)) {
+ handover_end(ho, HO_RESULT_ERROR);
+ return;
}
- LOGPHO(ho, LOGL_INFO, "Triggering %s\n", ho->inter_cell? "Handover" : "Assignment");
-
- /* copy some parameters from old lchan */
- old_lchan = ho->old_lchan;
- new_lchan = ho->new_lchan;
- memcpy(&new_lchan->encr, &old_lchan->encr, sizeof(new_lchan->encr));
- if (!ho->inter_cell) {
- new_lchan->ms_power = old_lchan->ms_power;
- new_lchan->rqd_ta = old_lchan->rqd_ta;
- } else {
- new_lchan->ms_power =
- ms_pwr_ctl_lvl(ho->new_bts->band, ho->new_bts->ms_max_power);
- /* FIXME: do we have a better idea of the timing advance? */
- //new_lchan->rqd_ta = old_lchan->rqd_ta;
- }
- new_lchan->bs_power = old_lchan->bs_power;
- new_lchan->rsl_cmode = old_lchan->rsl_cmode;
- new_lchan->tch_mode = old_lchan->tch_mode;
- memcpy(&new_lchan->mr_ms_lv, &old_lchan->mr_ms_lv, sizeof(new_lchan->mr_ms_lv));
- memcpy(&new_lchan->mr_bts_lv, &old_lchan->mr_bts_lv, sizeof(new_lchan->mr_bts_lv));
-
- new_lchan->conn = conn;
-
- rc = rsl_chan_activate_lchan(new_lchan,
- ho->async ? RSL_ACT_INTER_ASYNC : RSL_ACT_INTER_SYNC,
- ho->ho_ref);
- if (rc < 0) {
- LOGPHO(ho, LOGL_INFO, "%s Failure: activate lchan rc = %d\n",
- ho->inter_cell? "Handover" : "Assignment", rc);
- lchan_free(new_lchan);
- ho->new_lchan = NULL;
- bsc_clear_handover(conn, 0);
- return rc;
+ switch (old_lchan->type) {
+ case GSM_LCHAN_TCH_F:
+ case GSM_LCHAN_TCH_H:
+ ho_required_params.speech_version_used_present = true;
+ ho_required_params.speech_version_used = gsm0808_permitted_speech(old_lchan->type,
+ old_lchan->tch_mode);
+ if (!ho_required_params.speech_version_used) {
+ LOGPHO(ho, LOGL_ERROR,
+ "Cannot encode Speech Version (Used) for HANDOVER REQUIRED message\n");
+ handover_end(ho, HO_RESULT_ERROR);
+ return;
+ }
+ break;
+ default:
+ break;
}
- rsl_lchan_set_state(new_lchan, LCHAN_S_ACT_REQ);
- /* we continue in the SS_LCHAN handler / ho_chan_activ_ack */
-
- return 0;
-}
-
-/* clear any operation for this connection */
-void bsc_clear_handover(struct gsm_subscriber_connection *conn, int free_lchan)
-{
- struct bsc_handover *ho = conn->ho;
-
- if (!ho)
- return;
-
- if (ho->new_lchan) {
- ho->new_lchan->conn = NULL;
- if (free_lchan)
- lchan_release(ho->new_lchan, 0, RSL_REL_LOCAL_END);
- ho->new_lchan = NULL;
+ rc = bsc_send_handover_required(ho->mo.old_lchan, &ho_required_params);
+ if (rc) {
+ LOGPHO(ho, LOGL_ERROR, "Failed to send Handover Required (rc=%d)\n", rc);
+ handover_end(ho, HO_RESULT_ERROR);
}
-
- handover_free(ho);
- conn->ho = NULL;
}
-/* T3103 expired: Handover has failed without HO COMPLETE or HO FAIL */
-static void ho_T3103_cb(void *_ho)
-{
- struct bsc_handover *ho = _ho;
- struct gsm_network *net = ho->new_lchan->ts->trx->bts->network;
-
- DEBUGP(DHO, "HO T3103 expired\n");
- rate_ctr_inc(&net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_TIMEOUT]);
+#endif
- /* Inform the GSCON FSM about the timed out handover */
- osmo_fsm_inst_dispatch(ho->old_lchan->conn->fi, GSCON_EV_HO_TIMEOUT, NULL);
- bsc_clear_handover(ho->old_lchan->conn, 1);
-}
-/* RSL has acknowledged activation of the new lchan */
-static int ho_chan_activ_ack(struct gsm_lchan *new_lchan)
+static void ho_meas_rep(struct gsm_meas_rep *mr)
{
- struct bsc_handover *ho;
-
- /* we need to check if this channel activation is related to
- * a handover at all (and if, which particular handover) */
- ho = bsc_ho_by_new_lchan(new_lchan);
- if (!ho)
- return -ENODEV;
-
- LOGPHO(ho, LOGL_INFO, "Channel Activate Ack, send %s COMMAND\n", ho->inter_cell? "HANDOVER" : "ASSIGNMENT");
-
- /* we can now send the 04.08 HANDOVER COMMAND to the MS
- * using the old lchan */
-
- gsm48_send_ho_cmd(ho->old_lchan, new_lchan, new_lchan->ms_power, ho->ho_ref);
-
- /* start T3103. We can continue either with T3103 expiration,
- * 04.08 HANDOVER COMPLETE or 04.08 HANDOVER FAIL */
- osmo_timer_setup(&ho->T3103, ho_T3103_cb, ho);
- osmo_timer_schedule(&ho->T3103, 10, 0);
-
- /* create a RTP connection */
- if (is_ipaccess_bts(new_lchan->ts->trx->bts))
- rsl_ipacc_crcx(new_lchan);
-
- return 0;
-}
-
-/* RSL has not acknowledged activation of the new lchan */
-static int ho_chan_activ_nack(struct gsm_lchan *new_lchan)
-{
- struct bsc_handover *ho;
struct handover_decision_callbacks *hdc;
+ enum hodec_id hodec_id = ho_get_algorithm(mr->lchan->ts->trx->bts->ho);
- ho = bsc_ho_by_new_lchan(new_lchan);
- if (!ho) {
- /* This lchan is not involved in a handover. */
- return 0;
- }
-
- hdc = handover_decision_callbacks_get(ho->from_hodec_id);
- if (hdc && hdc->on_ho_chan_activ_nack)
- hdc->on_ho_chan_activ_nack(ho);
-
- bsc_clear_handover(new_lchan->conn, 0);
- return 0;
-}
-
-/* GSM 04.08 HANDOVER COMPLETE has been received on new channel */
-static int ho_gsm48_ho_compl(struct gsm_lchan *new_lchan)
-{
- struct gsm_network *net;
- struct bsc_handover *ho;
-
- ho = bsc_ho_by_new_lchan(new_lchan);
- if (!ho) {
- LOGP(DHO, LOGL_ERROR, "unable to find HO record\n");
- return -ENODEV;
- }
-
- net = new_lchan->ts->trx->bts->network;
-
- LOGPHO(ho, LOGL_INFO, "%s Complete\n", ho->inter_cell ? "Handover" : "Assignment");
-
- rate_ctr_inc(&net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_COMPLETED]);
-
- osmo_timer_del(&ho->T3103);
-
- /* Replace the ho lchan with the primary one */
- if (ho->old_lchan != new_lchan->conn->lchan)
- LOGPHO(ho, LOGL_ERROR, "Primary lchan changed during handover.\n");
-
- if (new_lchan->conn->ho != ho)
- LOGPHO(ho, LOGL_ERROR, "Handover channel changed during this handover.\n");
-
- new_lchan->conn->lchan = new_lchan;
- ho->old_lchan->conn = NULL;
-
- lchan_release(ho->old_lchan, 0, RSL_REL_LOCAL_END);
-
- handover_free(ho);
- new_lchan->conn->ho = NULL;
-
- /* Inform the GSCON FSM that the handover is complete */
- osmo_fsm_inst_dispatch(new_lchan->conn->fi, GSCON_EV_HO_COMPL, NULL);
- return 0;
+ hdc = handover_decision_callbacks_get(hodec_id);
+ if (!hdc || !hdc->on_measurement_report)
+ return;
+ hdc->on_measurement_report(mr);
}
-/* GSM 04.08 HANDOVER FAIL has been received */
-static int ho_gsm48_ho_fail(struct gsm_lchan *old_lchan)
+/* Count ongoing handovers within the given BTS.
+ * ho_scopes is an OR'd combination of enum handover_scope values to include in the count. */
+int handover_count(struct gsm_bts *bts, int ho_scopes)
{
- struct gsm_network *net = old_lchan->ts->trx->bts->network;
- struct bsc_handover *ho;
- struct handover_decision_callbacks *hdc;
+ struct gsm_bts_trx *trx;
+ int count = 0;
- ho = bsc_ho_by_old_lchan(old_lchan);
- if (!ho) {
- LOGP(DHO, LOGL_ERROR, "unable to find HO record\n");
- return -ENODEV;
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ int i;
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[i];
+ struct gsm_lchan *lchan;
+
+ /* skip administratively deactivated timeslots */
+ if (!nm_is_running(&ts->mo.nm_state))
+ continue;
+
+ ts_for_each_lchan(lchan, ts) {
+ if (!lchan->conn)
+ continue;
+ if (!lchan->conn->ho.fi)
+ continue;
+ if (lchan->conn->ho.scope & ho_scopes)
+ count++;
+ }
+ }
}
- hdc = handover_decision_callbacks_get(ho->from_hodec_id);
- if (hdc && hdc->on_ho_failure)
- hdc->on_ho_failure(ho);
-
- rate_ctr_inc(&net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_FAILED]);
-
- bsc_clear_handover(ho->new_lchan->conn, 1);
-
- /* Inform the GSCON FSM that the handover failed */
- osmo_fsm_inst_dispatch(old_lchan->conn->fi, GSCON_EV_HO_FAIL, NULL);
- return 0;
+ return count;
}
-/* GSM 08.58 HANDOVER DETECT has been received */
-static int ho_rsl_detect(struct gsm_lchan *new_lchan)
-{
- struct bsc_handover *ho;
-
- ho = bsc_ho_by_new_lchan(new_lchan);
- if (!ho) {
- LOGP(DHO, LOGL_ERROR, "unable to find HO record\n");
- return -ENODEV;
+struct gsm_bts *bts_by_neighbor_ident(const struct gsm_network *net,
+ const struct neighbor_ident_key *search_for)
+{
+ struct gsm_bts *found = NULL;
+ struct gsm_bts *bts;
+ struct gsm_bts *wildcard_match = NULL;
+
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ struct neighbor_ident_key entry = {
+ .from_bts = NEIGHBOR_IDENT_KEY_ANY_BTS,
+ .arfcn = bts->c0->arfcn,
+ .bsic_kind = BSIC_6BIT,
+ .bsic = bts->bsic,
+ };
+ if (neighbor_ident_key_match(&entry, search_for, true)) {
+ if (found) {
+ LOGP(DHO, LOGL_ERROR, "CONFIG ERROR: Multiple BTS match %s: %d and %d\n",
+ neighbor_ident_key_name(search_for),
+ found->nr, bts->nr);
+ return found;
+ }
+ found = bts;
+ }
+ if (neighbor_ident_key_match(&entry, search_for, false))
+ wildcard_match = bts;
}
- LOGPHO(ho, LOGL_DEBUG, "Handover RACH detected\n");
-
- /* This is just for logging on the DHO category. The actual MGCP switchover happens in
- * osmo_bsc_mgcp.c by receiving the same S_LCHAN_HANDOVER_DETECT signal.
- * (Calling mgcp_handover() directly currently breaks linking in utils/...) */
+ if (found)
+ return found;
- return 0;
+ return wildcard_match;
}
-static int ho_meas_rep(struct gsm_meas_rep *mr)
+struct neighbor_ident_key *bts_ident_key(const struct gsm_bts *bts)
{
- struct handover_decision_callbacks *hdc;
- enum hodec_id hodec_id = ho_get_algorithm(mr->lchan->ts->trx->bts->ho);
-
- hdc = handover_decision_callbacks_get(hodec_id);
- if (!hdc || !hdc->on_measurement_report)
- return 0;
- hdc->on_measurement_report(mr);
- return 0;
+ static struct neighbor_ident_key key;
+ key = (struct neighbor_ident_key){
+ .arfcn = bts->c0->arfcn,
+ .bsic_kind = BSIC_6BIT,
+ .bsic = bts->bsic,
+ };
+ return &key;
}
static int ho_logic_sig_cb(unsigned int subsys, unsigned int signal,
@@ -401,73 +229,23 @@ static int ho_logic_sig_cb(unsigned int subsys, unsigned int signal,
lchan_data = signal_data;
switch (subsys) {
case SS_LCHAN:
+ OSMO_ASSERT(lchan_data);
lchan = lchan_data->lchan;
+ OSMO_ASSERT(lchan);
+
switch (signal) {
- case S_LCHAN_ACTIVATE_ACK:
- return ho_chan_activ_ack(lchan);
- case S_LCHAN_ACTIVATE_NACK:
- return ho_chan_activ_nack(lchan);
- case S_LCHAN_HANDOVER_DETECT:
- return ho_rsl_detect(lchan);
- case S_LCHAN_HANDOVER_COMPL:
- return ho_gsm48_ho_compl(lchan);
- case S_LCHAN_HANDOVER_FAIL:
- return ho_gsm48_ho_fail(lchan);
case S_LCHAN_MEAS_REP:
- return ho_meas_rep(lchan_data->mr);
+ ho_meas_rep(lchan_data->mr);
+ break;
}
- break;
+
default:
break;
}
-
return 0;
}
-/* Return the old lchan or NULL. This is meant for audio handling */
-struct gsm_lchan *bsc_handover_pending(struct gsm_lchan *new_lchan)
-{
- struct bsc_handover *ho;
- ho = bsc_ho_by_new_lchan(new_lchan);
- if (!ho)
- return NULL;
- return ho->old_lchan;
-}
-
static __attribute__((constructor)) void on_dso_load_ho_logic(void)
{
osmo_signal_register_handler(SS_LCHAN, ho_logic_sig_cb, NULL);
}
-
-/* Count number of currently ongoing handovers
- * inter_cell: if true, count only handovers between two cells. If false, count only handovers within one
- * cell. */
-int bsc_ho_count(struct gsm_bts *bts, bool inter_cell)
-{
- struct bsc_handover *ho;
- int count = 0;
-
- llist_for_each_entry(ho, &bsc_handovers, list) {
- if (ho->inter_cell != inter_cell)
- continue;
- if (ho->new_lchan->ts->trx->bts == bts)
- count++;
- }
-
- return count;
-}
-
-void handover_decision_callbacks_register(struct handover_decision_callbacks *hdc)
-{
- llist_add_tail(&hdc->entry, &handover_decision_callbacks);
-}
-
-struct handover_decision_callbacks *handover_decision_callbacks_get(int hodec_id)
-{
- struct handover_decision_callbacks *hdc;
- llist_for_each_entry(hdc, &handover_decision_callbacks, entry) {
- if (hdc->hodec_id == hodec_id)
- return hdc;
- }
- return NULL;
-}