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.c473
1 files changed, 473 insertions, 0 deletions
diff --git a/src/osmo-bsc/handover_logic.c b/src/osmo-bsc/handover_logic.c
new file mode 100644
index 000000000..960bf6993
--- /dev/null
+++ b/src/osmo-bsc/handover_logic.c
@@ -0,0 +1,473 @@
+/* Handover Logic for Inter-BTS (Intra-BSC) Handover. This does not
+ * actually implement the handover algorithm/decision, but executes a
+ * handover decision */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <netinet/in.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/bsc/debug.h>
+#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/signal.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/bsc/bsc_subscriber.h>
+#include <osmocom/bsc/gsm_04_08_utils.h>
+#include <osmocom/bsc/handover.h>
+#include <osmocom/bsc/handover_cfg.h>
+#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
+
+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)
+{
+ struct bsc_handover *ho;
+
+ llist_for_each_entry(ho, &bsc_handovers, list) {
+ if (ho->new_lchan == new_lchan)
+ return ho;
+ }
+
+ return NULL;
+}
+
+static struct bsc_handover *bsc_ho_by_old_lchan(struct gsm_lchan *old_lchan)
+{
+ struct bsc_handover *ho;
+
+ llist_for_each_entry(ho, &bsc_handovers, list) {
+ if (ho->old_lchan == old_lchan)
+ return ho;
+ }
+
+ 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)
+{
+ 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;
+ }
+
+ OSMO_ASSERT(ho->old_lchan && ho->new_bts);
+
+ 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]);
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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]);
+
+ /* 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)
+{
+ 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;
+
+ 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;
+}
+
+/* GSM 04.08 HANDOVER FAIL has been received */
+static int ho_gsm48_ho_fail(struct gsm_lchan *old_lchan)
+{
+ struct gsm_network *net = old_lchan->ts->trx->bts->network;
+ struct bsc_handover *ho;
+ struct handover_decision_callbacks *hdc;
+
+ ho = bsc_ho_by_old_lchan(old_lchan);
+ if (!ho) {
+ LOGP(DHO, LOGL_ERROR, "unable to find HO record\n");
+ return -ENODEV;
+ }
+
+ 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;
+}
+
+/* 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;
+ }
+
+ 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/...) */
+
+ return 0;
+}
+
+static int ho_meas_rep(struct gsm_meas_rep *mr)
+{
+ 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 int ho_logic_sig_cb(unsigned int subsys, unsigned int signal,
+ void *handler_data, void *signal_data)
+{
+ struct lchan_signal_data *lchan_data;
+ struct gsm_lchan *lchan;
+
+ lchan_data = signal_data;
+ switch (subsys) {
+ case SS_LCHAN:
+ lchan = lchan_data->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);
+ }
+ 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;
+}