diff options
Diffstat (limited to 'src/osmo-bsc/handover_logic.c')
-rw-r--r-- | src/osmo-bsc/handover_logic.c | 473 |
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; +} |