From acaf8eaeaab95f5bca234132810831b3cfaa51a7 Mon Sep 17 00:00:00 2001 From: Andreas Eversberg Date: Sun, 7 Jul 2013 08:24:32 +0200 Subject: HO: New improved handover algorithm The handover algorithm selects the best cell, tries to solve congestion of cells and forces handover in case of bad radio link: Handover shall ensure that the mobile station is always on the best cell during a call. This results in best speech quality and allows a lower TX level (up-link) to save battery and reduce interference. Sometimes this is not desirable, especially if a cell with large coverage becomes congested after handover. In this situation it makes sense to balance the usage between cells to prevent congestion. If a cell is congested, one or several mobile stations need to be handovered to a different cell, even if that cell is not the best cell for these mobile station. In case that a mobile station needs to handover to prevent a loss of radio link, it makes sense to handover it to a cell that is congested afterwards. In this situation, a different mobile station on the congested target cell can be handovered to a non-congested neighbor cell, to solve or balance the congestion. Because congestion of TCH/F and TCH/H is handled individually or bad quality might require change from TCH/H to TCH/F, assignment instead of handover might be used to change the channel within the same BTS. This new algorithm handles both assigment and handover depending on the target BTS. --- openbsc/include/openbsc/gsm_data.h | 2 +- openbsc/include/openbsc/handover.h | 7 +- openbsc/include/openbsc/handover_decision.h | 4 + openbsc/src/libbsc/Makefile.am | 2 +- openbsc/src/libbsc/handover_decision_1.c | 11 +- openbsc/src/libbsc/handover_decision_2.c | 1635 +++++++++++++++++++++++++++ openbsc/src/libbsc/handover_logic.c | 155 ++- openbsc/src/libbsc/handover_vty.c | 4 +- openbsc/src/libcommon/gsm_data.c | 18 +- openbsc/src/libmsc/vty_interface_layer3.c | 3 +- 10 files changed, 1798 insertions(+), 43 deletions(-) create mode 100644 openbsc/src/libbsc/handover_decision_2.c diff --git a/openbsc/include/openbsc/gsm_data.h b/openbsc/include/openbsc/gsm_data.h index 94bdfa1be..0928d0262 100644 --- a/openbsc/include/openbsc/gsm_data.h +++ b/openbsc/include/openbsc/gsm_data.h @@ -96,7 +96,7 @@ struct neigh_meas_proc { uint8_t bsic; uint8_t rxlev[MAX_WIN_NEIGH_AVG]; unsigned int rxlev_cnt; - uint8_t last_seen_nr; + int last_seen_nr; }; /* penalty timers for handover */ diff --git a/openbsc/include/openbsc/handover.h b/openbsc/include/openbsc/handover.h index 5ba8e1641..587161893 100644 --- a/openbsc/include/openbsc/handover.h +++ b/openbsc/include/openbsc/handover.h @@ -6,10 +6,15 @@ struct gsm_subscriber_connection; /* Count number of currently ongoing async handovers */ int bsc_ho_count(struct gsm_bts *bts); +/* Add a penalty timer to connection for the given BTS */ +void add_penalty_timer(struct gsm_subscriber_connection *conn, + struct gsm_bts *bts, int timeout); + /* Hand over the specified logical channel to the specified new BTS. * This is the main entry point for the actual handover algorithm, * after it has decided it wants to initiate HO to a specific BTS */ -int bsc_handover_start(struct gsm_lchan *old_lchan, struct gsm_bts *bts); +int bsc_handover_start(struct gsm_lchan *old_lchan, struct gsm_bts *bts, + int full_rate); /* clear any operation for this connection */ void bsc_clear_handover(struct gsm_subscriber_connection *conn, int free_lchan); diff --git a/openbsc/include/openbsc/handover_decision.h b/openbsc/include/openbsc/handover_decision.h index 2ea17f1be..953031d34 100644 --- a/openbsc/include/openbsc/handover_decision.h +++ b/openbsc/include/openbsc/handover_decision.h @@ -8,5 +8,9 @@ void bsc_vty_init_handover(void); void init_ho_1(void); +void congestion_check_2(void *data); +void init_ho_2(void); +void init_ho_timer_2(void); + #endif /* _HANDOVER_DECISION_H */ diff --git a/openbsc/src/libbsc/Makefile.am b/openbsc/src/libbsc/Makefile.am index fa5d6bdfe..77e2d884e 100644 --- a/openbsc/src/libbsc/Makefile.am +++ b/openbsc/src/libbsc/Makefile.am @@ -16,7 +16,7 @@ libbsc_a_SOURCES = abis_nm.c abis_nm_vty.c \ bts_sysmobts.c \ chan_alloc.c \ gsm_subscriber_base.c \ - handover_decision_1.c \ + handover_decision_1.c handover_decision_2.c \ handover_vty.c handover_logic.c meas_rep.c \ rest_octets.c system_information.c \ e1_config.c \ diff --git a/openbsc/src/libbsc/handover_decision_1.c b/openbsc/src/libbsc/handover_decision_1.c index 1151c0b24..391a5c9a9 100644 --- a/openbsc/src/libbsc/handover_decision_1.c +++ b/openbsc/src/libbsc/handover_decision_1.c @@ -48,7 +48,8 @@ static int handover_to_arfcn_bsic(struct gsm_lchan *lchan, } /* and actually try to handover to that cell */ - return bsc_handover_start(lchan, new_bts); + return bsc_handover_start(lchan, new_bts, + (lchan->type != GSM_LCHAN_TCH_H)); } /* did we get a RXLEV for a given cell in the given report? */ @@ -76,13 +77,6 @@ static int neigh_meas_avg(struct neigh_meas_proc *nmp, int window) unsigned int i, idx; int avg = 0; - /* reduce window to the actual number of existing measurements */ - if (window < nmp->rxlev_cnt) - window = nmp->rxlev_cnt; - /* this should never happen */ - if (window == 0) - return 0; - idx = calc_initial_idx(ARRAY_SIZE(nmp->rxlev), nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev), window); @@ -161,7 +155,6 @@ static void process_meas_neigh(struct gsm_meas_rep *mr) nmp->arfcn = mrc->arfcn; nmp->bsic = mrc->bsic; - nmp->rxlev_cnt = 0; idx = nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev); nmp->rxlev[idx] = mrc->rxlev; nmp->rxlev_cnt++; diff --git a/openbsc/src/libbsc/handover_decision_2.c b/openbsc/src/libbsc/handover_decision_2.c new file mode 100644 index 000000000..7aba421d6 --- /dev/null +++ b/openbsc/src/libbsc/handover_decision_2.c @@ -0,0 +1,1635 @@ +/* Handover Decision making for Inter-BTS (Intra-BSC) Handover. This + * only implements the handover algorithm/decision, but not execution + * of it */ + +/* (C) 2009 by Harald Welte + * + * 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 . + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern struct gsm_network *bsc_gsmnet; + +#define REQUIREMENT_A_TCHF 0x01 +#define REQUIREMENT_B_TCHF 0x02 +#define REQUIREMENT_C_TCHF 0x04 +#define REQUIREMENT_A_TCHH 0x10 +#define REQUIREMENT_B_TCHH 0x20 +#define REQUIREMENT_C_TCHH 0x40 +#define REQUIREMENT_TCHF_MASK 0x0f +#define REQUIREMENT_TCHH_MASK 0xf0 +#define REQUIREMENT_A_MASK 0x11 +#define REQUIREMENT_B_MASK 0x22 +#define REQUIREMENT_C_MASK 0x44 + +struct ho_candidate { + struct gsm_lchan *lchan; /* candidate for whom */ + struct gsm_bts *bts; /* target BTS */ + uint8_t requirements; /* what is fulfilled */ + int avg; /* average RX level */ +}; + +enum ho_reason { + HO_REASON_INTERFERENCE, + HO_REASON_BAD_QUALITY, + HO_REASON_LOW_RXLEVEL, + HO_REASON_MAX_DISTANCE, + HO_REASON_BETTER_CELL, + HO_REASON_CONGESTION, +}; + +static enum ho_reason ho_reason; + +static const struct value_string ho_reason_names[] = { + { HO_REASON_INTERFERENCE, "interference (bad quality)" }, + { HO_REASON_BAD_QUALITY, "bad quality" }, + { HO_REASON_LOW_RXLEVEL, "low rxlevel" }, + { HO_REASON_MAX_DISTANCE, "maximum allowed distance" }, + { HO_REASON_BETTER_CELL, "better cell" }, + { HO_REASON_CONGESTION, "congestion" }, + { 0, NULL } +}; + +static const char *get_ho_reason_name(int value) +{ + return get_value_string(ho_reason_names, value); +} + +void log_bts_subscr(int subsys, int level, struct gsm_lchan *lchan) +{ + LOGP(subsys, level, "(BTS %d subscr %s) ", lchan->ts->trx->bts->nr, + subscr_name(lchan->conn->subscr)); +} + +/* did we get a RXLEV for a given cell in the given report? */ +static int rxlev_for_cell_in_rep(struct gsm_meas_rep *mr, + uint16_t arfcn, uint8_t bsic) +{ + int i; + + for (i = 0; i < mr->num_cell; i++) { + struct gsm_meas_rep_cell *mrc = &mr->cell[i]; + + /* search for matching report */ + if (!(mrc->arfcn == arfcn && mrc->bsic == bsic)) + continue; + + mrc->flags |= MRC_F_PROCESSED; + return mrc->rxlev; + } + return -ENODEV; +} + +/* obtain averaged rxlev for given neighbor */ +static int neigh_meas_avg(struct neigh_meas_proc *nmp, int window) +{ + unsigned int i, idx; + int avg = 0; + + /* reduce window to the actual number of existing measurements */ + if (window > nmp->rxlev_cnt) + window = nmp->rxlev_cnt; + /* this should never happen */ + if (window == 0) + return 0; + + idx = calc_initial_idx(ARRAY_SIZE(nmp->rxlev), + nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev), + window); + + for (i = 0; i < window; i++) { + int j = (idx+i) % ARRAY_SIZE(nmp->rxlev); + + avg += nmp->rxlev[j]; + } + + return avg / window; +} + +/* find empty or evict bad neighbor */ +static struct neigh_meas_proc *find_evict_neigh(struct gsm_lchan *lchan) +{ + int j, worst = 999999; + struct neigh_meas_proc *nmp_worst = NULL; + + /* first try to find an empty/unused slot */ + for (j = 0; j < ARRAY_SIZE(lchan->neigh_meas); j++) { + struct neigh_meas_proc *nmp = &lchan->neigh_meas[j]; + if (!nmp->arfcn) + return nmp; + } + + /* no empty slot found. evict worst neighbor from list */ + for (j = 0; j < ARRAY_SIZE(lchan->neigh_meas); j++) { + struct neigh_meas_proc *nmp = &lchan->neigh_meas[j]; + int avg = neigh_meas_avg(nmp, MAX_WIN_NEIGH_AVG); + if (!nmp_worst || avg < worst) { + worst = avg; + nmp_worst = nmp; + } + } + + return nmp_worst; +} + +/* process neighbor cell measurement reports */ +static void process_meas_neigh(struct gsm_meas_rep *mr) +{ + int i, j, idx; + + /* for each reported cell, try to update global state */ + for (j = 0; j < ARRAY_SIZE(mr->lchan->neigh_meas); j++) { + struct neigh_meas_proc *nmp = &mr->lchan->neigh_meas[j]; + unsigned int idx; + int rxlev; + + /* skip unused entries */ + if (!nmp->arfcn) + continue; + + rxlev = rxlev_for_cell_in_rep(mr, nmp->arfcn, nmp->bsic); + idx = nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev); + if (rxlev >= 0) { + nmp->rxlev[idx] = rxlev; + nmp->last_seen_nr = mr->lchan->meas_rep_cnt - 1; + } else + nmp->rxlev[idx] = 0; + nmp->rxlev_cnt++; + } + + /* iterate over list of reported cells, check if we did not + * process all of them */ + for (i = 0; i < mr->num_cell; i++) { + struct gsm_meas_rep_cell *mrc = &mr->cell[i]; + struct neigh_meas_proc *nmp; + + if (mrc->flags & MRC_F_PROCESSED) + continue; + + nmp = find_evict_neigh(mr->lchan); + + nmp->arfcn = mrc->arfcn; + nmp->bsic = mrc->bsic; + + nmp->rxlev_cnt = 0; + idx = nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev); + nmp->rxlev[idx] = mrc->rxlev; + nmp->rxlev_cnt++; + nmp->last_seen_nr = mr->lchan->meas_rep_cnt - 1; + + mrc->flags |= MRC_F_PROCESSED; + } +} + +/* + * Check what requirements the given cell fulfills. + * A bit mask of fulfilled requirements is returned. + * + * Target cell requirement A + * + * In order to successfully handover/assign to a better cell, the target cell + * must be able to continue the current call. Therefore the cell must fulfill + * the following criteria: + * + * * The handover must be enabled for the target cell, if it differs from the + * originating cell. + * * The assignment must be enabled for the cell, if it equals the current + * cell. + * * The handover penalty timer must not run for the cell. + * * If FR, EFR or HR codec is used, the cell must support this codec. + * * If FR or EFR codec is used, the cell must have a TCH/F slot type + * available. + * * If HR codec is used, the cell must have a TCH/H slot type available. + * * If AMR codec is used, the cell must have a TCH/F slot available, if AFS + * is supported by mobile and BTS. + * * If AMR codec is used, the cell must have a TCH/H slot available, if AHS + * is supported by mobile and BTS. + * * osmo-nitb with built-in MNCC application: + * o If AMR codec is used, the cell must support AMR codec with equal codec + * rate or rates. (not meaning TCH types) + * * If defined, the number of maximum unsynchronized handovers to this cell + * may not be exceeded. (This limits processing load for random access + * bursts.) + * + * + * Target cell requirement B + * + * In order to prevent congestion of a target cell, the cell must fulfill the + * requirement A, but also: + * + * * The minimum free channels, that are defined for that cell must be + * maintained after handover/assignment. + * * The minimum free channels are defined for TCH/F and TCH/H slot types + * individually. + * + * + * Target cell requirement C + * + * In order to balance congested cells, the target cell must fulfill the + * requirement A, but also: + * + * * The target cell (which is congested also) must have more or equal free + * slots after handover/assignment. + * * The number of free slots are checked for TCH/F and TCH/H slot types + * individually. + */ +static uint8_t check_requirements(struct gsm_lchan *lchan, struct gsm_bts *bts, + int tchf_count, int tchh_count) +{ + time_t now; + int count; + uint8_t requirement = 0; + struct ho_penalty_timer *timer; + struct gsm_bts *current_bts = lchan->ts->trx->bts; + struct gsm_mncc_bearer_cap *bcap; + int i; + + /* Requirement A */ + + /* the handover/assignment must not be disabled */ + if (current_bts == bts) { + if (!bts->handover.as_active) { + LOGP(DHODEC, LOGL_DEBUG, " - BTS %d is not a " + "candidate, because assignment is not " + "enabled\n", bts->nr); + return 0; + } + } else { + if (!bts->handover.ho_active) { + LOGP(DHODEC, LOGL_DEBUG, " - BTS %d is not a " + "candidate, because handover is not enabled " + "there\n", + bts->nr); + return 0; + } + } + + /* the handover penalty timer must not run for this bts */ + time(&now); + llist_for_each_entry(timer, &lchan->conn->ho_penalty_timers, entry) { + if (timer->bts != bts->nr) + continue; + if (now < timer->timeout) { + LOGP(DHODEC, LOGL_DEBUG, " - BTS %d is not a " + "candidate, because penalty timer is running " + "(%ld seconds left)\n", bts->nr, + timer->timeout - now); + return 0; + } + } + + /* compatibility check for codecs. + * if so, the candidates for full rate and half rate are selected */ + switch (lchan->tch_mode) { + case GSM48_CMODE_SPEECH_V1: + switch (lchan->type) { + case GSM_LCHAN_TCH_F: /* mandatory */ + requirement |= REQUIREMENT_A_TCHF; + break; + case GSM_LCHAN_TCH_H: + if (bts->codec.hr) + requirement |= REQUIREMENT_A_TCHH; + break; + default: + LOGP(DHODEC, LOGL_DEBUG, " - BTS %d is not a " + "candidate, because channel type is not TCH\n", + bts->nr); + return 0; + } + break; + case GSM48_CMODE_SPEECH_EFR: + if (bts->codec.efr) + requirement |= REQUIREMENT_A_TCHF; + break; + case GSM48_CMODE_SPEECH_AMR: + /* only select AFS/AHS, if supported by the MS */ + bcap = &lchan->conn->bcap; + for (i = 0; bcap->speech_ver[i] >= 0; i++) { + if (bcap->speech_ver[i] == 4 && bts->codec.afs) + requirement |= REQUIREMENT_A_TCHF; + if (bcap->speech_ver[i] == 5 && bts->codec.ahs) + requirement |= REQUIREMENT_A_TCHH; + } + break; + default: + LOGP(DHODEC, LOGL_DEBUG, " - BTS %d is not a candidate, " + "because current channel mode is not SPEECH\n", + bts->nr); + return 0; + } + + /* no candidate, because new cell is incompatible */ + if (!requirement) { + LOGP(DHODEC, LOGL_DEBUG, " - BTS %d is not a candidate, " + "because codec of MS and BTS are incompatible\n", + bts->nr); + return 0; + } + + /* count available slots and remove slot types that are not available */ + if ((requirement & REQUIREMENT_A_TCHF) && !tchf_count) + requirement &= ~(REQUIREMENT_A_TCHF); + if ((requirement & REQUIREMENT_A_TCHH) && !tchh_count) + requirement &= ~(REQUIREMENT_A_TCHH); + + /* no candidate, because new cell has not free slots */ + if (!requirement) { + LOGP(DHODEC, LOGL_DEBUG, " - BTS %d is not a candidate, " + "because there is no (suitable) slot free\n", + bts->nr); + return 0; + } + + /* omit same channel type on same BTS (will not change anything) */ + if (bts == current_bts) { + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + requirement &= ~(REQUIREMENT_A_TCHF); + break; + case GSM_LCHAN_TCH_H: + requirement &= ~(REQUIREMENT_A_TCHH); + break; + default: + break; + } + } + + /* no candidate, because no different slot for improvement */ + if (!requirement) { + LOGP(DHODEC, LOGL_DEBUG, " - BTS %d is not a candidate, " + "because no diffrent slot for improvement available\n", + bts->nr); + return 0; + } + + /* built-in call control requires equal codec rates. + * remove rates that are not equal. */ + if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR + && current_bts->network->mncc_recv != mncc_sock_from_cc) { + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + if ((requirement & REQUIREMENT_A_TCHF) + && !!memcmp(¤t_bts->mr_full, &bts->mr_full, + sizeof(struct amr_multirate_conf))) + requirement &= ~(REQUIREMENT_A_TCHF); + if ((requirement & REQUIREMENT_A_TCHH) + && !!memcmp(¤t_bts->mr_full, &bts->mr_half, + sizeof(struct amr_multirate_conf))) + requirement &= ~(REQUIREMENT_A_TCHH); + break; + case GSM_LCHAN_TCH_H: + if ((requirement & REQUIREMENT_A_TCHF) + && !!memcmp(¤t_bts->mr_half, &bts->mr_full, + sizeof(struct amr_multirate_conf))) + requirement &= ~(REQUIREMENT_A_TCHF); + if ((requirement & REQUIREMENT_A_TCHH) + && !!memcmp(¤t_bts->mr_half, &bts->mr_half, + sizeof(struct amr_multirate_conf))) + requirement &= ~(REQUIREMENT_A_TCHH); + break; + default: + break; + } + } + + /* no candidate, because new cell's AMR rates are incompatible */ + if (!requirement) { + LOGP(DHODEC, LOGL_DEBUG, " - BTS %d is not a candidate, " + "because built-in MNCC doesn't privide equal AMR " + "rates\n", bts->nr); + return 0; + } + + /* the maximum number of unsynchonized handovers must no be exceeded */ + if (current_bts != bts + && bsc_ho_count(bts) >= bts->handover.max_unsync_ho) { + LOGP(DHODEC, LOGL_DEBUG, " - BTS %d is not a candidate, " + "because maximum number of allowed handovers would be " + "exceeded\n", + bts->nr); + return 0; + } + + /* Requirement B */ + + /* the minimum free timeslots that are defined for this cell must + * be maintained _after_ handover/assignment */ + if ((requirement & REQUIREMENT_A_TCHF) + && tchf_count - 1 >= bts->handover.min_free_tchf) + requirement |= REQUIREMENT_B_TCHF; + if ((requirement & REQUIREMENT_A_TCHH) + && tchh_count - 1 >= bts->handover.min_free_tchh) + requirement |= REQUIREMENT_B_TCHH; + + /* Requirement C */ + + /* the free timeslots of the target cell must be more or qual to the + * free slots of the current cell _after_ handover/assignment */ + count = lc_count_bts(current_bts, (lchan->type == GSM_LCHAN_TCH_H) ? + GSM_PCHAN_TCH_H : GSM_PCHAN_TCH_F); + if ((requirement & REQUIREMENT_A_TCHF) && tchf_count - 1 >= count + 1) + requirement |= REQUIREMENT_C_TCHF; + if ((requirement & REQUIREMENT_A_TCHH) && tchh_count - 1 >= count + 1) + requirement |= REQUIREMENT_C_TCHH; + + /* return mask of fulfilled requirements */ + return requirement; +} + +/* Trigger handover or assignment depending on the target BTS */ +static int trigger_handover_or_assignment(struct gsm_lchan *lchan, + struct gsm_bts *new_bts, uint8_t requirements) +{ + struct gsm_bts *current_bts = lchan->ts->trx->bts; + int improve_afs = 0; + int full_rate = 0; + + log_bts_subscr(DHODEC, LOGL_NOTICE, lchan); + + /* improve_afs becomes > 0, if AFS is used and is improved */ + if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) + improve_afs = new_bts->handover.afs_rxlev_improve; + + /* select TCH rate, prefer TCH/F if AFS is improved */ + switch (lchan->type) { + case GSM_LCHAN_TCH_F: + /* keep on full rate, if a candidate */ + if ((requirements & REQUIREMENT_TCHF_MASK)) { + if (current_bts == new_bts) { + LOGPC(DHODEC, LOGL_INFO, "Not performing " + "assignment: Already on target type\n"); + return 0; + } + full_rate = 1; + break; + } + /* change to half rate */ + if (!(requirements & REQUIREMENT_TCHH_MASK)) { + LOGPC(DHODEC, LOGL_ERROR, "Not performing assignment: " + "No requirement given, please fix!\n"); + return -EINVAL; + } + break; + case GSM_LCHAN_TCH_H: + /* change to full rate if AFS is improved and a candidate */ + if (improve_afs > 0 && (requirements & REQUIREMENT_TCHF_MASK)) { + full_rate = 1; + LOGPC(DHODEC, LOGL_INFO, "[Improve AHS->AFS] "); + break; + } + /* change to full rate if the only candidate */ + if ((requirements & REQUIREMENT_TCHF_MASK) + && !(requirements & REQUIREMENT_TCHH_MASK)) { + full_rate = 1; + break; + } + /* keep on half rate */ + if (!(requirements & REQUIREMENT_TCHH_MASK)) { + LOGPC(DHODEC, LOGL_ERROR, "Not performing assignment: " + "No requirement given, please fix!\n"); + return -EINVAL; + } + if (current_bts == new_bts) { + LOGPC(DHODEC, LOGL_INFO, "Not performing assignment: " + "Already on target type\n"); + return 0; + } + break; + default: + LOGPC(DHODEC, LOGL_ERROR, "Not performing handover nor " + "assignment: lchan type no TCH, please fix!\n"); + return -EINVAL; + } + + /* trigger handover or asignment */ + if (current_bts == new_bts) { + LOGPC(DHODEC, LOGL_NOTICE, "Trigger assignment due to %s to " + "%s.\n", get_ho_reason_name(ho_reason), + (full_rate) ? "TCH/F" : "TCH/H"); + return bsc_handover_start(lchan, NULL, full_rate); + } else { + LOGPC(DHODEC, LOGL_NOTICE, "Trigger handover to BTS %d due to " + "%s to %s.\n", new_bts->nr, + get_ho_reason_name(ho_reason), + (full_rate) ? "TCH/F" : "TCH/H"); + return bsc_handover_start(lchan, new_bts, full_rate); + } + + return 0; +} + +/* debug collected candidates */ +static inline void debug_candidate(struct ho_candidate *candidate, + int neighbor, int8_t rxlev, int tchf_count, int tchh_count) +{ + if (!candidate->requirements) + return; + + if (neighbor) + LOGP(DHODEC, LOGL_DEBUG, " - neighbor BTS %d, RX level " + "%d -> %d\n", candidate->bts->nr, rxlev2dbm(rxlev), + rxlev2dbm(candidate->avg)); + else + LOGP(DHODEC, LOGL_DEBUG, " - current BTS %d, RX level %d\n", + candidate->bts->nr, rxlev2dbm(candidate->avg)); + + LOGP(DHODEC, LOGL_DEBUG, " o free TCH/F slots %d, minimum required " + "%d\n", tchf_count, candidate->bts->handover.min_free_tchf); + LOGP(DHODEC, LOGL_DEBUG, " o free TCH/H slots %d, minimum required " + "%d\n", tchh_count, candidate->bts->handover.min_free_tchh); + + if ((candidate->requirements & REQUIREMENT_TCHF_MASK)) + LOGP(DHODEC, LOGL_DEBUG, " o requirement "); + else + LOGP(DHODEC, LOGL_DEBUG, " o no requirement "); + if ((candidate->requirements & REQUIREMENT_A_TCHF)) + LOGPC(DHODEC, LOGL_DEBUG, "A "); + if ((candidate->requirements & REQUIREMENT_B_TCHF)) + LOGPC(DHODEC, LOGL_DEBUG, "B "); + if ((candidate->requirements & REQUIREMENT_C_TCHF)) + LOGPC(DHODEC, LOGL_DEBUG, "C "); + LOGPC(DHODEC, LOGL_DEBUG, "fulfilled for TCHF"); + if (!(candidate->requirements & REQUIREMENT_TCHF_MASK)) /* nothing */ + LOGPC(DHODEC, LOGL_DEBUG, " (no %s possible)\n", + (neighbor) ? "handover" : "assignment"); + else if ((candidate->requirements & REQUIREMENT_TCHF_MASK) + == REQUIREMENT_A_TCHF) /* only A */ + LOGPC(DHODEC, LOGL_DEBUG, " (more congestion after %s)\n", + (neighbor) ? "handover" : "assignment"); + else if ((candidate->requirements & REQUIREMENT_B_TCHF)) /* B incl. */ + LOGPC(DHODEC, LOGL_DEBUG, " (not congested after %s)\n", + (neighbor) ? "handover" : "assignment"); + else /* so it must include C */ + LOGPC(DHODEC, LOGL_DEBUG, " (less or equally congested after " + "%s)\n", (neighbor) ? "handover" : "assignment"); + + if ((candidate->requirements & REQUIREMENT_TCHH_MASK)) + LOGP(DHODEC, LOGL_DEBUG, " o requirement "); + else + LOGP(DHODEC, LOGL_DEBUG, " o no requirement "); + if ((candidate->requirements & REQUIREMENT_A_TCHH)) + LOGPC(DHODEC, LOGL_DEBUG, "A "); + if ((candidate->requirements & REQUIREMENT_B_TCHH)) + LOGPC(DHODEC, LOGL_DEBUG, "B "); + if ((candidate->requirements & REQUIREMENT_C_TCHH)) + LOGPC(DHODEC, LOGL_DEBUG, "C "); + LOGPC(DHODEC, LOGL_DEBUG, "fulfilled for TCHH"); + if (!(candidate->requirements & REQUIREMENT_TCHH_MASK)) /* nothing */ + LOGPC(DHODEC, LOGL_DEBUG, " (no %s possible)\n", + (neighbor) ? "handover" : "assignment"); + else if ((candidate->requirements & REQUIREMENT_TCHH_MASK) + == REQUIREMENT_A_TCHH) /* only A */ + LOGPC(DHODEC, LOGL_DEBUG, " (more congestion after %s)\n", + (neighbor) ? "handover" : "assignment"); + else if ((candidate->requirements & REQUIREMENT_B_TCHH)) /* B incl. */ + LOGPC(DHODEC, LOGL_DEBUG, " (not congested after %s)\n", + (neighbor) ? "handover" : "assignment"); + else /* so it must include C */ + LOGPC(DHODEC, LOGL_DEBUG, " (less or equally congested after " + "%s)\n", (neighbor) ? "handover" : "assignment"); +} + +/* subroutine for collecting candidates of one lchan */ +static int collect_candidates_for_lchan(struct gsm_lchan *lchan, + struct ho_candidate *clist, int *_av_rxlev, int check_hyst, + int ign_level) +{ + struct gsm_bts *current_bts = lchan->ts->trx->bts; + int candidates = 0, i; + int av_rxlev; + int tchf_count, tchh_count; + + /* caculate average rxlev for this cell over the window */ + av_rxlev = get_meas_rep_avg(lchan, (current_bts->handover.full) ? + MEAS_REP_DL_RXLEV_FULL : MEAS_REP_DL_RXLEV_SUB, + current_bts->handover.win_rxlev_avg); + if (_av_rxlev) + *_av_rxlev = av_rxlev; + + LOGP(DHODEC, LOGL_DEBUG, "Collecting candidates (for subscr. %s):\n", + subscr_name(lchan->conn->subscr)); + + /* in case there is no measurment report (yet) */ + if (av_rxlev < 0) { + LOGP(DHODEC, LOGL_DEBUG, "- Omitting, because not enough " + "measurements yet\n"); + return 0; + } + + /* check requirements for current cell */ + if (current_bts->handover.as_active) { + tchf_count = lc_count_bts(current_bts, GSM_PCHAN_TCH_F); + tchh_count = lc_count_bts(current_bts, GSM_PCHAN_TCH_H); + clist[candidates].lchan = lchan; + clist[candidates].bts = current_bts; + clist[candidates].requirements = check_requirements(lchan, + current_bts, tchf_count, tchh_count); + clist[candidates].avg = av_rxlev; + debug_candidate(&clist[candidates], 0, 0, tchf_count, + tchh_count); + candidates++; + } else { + LOGP(DHODEC, LOGL_DEBUG, " - Assignment for BTS %u disabled\n", + current_bts->nr); + } + + /* check requirements for all neighbor cells */ + if (current_bts->handover.ho_active) { + int n = 0; + for (i = 0; i < ARRAY_SIZE(lchan->neigh_meas); i++) { + struct neigh_meas_proc *nmp = &lchan->neigh_meas[i]; + struct gsm_bts *neighbor_bts; + int avg; + + /* skip empty slots */ + if (nmp->arfcn == 0) + continue; + + /* skip if measurement report is old */ + if (nmp->last_seen_nr != lchan->meas_rep_cnt - 1) + continue; + + neighbor_bts = gsm_bts_neighbor(current_bts, nmp->arfcn, + nmp->bsic); + if (!neighbor_bts) + continue; + + /* in case we have measurements of our bts, due to + * missconfiguration */ + if (neighbor_bts == current_bts) + continue; + + n++; + + /* caculate average rxlev for this cell over the + * window */ + avg = neigh_meas_avg(nmp, + current_bts->handover.win_rxlev_avg_neigh); + + /* check if rx level is better level */ + if (!ign_level && check_hyst + && avg <= av_rxlev + + current_bts->handover.pwr_hysteresis) { + LOGP(DHODEC, LOGL_DEBUG, " - BTS %d is not a " + "candidate, because RX level (%d) is " + "lower or equal than current RX level " + "(%d) + hysteresis (%d)\n", + neighbor_bts->nr, rxlev2dbm(avg), + rxlev2dbm(av_rxlev), + current_bts->handover.pwr_hysteresis); + continue; + } else + if (!ign_level && avg <= av_rxlev) { + LOGP(DHODEC, LOGL_DEBUG, " - BTS %d is not a " + "candidate, because RX level (%d) is " + "lower or equal than current RX level " + "(%d)\n", neighbor_bts->nr, + rxlev2dbm(avg), rxlev2dbm(av_rxlev)); + continue; + } + + /* if the minimum level is not reached */ + if (rxlev2dbm(avg) + < neighbor_bts->handover.min_rxlev) { + LOGP(DHODEC, LOGL_DEBUG, " - BTS %d is not a " + "candidate, because RX level (%d) is " + "below minimum required RX level " + "(%d)\n", neighbor_bts->nr, + rxlev2dbm(avg), + neighbor_bts->handover.min_rxlev); + continue; + } + + tchf_count = lc_count_bts(neighbor_bts, GSM_PCHAN_TCH_F); + tchh_count = lc_count_bts(neighbor_bts, GSM_PCHAN_TCH_H); + clist[candidates].lchan = lchan; + clist[candidates].bts = neighbor_bts; + clist[candidates].requirements = check_requirements( + lchan, neighbor_bts, tchf_count, tchh_count); + clist[candidates].avg = avg; + debug_candidate(&clist[candidates], 1, av_rxlev, + tchf_count, tchh_count); + candidates++; + } + if (!n) { + LOGP(DHODEC, LOGL_DEBUG, " - No neighbor cells at " + "measurement report\n"); + } + } else { + LOGP(DHODEC, LOGL_DEBUG, " - No neighbor cells, because " + "handover is disabled in this cell\n"); + } + + return candidates; +} + +/* + * Search for a better cell + * + * Do not trigger handover/assignment on slots which have already ongoing + * handover/assignment processes. If no AFS improvement offset is given, try to + * maintain the same TCH rate, if available. + * Do not perform this process, if handover and assignment are disabled for + * the current cell. + * Do not perform handover, if the minimum acceptable RX level + * is not reched for this cell. + * + * If one or more 'better cells' are available, check the current and neighbor + * cell measurements in descending order of their RX levels (down-link): + * + * * Select the best candidate that fulfills requirement B (no congestion + * after handover/assignment) and trigger handover or assignment. + * * If no candidate fulfills requirement B, select the best candidate that + * fulfills requirement C (less or equally congested cells after handover) + * and trigger handover or assignment. + * * If no candidate fulfills requirement C, do not perform handover nor + * assignment. + * + * If the RX level (down-link) or RX quality (down-link) of the current cell is + * below minimum acceptable level, or if the maximum allowed timing advance is + * reached or exceeded, check the RX levels (down-link) of the current and + * neighbor cells in descending order of their levels: (bad BTS case) + * + * * Select the best candidate that fulfills requirement B (no congestion after + * handover/assignment) and trigger handover or assignment. + * * If no candidate fulfills requirement B, select the best candidate that + * fulfills requirement C (less or equally congested cells after handover) + * and trigger handover or assignment. + * * If no candidate fulfills requirement C, select the best candidate that + * fulfills requirement A (ignore congestion after handover or assignment) + * and trigger handover or assignment. + * * If no candidate fulfills requirement A, do not perform handover nor + * assignment. + * + * RX levels (down-link) of current and neighbor cells: + * + * * The RX levels of the current cell and neighbor cells are improved by a + * given offset, if AFS (AMR on TCH/F) is used or is a candidate for + * handover/assignment. + * * If AMR is used, the requirement for handover is checked for TCH/F and + * TCH/H. Both results (if any) are used as a candidate. + * * If AMR is used, the requirement for assignment to a different TCH slot + * rate is checked. The result (if available) is used as a candidate. + */ +static int better_cell(struct gsm_lchan *lchan, int better_cell_search, + int ign_level) +{ + struct gsm_bts *current_bts = lchan->ts->trx->bts; + int ahs = (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR + && lchan->type == GSM_LCHAN_TCH_H); + int av_rxlev; + struct ho_candidate clist[1 + ARRAY_SIZE(lchan->neigh_meas)]; + int candidates, i; + struct ho_candidate *best_cand = NULL; + unsigned int best_better_db; + int better; + int is_improved = 0; + + /* check for disabled handover/assignment at the current cell */ + if (!current_bts->handover.as_active + && !current_bts->handover.ho_active) { + LOGP(DHODEC, LOGL_INFO, "Skipping, Handover and Assignment is " + "disabled in this cell\n"); + return 0; + } + + /* collect candidates */ + candidates = collect_candidates_for_lchan(lchan, clist, &av_rxlev, + better_cell_search, ign_level); + + /* if no candiate (may happen if assignment is disabled and no + * neighbor cell report exists) */ + if (candidates == 0) + goto no_cand; + + /* select best candidate that fulfills requirement B */ + best_better_db = 0; + for (i = 0; i < candidates; i++) { + if (!(clist[i].requirements & REQUIREMENT_B_MASK)) + continue; + + better = clist[i].avg - av_rxlev; + /* improve AHS */ + if (ahs && (clist[i].requirements & REQUIREMENT_B_TCHF)) { + better += clist[i].bts->handover.afs_rxlev_improve; + is_improved = 1; + } else + is_improved = 0; + if (better > best_better_db) { + best_cand = &clist[i]; + best_better_db = better; + } + } + + /* perform handover, if there is a candidate */ + if (best_cand) { + LOGP(DHODEC, LOGL_INFO, "Best candidate BTS %d (RX level %d) " + "without congestion after handover found.\n", + best_cand->bts->nr, rxlev2dbm(best_cand->avg)); + if (is_improved) + LOGP(DHODEC, LOGL_INFO, "(is improved due to " + "AHS -> AFS)\n"); + return trigger_handover_or_assignment(lchan, best_cand->bts, + best_cand->requirements & REQUIREMENT_B_MASK); + } + + /* select best candidate that fulfills requirement C */ + best_better_db = 0; + for (i = 0; i < candidates; i++) { + if (!(clist[i].requirements & REQUIREMENT_C_MASK)) + continue; + + better = clist[i].avg - av_rxlev; + /* improve AHS */ + if (ahs && (clist[i].requirements & REQUIREMENT_C_TCHF)) { + better += clist[i].bts->handover.afs_rxlev_improve; + is_improved = 1; + } else + is_improved = 0; + if (better > best_better_db) { + best_cand = &clist[i]; + best_better_db = better; + } + } + + /* perform handover, if there is a candidate */ + if (best_cand) { + LOGP(DHODEC, LOGL_INFO, "Best candidate BTS %d (RX level %d) " + "with less or equal congestion after handover found.\n", + best_cand->bts->nr, rxlev2dbm(best_cand->avg)); + if (is_improved) + LOGP(DHODEC, LOGL_INFO, "(is improved due to " + "AHS -> AFS)\n"); + return trigger_handover_or_assignment(lchan, best_cand->bts, + best_cand->requirements & REQUIREMENT_C_MASK); + } + + /* we are done in case of searching a better cell */ + if (better_cell_search) + goto no_cand; + + /* select best candidate that fulfills requirement A */ + best_better_db = 0; + for (i = 0; i < candidates; i++) { + if (!(clist[i].requirements & REQUIREMENT_A_MASK)) + continue; + + better = clist[i].avg - av_rxlev; + /* improve AHS */ + if (ahs && (clist[i].requirements & REQUIREMENT_A_TCHF)) { + better += clist[i].bts->handover.afs_rxlev_improve; + is_improved = 1; + } else + is_improved = 0; + if (better > best_better_db) { + best_cand = &clist[i]; + best_better_db = better; + } + } + + /* perform handover, if there is a candidate */ + if (best_cand) { + LOGP(DHODEC, LOGL_INFO, "Best candidate BTS %d (RX level %d) " + "with greater congestion found.\n", best_cand->bts->nr, + rxlev2dbm(best_cand->avg)); + if (is_improved) + LOGP(DHODEC, LOGL_INFO, "(is improved due to " + "AHS -> AFS)\n"); + return trigger_handover_or_assignment(lchan, best_cand->bts, + best_cand->requirements & REQUIREMENT_A_MASK); + } + +no_cand: + if (better_cell_search) + LOGP(DHODEC, LOGL_INFO, "No better candidate found\n"); + else + LOGP(DHODEC, LOGL_INFO, "No suitable candidate found\n"); + + return 0; +} + +/* + * Handover/assignment check, if measurement report is received + * + * Do not trigger handover/assignment on slots which have already ongoing + * handover/assignment processes. + * + * In case of handover triggered because maximum allowed timing advance is + * exceeded, the handover penalty timer is started for the originating cell. + * + */ +static int attempt_handover_after_mr(struct gsm_meas_rep *mr) +{ + struct gsm_lchan *lchan = mr->lchan; + struct gsm_bts *bts = lchan->ts->trx->bts; + int av_rxlev = -EINVAL, av_rxqual = -EINVAL; + int rc; + + /* we currently only do handover for TCH channels */ + switch (mr->lchan->type) { + case GSM_LCHAN_TCH_F: + case GSM_LCHAN_TCH_H: + break; + default: + return 0; + } + + /* parse actual neighbor cell info */ + if (mr->num_cell > 0 && mr->num_cell < 7) + process_meas_neigh(mr); + + /* check for ongoing handover/assignment */ + if (!lchan->conn) { + log_bts_subscr(DHODEC, LOGL_ERROR, lchan); + LOGPC(DHODEC, LOGL_ERROR, "Skipping, No subscriber " + "connection???\n"); + return 0; + } + if (lchan->conn->secondary_lchan) { + log_bts_subscr(DHODEC, LOGL_INFO, lchan); + LOGPC(DHODEC, LOGL_INFO, "Skipping, Initial Assignment is " + "still ongoing\n"); + return 0; + } + if (lchan->conn->ho_lchan) { + log_bts_subscr(DHODEC, LOGL_INFO, lchan); + LOGPC(DHODEC, LOGL_INFO, "Skipping, Handover already " + "triggered\n"); + return 0; + } + + /* get average levels. if not enought measurements yet, value is < 0 */ + av_rxlev = get_meas_rep_avg(lchan, (bts->handover.full) ? + MEAS_REP_DL_RXLEV_FULL : MEAS_REP_DL_RXLEV_SUB, + bts->handover.win_rxlev_avg); + av_rxqual = get_meas_rep_avg(lchan, (bts->handover.full) ? + MEAS_REP_DL_RXQUAL_FULL : MEAS_REP_DL_RXQUAL_SUB, + bts->handover.win_rxqual_avg); + if (av_rxlev < 0 && av_rxqual < 0) { + log_bts_subscr(DHODEC, LOGL_INFO, lchan); + LOGPC(DHODEC, LOGL_INFO, "Skipping, Not enough recent " + "measuements\n"); + return 0; + } + if (av_rxlev >= 0) { + log_bts_subscr(DHODEC, LOGL_DEBUG, lchan); + LOGPC(DHODEC, LOGL_DEBUG, "Measurement report: average " + "RX level = %d\n", rxlev2dbm(av_rxlev)); + } + if (av_rxqual >= 0) { + log_bts_subscr(DHODEC, LOGL_DEBUG, lchan); + LOGPC(DHODEC, LOGL_DEBUG, "Measurement report: average " + "RX quality = %d\n", av_rxqual); + } + + /* improve levels in case of AFS, if defined */ + if (lchan->type == GSM_LCHAN_TCH_F + && lchan->tch_mode == GSM48_CMODE_SPEECH_AMR) { + if (av_rxlev >= 0 && bts->handover.afs_rxlev_improve) { + int imp = av_rxlev + bts->handover.afs_rxlev_improve; + log_bts_subscr(DHODEC, LOGL_INFO, lchan); + LOGPC(DHODEC, LOGL_INFO, "Virtually improving RX " + "level from %d to %d, due to AFS improvement " + "setting\n", rxlev2dbm(av_rxlev), + rxlev2dbm(imp)); + av_rxlev = imp; + } + if (av_rxqual >= 0 && bts->handover.afs_rxqual_improve) { + int imp = av_rxqual - bts->handover.afs_rxqual_improve; + if (imp < 0) + imp = 0; + log_bts_subscr(DHODEC, LOGL_INFO, lchan); + LOGPC(DHODEC, LOGL_INFO, "Virtually improving RX " + "quality from %d to %d, due to AFS improvement " + "setting\n", rxlev2dbm(av_rxqual), + rxlev2dbm(imp)); + av_rxqual = imp; + } + } + + /* Bad Quality */ + if (av_rxqual >= 0 && av_rxqual > bts->handover.min_rxqual) { + if (rxlev2dbm(av_rxlev) > -85) { + ho_reason = HO_REASON_INTERFERENCE; + log_bts_subscr(DHODEC, LOGL_INFO, lchan); + LOGPC(DHODEC, LOGL_INFO, "Trying handover/assignment " + "due to interference (bad quality)\n"); + } else { + ho_reason = HO_REASON_BAD_QUALITY; + log_bts_subscr(DHODEC, LOGL_INFO, lchan); + LOGPC(DHODEC, LOGL_INFO, "Trying handover/assignment " + "due to bad quality\n"); + } + rc = better_cell(lchan, 0, 1); + if (lchan->conn->ho_lchan || lchan->conn->secondary_lchan) + return rc; + return 0; + } + + /* Low Level */ + if (av_rxlev >= 0 && rxlev2dbm(av_rxlev) < bts->handover.min_rxlev) { + ho_reason = HO_REASON_LOW_RXLEVEL; + log_bts_subscr(DHODEC, LOGL_INFO, lchan); + LOGPC(DHODEC, LOGL_INFO, "Trying handover/assignment due to " + "low level\n"); + rc = better_cell(lchan, 0, 1); + if (lchan->conn->ho_lchan || lchan->conn->secondary_lchan) + return rc; + return 0; + } + + /* Max Distance */ + if (lchan->meas_rep_cnt > 0 + && lchan->rqd_ta > bts->handover.max_distance) { + ho_reason = HO_REASON_MAX_DISTANCE; + log_bts_subscr(DHODEC, LOGL_INFO, lchan); + LOGPC(DHODEC, LOGL_INFO, "Trying handover due high TA\n"); + /* start penalty timer to prevent comming back too + * early. it must be started before selecting a better cell, + * so there is no assignment selected, due to running + * penalty timer. */ + add_penalty_timer(lchan->conn, bts, + bts->handover.penalty_max_dist); + rc = better_cell(lchan, 0, 1); + if (lchan->conn->ho_lchan || lchan->conn->secondary_lchan) + return rc; + return 0; + } + + /* try handover to a better cell */ + if (av_rxlev >= 0 && (mr->nr % bts->handover.pwr_interval) == 0) { + log_bts_subscr(DHODEC, LOGL_INFO, lchan); + LOGPC(DHODEC, LOGL_INFO, "Looking for better cell (with no, " + "less or equal congestion hafter handover)\n"); + ho_reason = HO_REASON_BETTER_CELL; + rc = better_cell(lchan, 1, 0); + if (lchan->conn->ho_lchan || lchan->conn->secondary_lchan) + return rc; + return 0; + } + + return 0; +} + +/* + * Handover/assignment check after timer timeout: + * + * Even if handover process tries to prevent a congestion, a cell might get + * congested due to new call setups or handovers to prevent loss of radio link. + * A cell is congested, if not the minimum number of free slots are available. + * The minimum number can be defined for TCH/F and TCH/H individually. + * + * Do not perform congestion check, if no minimum free slots are defined for + * a cell. + * Do not trigger handover/assignment on slots which have already ongoing + * handover/assignment processes. If no AFS improvement offset is given, try to + * maintain the same TCH rate, if available. + * Do not perform this process, if handover and assignment are disabled for + * the current cell. + * Do not perform handover, if the minimum acceptable RX level + * is not reched for this cell. + * Only check candidates that will solve/reduce congestion. + * + * If a cell is congested, all slots are checked for all their RX levels + * (down-link) of the current and neighbor cell measurements in descending + * order of their RX levels: + * + * * Select the best candidate that fulfills requirement B (no congestion after + * handover/assignment), trigger handover or assignment. Candidates that will + * cause an assignment from AHS (AMR on TCH/H) to AFS (AMR on TCH/F) are + * omitted. + * o This process repeated until the minimum required number of free slots + * are restored or if all cell measurements are checked. The process ends + * then, otherwise: + * * Select the worst candidate that fulfills requirement B, trigger + * assignment. Note that only assignment candidates for changing from AHS to + * AFS are left. + * o This process repeated until the minimum required number of free slots + * are restored or if all cell measurements are checked. The process ends + * then, otherwise: + * * Select the best candidates that fulfill requirement C (less or equally + * congested cells after handover/assignment), trigger handover or + * assignment. Candidates that will cause an assignment from AHS (AMR on + * TCH/H) to AFS (AMR on TCH/F) are omitted. + * o This process repeated until the minimum required number of free slots + * are restored or if all cell measurements are checked. The process ends + * then, otherwise: + * * Select the worst candidate that fulfills requirement C, trigger + * assignment. Note that only assignment candidates for changing from AHS to + * AFS are left. + * o This process repeated until the minimum required number of free slots + * are restored or if all cell measurements are checked. + */ +static int congestion_check_bts(struct gsm_bts *bts, int tchf_congestion, + int tchh_congestion) +{ + struct gsm_lchan *lc; + struct gsm_bts_trx *trx; + struct gsm_bts_trx_ts *ts; + int i, j; + struct ho_candidate *clist; + int candidates = 0; + struct ho_candidate *best_cand = NULL, *worst_cand = NULL; + struct gsm_lchan *delete_lchan = NULL; + unsigned int best_avg_db, worst_avg_db; + int avg; + int rc = 0; + int any_ho = 0; + int is_improved = 0; + + if (tchf_congestion < 0) + tchf_congestion = 0; + if (tchh_congestion < 0) + tchh_congestion = 0; + + LOGP(DHODEC, LOGL_INFO, "BTS %d is congested: %d TCH/F and %d TCH/H " + "must be moved\n", bts->nr, tchf_congestion, tchh_congestion); + + /* allocate array of all bts */ + clist = talloc_zero_array(tall_bsc_ctx, struct ho_candidate, + bts->num_trx * 8 * 2 * (1 + ARRAY_SIZE(lc->neigh_meas))); + if (!clist) + return 0; + + /* loop through all active lchan and collect candidates */ + llist_for_each_entry(trx, &bts->trx_list, list) { + if (!trx_is_usable(trx)) + continue; + + for (i = 0; i < 8; i++) { + ts = &trx->ts[i]; + if (!ts_is_usable(ts)) + continue; + switch (ts->pchan) { + case GSM_PCHAN_TCH_F_PDCH: + /* we can only consider such a dynamic channel + * if the PDCH is currently inactive */ + if (ts->flags & TS_F_PDCH_MODE) + break; + // fall through + case GSM_PCHAN_TCH_F: + lc = &ts->lchan[0]; + /* omit if channel not active */ + if (lc->type != GSM_LCHAN_TCH_F + || lc->state != LCHAN_S_ACTIVE) + break; + /* omit of there is an ongoing ho/as */ + if (!lc->conn || lc->conn->secondary_lchan + || lc->conn->ho_lchan) + break; + /* collect candidates */ + candidates += collect_candidates_for_lchan(lc, + &clist[candidates], NULL, 0, 1); + break; + case GSM_PCHAN_TCH_H: + for (j = 0; j < 2; j++) { + lc = &ts->lchan[j]; + /* omit if channel not active */ + if (lc->type != GSM_LCHAN_TCH_H + || lc->state != LCHAN_S_ACTIVE) + continue; + /* omit of there is an ongoing ho/as */ + if (!lc->conn + || lc->conn->secondary_lchan + || lc->conn->ho_lchan) + continue; + candidates += + collect_candidates_for_lchan(lc, + &clist[candidates], NULL, 0, 1); + } + break; + default: + break; + } + } + } + + /* if no candiate */ + if (candidates == 0) { + LOGP(DHODEC, LOGL_DEBUG, "No candidates at BTS %d to solve " + "congestion.\n", bts->nr); + goto exit; + } + +#if 0 +next_b1: +#endif + /* select best candidate that fulfills requirement B, + * omit change from AHS to AFS */ + best_avg_db = 0; + for (i = 0; i < candidates; i++) { + /* delete subscriber that just have handovered */ + if (clist[i].lchan == delete_lchan) + clist[i].lchan = NULL; + /* omit all subscribers that are handovered */ + if (!clist[i].lchan) + continue; + + if (!(clist[i].requirements & REQUIREMENT_B_MASK)) + continue; + /* omit assignment from AHS to AFS */ + if (clist[i].lchan->ts->trx->bts == clist[i].bts + && clist[i].lchan->type == GSM_LCHAN_TCH_H + && (clist[i].requirements & REQUIREMENT_B_TCHF)) + continue; + /* omit candidates that will not solve/reduce congestion */ + if (clist[i].lchan->type == GSM_LCHAN_TCH_F + && tchf_congestion <= 0) + continue; + if (clist[i].lchan->type == GSM_LCHAN_TCH_H + && tchh_congestion <= 0) + continue; + + avg = clist[i].avg; + /* improve AHS */ + if (clist[i].lchan->tch_mode == GSM48_CMODE_SPEECH_AMR + && clist[i].lchan->type == GSM_LCHAN_TCH_H + && (clist[i].requirements & REQUIREMENT_B_TCHF)) { + avg += clist[i].bts->handover.afs_rxlev_improve; + is_improved = 1; + } else + is_improved = 0; + if (avg > best_avg_db) { + best_cand = &clist[i]; + best_avg_db = avg; + } + } + + /* perform handover, if there is a candidate */ + if (best_cand) { + any_ho = 1; + log_bts_subscr(DHODEC, LOGL_INFO, best_cand->lchan); + LOGPC(DHODEC, LOGL_INFO, "Best candidate BTS %d (RX level %d) " + "without congestion found.\n", best_cand->bts->nr, + rxlev2dbm(best_cand->avg)); + if (is_improved) + LOGP(DHODEC, LOGL_INFO, "(is improved due to " + "AHS -> AFS)\n"); + trigger_handover_or_assignment(best_cand->lchan, best_cand->bts, + best_cand->requirements & REQUIREMENT_B_MASK); +#if 0 + /* if there is still congestion, mark lchan as deleted + * and redo this process */ + if (best_cand->lchan->type == GSM_LCHAN_TCH_H) + tchh_congestion--; + else + tchf_congestion--; + if (tchf_congestion > 0 || tchh_congestion > 0) { + delete_lchan = best_cand->lchan; + best_cand = NULL; + goto next_b1; + } +#else + /* must exit here, because triggering handover/assignment + * will cause change in requirements. more check for this + * bts is performed in the next iteration. + */ +#endif + goto exit; + } + +#if 0 +next_b2: +#endif + /* select worst candidate that fulfills requirement B, + * select candidates that change from AHS to AFS only */ + if (tchh_congestion > 0) { + /* since this will only check half rate channels, it will + * only need to be checked, if tchh is congested */ + worst_avg_db = 999; + for (i = 0; i < candidates; i++) { + /* delete subscriber that just have handovered */ + if (clist[i].lchan == delete_lchan) + clist[i].lchan = NULL; + /* omit all subscribers that are handovered */ + if (!clist[i].lchan) + continue; + + if (!(clist[i].requirements & REQUIREMENT_B_MASK)) + continue; + /* omit all but assignment from AHS to AFS */ + if (clist[i].lchan->ts->trx->bts != clist[i].bts + || clist[i].lchan->type != GSM_LCHAN_TCH_H + || !(clist[i].requirements & REQUIREMENT_B_TCHF)) + continue; + + avg = clist[i].avg; + /* improve AHS */ + if (clist[i].lchan->tch_mode == GSM48_CMODE_SPEECH_AMR + && clist[i].lchan->type == GSM_LCHAN_TCH_H) { + avg += clist[i].bts->handover.afs_rxlev_improve; + is_improved = 1; + } else + is_improved = 0; + if (avg < worst_avg_db) { + worst_cand = &clist[i]; + worst_avg_db = avg; + } + } + } + + /* perform handover, if there is a candidate */ + if (worst_cand) { + any_ho = 1; + LOGP(DHODEC, LOGL_INFO, "Worst candidate for assignment " + "(RX level %d) from TCH/H -> TCH/F without congestion " + "found.\n", rxlev2dbm(worst_cand->avg)); + if (is_improved) + LOGP(DHODEC, LOGL_INFO, "(is improved due to " + "AHS -> AFS)\n"); + trigger_handover_or_assignment(worst_cand->lchan, + worst_cand->bts, + worst_cand->requirements & REQUIREMENT_B_MASK); +#if 0 + /* if there is still congestion, mark lchan as deleted + * and redo this process */ + tchh_congestion--; + if (tchh_congestion > 0) { + delete_lchan = worst_cand->lchan; + best_cand = NULL; + goto next_b2; + } +#else + /* must exit here, because triggering handover/assignment + * will cause change in requirements. more check for this + * bts is performed in the next iteration. + */ +#endif + goto exit; + } + +#if 0 +next_c1: +#endif + /* select best candidate that fulfills requirement C, + * omit change from AHS to AFS */ + best_avg_db = 0; + for (i = 0; i < candidates; i++) { + /* delete subscriber that just have handovered */ + if (clist[i].lchan == delete_lchan) + clist[i].lchan = NULL; + /* omit all subscribers that are handovered */ + if (!clist[i].lchan) + continue; + + if (!(clist[i].requirements & REQUIREMENT_C_MASK)) + continue; + /* omit assignment from AHS to AFS */ + if (clist[i].lchan->ts->trx->bts == clist[i].bts + && clist[i].lchan->type == GSM_LCHAN_TCH_H + && (clist[i].requirements & REQUIREMENT_C_TCHF)) + continue; + /* omit candidates that will not solve/reduce congestion */ + if (clist[i].lchan->type == GSM_LCHAN_TCH_F + && tchf_congestion <= 0) + continue; + if (clist[i].lchan->type == GSM_LCHAN_TCH_H + && tchh_congestion <= 0) + continue; + + avg = clist[i].avg; + /* improve AHS */ + if (clist[i].lchan->tch_mode == GSM48_CMODE_SPEECH_AMR + && clist[i].lchan->type == GSM_LCHAN_TCH_H + && (clist[i].requirements & REQUIREMENT_C_TCHF)) { + avg += clist[i].bts->handover.afs_rxlev_improve; + is_improved = 1; + } else + is_improved = 0; + if (avg > best_avg_db) { + best_cand = &clist[i]; + best_avg_db = avg; + } + } + + /* perform handover, if there is a candidate */ + if (best_cand) { + any_ho = 1; + LOGP(DHODEC, LOGL_INFO, "Best candidate BTS %d (RX level %d) " + "with less or equal congestion found.\n", + best_cand->bts->nr, rxlev2dbm(best_cand->avg)); + if (is_improved) + LOGP(DHODEC, LOGL_INFO, "(is improved due to " + "AHS -> AFS)\n"); + trigger_handover_or_assignment(best_cand->lchan, best_cand->bts, + best_cand->requirements & REQUIREMENT_C_MASK); +#if 0 + /* if there is still congestion, mark lchan as deleted + * and redo this process */ + if (best_cand->lchan->type == GSM_LCHAN_TCH_H) + tchh_congestion--; + else + tchf_congestion--; + if (tchf_congestion > 0 || tchh_congestion > 0) { + delete_lchan = best_cand->lchan; + best_cand = NULL; + goto next_c1; + } +#else + /* must exit here, because triggering handover/assignment + * will cause change in requirements. more check for this + * bts is performed in the next iteration. + */ +#endif + goto exit; + } + +#if 0 +next_c2: +#endif + /* select worst candidate that fulfills requirement C, + * select candidates that change from AHS to AFS only */ + if (tchh_congestion > 0) { + /* since this will only check half rate channels, it will + * only need to be checked, if tchh is congested */ + worst_avg_db = 999; + for (i = 0; i < candidates; i++) { + /* delete subscriber that just have handovered */ + if (clist[i].lchan == delete_lchan) + clist[i].lchan = NULL; + /* omit all subscribers that are handovered */ + if (!clist[i].lchan) + continue; + + if (!(clist[i].requirements & REQUIREMENT_C_MASK)) + continue; + /* omit all but assignment from AHS to AFS */ + if (clist[i].lchan->ts->trx->bts != clist[i].bts + || clist[i].lchan->type != GSM_LCHAN_TCH_H + || !(clist[i].requirements & REQUIREMENT_C_TCHF)) + continue; + + avg = clist[i].avg; + /* improve AHS */ + if (clist[i].lchan->tch_mode == GSM48_CMODE_SPEECH_AMR + && clist[i].lchan->type == GSM_LCHAN_TCH_H) { + avg += clist[i].bts->handover.afs_rxlev_improve; + avg += clist[i].bts->handover.afs_rxlev_improve; + is_improved = 1; + } else + is_improved = 0; + if (avg < worst_avg_db) { + worst_cand = &clist[i]; + worst_avg_db = avg; + } + } + } + + /* perform handover, if there is a candidate */ + if (worst_cand) { + any_ho = 1; + LOGP(DHODEC, LOGL_INFO, "Worst candidate for assignment " + "(RX level %d) from TCH/H -> TCH/F with less or equal " + "congestion found.\n", rxlev2dbm(worst_cand->avg)); + if (is_improved) + LOGP(DHODEC, LOGL_INFO, "(is improved due to " + "AHS -> AFS)\n"); + trigger_handover_or_assignment(worst_cand->lchan, + worst_cand->bts, + worst_cand->requirements & REQUIREMENT_C_MASK); +#if 0 + /* if there is still congestion, mark lchan as deleted + * and redo this process */ + tchh_congestion--; + if (tchh_congestion > 0) { + delete_lchan = worst_cand->lchan; + worst_cand = NULL; + goto next_c2; + } +#else + /* must exit here, because triggering handover/assignment + * will cause change in requirements. more check for this + * bts is performed in the next iteration. + */ +#endif + goto exit; + } + +exit: + /* free array */ + talloc_free(clist); + + if (tchf_congestion <= 0 && tchh_congestion <= 0) + LOGP(DHODEC, LOGL_INFO, "Congestion at BTS %d solved!\n", + bts->nr); + else if (any_ho) + LOGP(DHODEC, LOGL_INFO, "Congestion at BTS %d reduced!\n", + bts->nr); + else + LOGP(DHODEC, LOGL_INFO, "Congestion at BTS %d can't be " + "reduced/solved!\n", bts->nr); + + return rc; +} +void congestion_check_2(void *data) +{ + int min_free_tchf, min_free_tchh; + int tchf_count, tchh_count; + struct gsm_bts *bts; + int n = 0; + + LOGP(DHODEC, LOGL_DEBUG, "Checking for congestion of all BTS\n"); + + ho_reason = HO_REASON_CONGESTION; + + /* loop through all bts */ + llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) { + /* only check BTS if TRX 0 is usable */ + if (!trx_is_usable(bts->c0)) + continue; + + /* only check BTS if handover or assignment is enabled */ + if (!bts->handover.as_active + && !bts->handover.ho_active) + continue; + + min_free_tchf = bts->handover.min_free_tchf; + min_free_tchh = bts->handover.min_free_tchh; + + /* only check BTS with congestion level set */ + if (!min_free_tchf && !min_free_tchh) + continue; + + tchf_count = lc_count_bts(bts, GSM_PCHAN_TCH_F); + tchh_count = lc_count_bts(bts, GSM_PCHAN_TCH_H); + + /* only check BTS if congested */ + if (tchf_count >= min_free_tchf && tchh_count >= min_free_tchh) + continue; + + /* try resolving congestion of bts */ + congestion_check_bts(bts, min_free_tchf - tchf_count, + min_free_tchh - tchh_count); + n++; + } + + if (!n) + LOGP(DHODEC, LOGL_DEBUG, "No congested BTS\n"); + + /* schedule next event */ + osmo_timer_schedule(&bsc_gsmnet->ho_congest_timer, + bsc_gsmnet->ho_congest_timeout, 0); +} + +void init_ho_timer_2(void) +{ + if (!bsc_gsmnet->ho_congest_timeout) + return; + bsc_gsmnet->ho_congest_timer.cb = congestion_check_2; + bsc_gsmnet->ho_congest_timer.data = NULL; + osmo_timer_schedule(&bsc_gsmnet->ho_congest_timer, + bsc_gsmnet->ho_congest_timeout, 0); +} + +static int ho_dec_sig_cb(unsigned int subsys, unsigned int signal, + void *handler_data, void *signal_data) +{ + struct lchan_signal_data *lchan_data; + + if (subsys != SS_LCHAN) + return 0; + + lchan_data = signal_data; + switch (signal) { + case S_LCHAN_MEAS_REP: + attempt_handover_after_mr(lchan_data->mr); + break; + } + + return 0; +} + +void init_ho_2(void) +{ + osmo_signal_register_handler(SS_LCHAN, ho_dec_sig_cb, NULL); +} + diff --git a/openbsc/src/libbsc/handover_logic.c b/openbsc/src/libbsc/handover_logic.c index 338b8bb81..06f968c2b 100644 --- a/openbsc/src/libbsc/handover_logic.c +++ b/openbsc/src/libbsc/handover_logic.c @@ -39,6 +39,7 @@ #include #include #include +#include struct bsc_handover { struct llist_head list; @@ -50,10 +51,25 @@ struct bsc_handover { uint8_t ho_ref; int ho_async; + int do_ass; }; static LLIST_HEAD(bsc_handovers); +void log_handover(int subsys, int level, struct bsc_handover *ho) +{ + struct gsm_bts *old_bts = ho->old_lchan->ts->trx->bts; + struct gsm_bts *new_bts = ho->new_lchan->ts->trx->bts; + + if (old_bts == new_bts) + LOGP(subsys, level, "(BTS %d (subscr %s)) ", + old_bts->nr, subscr_name(ho->old_lchan->conn->subscr)); + else + LOGP(subsys, level, "(BTS %d (subscr %s) -> BTS %d (subscr %s)) ", + old_bts->nr, subscr_name(ho->old_lchan->conn->subscr), + new_bts->nr, subscr_name(ho->new_lchan->conn->subscr)); +} + static struct bsc_handover *bsc_ho_by_new_lchan(struct gsm_lchan *new_lchan) { struct bsc_handover *ho; @@ -94,35 +110,81 @@ int bsc_ho_count(struct gsm_bts *bts) return count; } +/* Add a penalty timer to connection for the given BTS */ +void add_penalty_timer(struct gsm_subscriber_connection *conn, + struct gsm_bts *bts, int timeout) +{ + struct ho_penalty_timer *timer; + time_t now, then; + + /* no not add timer, if there is no timeout set */ + if (!timeout) + return; + + time(&now); + then = now + timeout; + + /* timer already running for that BTS? */ + llist_for_each_entry(timer, &conn->ho_penalty_timers, entry) { + if (timer->bts != bts->nr) + continue; + /* raise, if running timer will timeout earlier or has timed + * out already, otherwise keep later timeout */ + if (timer->timeout < then) + timer->timeout = then; + return; + } + + /* add new timer */ + timer = talloc_zero(tall_bsc_ctx, struct ho_penalty_timer); + if (!timer) + return; + + timer->bts = bts->nr; + timer->timeout = then; + + llist_add_tail(&timer->entry, &conn->ho_penalty_timers); +} + /* Hand over the specified logical channel to the specified new BTS. * This is the main entry point for the actual handover algorithm, * after it has decided it wants to initiate HO to a specific BTS */ -int bsc_handover_start(struct gsm_lchan *old_lchan, struct gsm_bts *bts) +int bsc_handover_start(struct gsm_lchan *old_lchan, struct gsm_bts *new_bts, + int full_rate) { + struct gsm_bts *old_bts = old_lchan->ts->trx->bts; struct gsm_lchan *new_lchan; + int chan_type; struct bsc_handover *ho; static uint8_t ho_ref; + uint8_t rqd_ta; int rc; + int do_assignment = 0; + + /* if new BTS is not given, assignemnt is performed */ + if (!new_bts) { + new_bts = old_bts; + do_assignment = 1; + } /* don't attempt multiple handovers for the same lchan at * the same time */ if (bsc_ho_by_old_lchan(old_lchan)) return -EBUSY; - DEBUGP(DHO, "(old_lchan on BTS %u, new BTS %u)\n", - old_lchan->ts->trx->bts->nr, bts->nr); - - osmo_counter_inc(bts->network->stats.handover.attempted); + osmo_counter_inc(new_bts->network->stats.handover.attempted); if (!old_lchan->conn) { LOGP(DHO, LOGL_ERROR, "Old lchan lacks connection data.\n"); return -ENOSPC; } - new_lchan = lchan_alloc(bts, old_lchan->type, 0); + chan_type = full_rate ? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H; + + new_lchan = lchan_alloc(new_bts, chan_type, 0); if (!new_lchan) { LOGP(DHO, LOGL_NOTICE, "No free channel\n"); - osmo_counter_inc(bts->network->stats.handover.no_channel); + osmo_counter_inc(new_bts->network->stats.handover.no_channel); return -ENOSPC; } @@ -134,24 +196,39 @@ int bsc_handover_start(struct gsm_lchan *old_lchan, struct gsm_bts *bts) } ho->old_lchan = old_lchan; ho->new_lchan = new_lchan; - if (old_lchan->ts->trx->bts != bts) { + if (!do_assignment) { ho->ho_ref = ho_ref++; ho->ho_async = 1; - } + } else + ho->do_ass = 1; /* copy some parameters from old lchan */ memcpy(&new_lchan->encr, &old_lchan->encr, sizeof(new_lchan->encr)); - new_lchan->ms_power = old_lchan->ms_power; + if (do_assignment) { + 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(new_bts->band, new_bts->ms_max_power); + /* FIXME: do we have a better idea of the timing advance? */ + } 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 = old_lchan->conn; new_lchan->conn->ho_lchan = new_lchan; - /* FIXME: do we have a better idea of the timing advance? */ + rqd_ta = new_lchan->rqd_ta; + if (new_bts->type == GSM_BTS_TYPE_BS11) + rqd_ta <<= 2; rc = rsl_chan_activate_lchan(new_lchan, - (ho->ho_async) ? RSL_ACT_INTER_ASYNC : 0x00, 0, ho->ho_ref); + (ho->ho_async) ? RSL_ACT_INTER_ASYNC : 0x00, rqd_ta, + ho->ho_ref); if (rc < 0) { LOGP(DHO, LOGL_ERROR, "could not activate channel\n"); new_lchan->conn->ho_lchan = NULL; @@ -161,6 +238,12 @@ int bsc_handover_start(struct gsm_lchan *old_lchan, struct gsm_bts *bts) return rc; } + log_handover(DHO, LOGL_INFO, ho); + if (do_assignment) + LOGPC(DHO, LOGL_INFO, "Start Assignment\n"); + else + LOGPC(DHO, LOGL_INFO, "Start Handover\n"); + rsl_lchan_set_state(new_lchan, LCHAN_S_ACT_REQ); llist_add(&ho->list, &bsc_handovers); /* we continue in the SS_LCHAN handler / ho_chan_activ_ack */ @@ -221,12 +304,19 @@ static int ho_chan_activ_ack(struct gsm_lchan *new_lchan) if (!ho) return -ENODEV; - DEBUGP(DHO, "handover activate ack, send HO Command\n"); + log_handover(DHO, LOGL_INFO, ho); + LOGPC(DHO, LOGL_INFO, "Channel activate ack, send HO/AS Command\n"); - /* we can now send the 04.08 HANDOVER COMMAND to the MS + /* we can now send the 04.08 HANDOVER/ASSIGNMENT COMMAND to the MS * using the old lchan */ - gsm48_send_ho_cmd(ho->old_lchan, new_lchan, 0, ho->ho_ref); + if (ho->do_ass) { + gsm48_send_rr_ass_cmd(ho->old_lchan, new_lchan, + new_lchan->ms_power); + } else { + 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 */ @@ -245,12 +335,17 @@ static int ho_chan_activ_ack(struct gsm_lchan *new_lchan) static int ho_chan_activ_nack(struct gsm_lchan *new_lchan) { struct bsc_handover *ho; + struct gsm_bts *new_bts = new_lchan->ts->trx->bts; ho = bsc_ho_by_new_lchan(new_lchan); - if (!ho) { - LOGP(DHO, LOGL_INFO, "ACT NACK: unable to find HO record\n"); + if (!ho) return -ENODEV; - } + + log_handover(DHO, LOGL_NOTICE, ho); + LOGPC(DHO, LOGL_NOTICE, "Channel activate nack, start penalty timer\n"); + + /* if channel failed, wait 10 seconds befor allowing to retry handover */ + add_penalty_timer(ho->old_lchan->conn, new_bts, 10); new_lchan->conn->ho_lchan = NULL; new_lchan->conn = NULL; @@ -274,12 +369,10 @@ static int ho_gsm48_ho_compl(struct gsm_lchan *new_lchan) return -ENODEV; } - net = new_lchan->ts->trx->bts->network; - LOGP(DHO, LOGL_INFO, "Subscriber %s HO from BTS %u->%u on ARFCN " - "%u->%u\n", subscr_name(ho->old_lchan->conn->subscr), - ho->old_lchan->ts->trx->bts->nr, new_lchan->ts->trx->bts->nr, - ho->old_lchan->ts->trx->arfcn, new_lchan->ts->trx->arfcn); + log_handover(DHO, LOGL_INFO, ho); + LOGPC(DHO, LOGL_INFO, "Handover/Assignment completed\n"); + net = new_lchan->ts->trx->bts->network; osmo_counter_inc(net->stats.handover.completed); osmo_timer_del(&ho->T3103); @@ -309,7 +402,8 @@ static int ho_gsm48_ho_compl(struct gsm_lchan *new_lchan) /* 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 gsm_bts *old_bts = old_lchan->ts->trx->bts, *new_bts; + struct gsm_network *net = old_bts->network; struct bsc_handover *ho; ho = bsc_ho_by_old_lchan(old_lchan); @@ -317,6 +411,14 @@ static int ho_gsm48_ho_fail(struct gsm_lchan *old_lchan) LOGP(DHO, LOGL_ERROR, "unable to find HO record\n"); return -ENODEV; } + new_bts = ho->new_lchan->ts->trx->bts; + + log_handover(DHO, LOGL_NOTICE, ho); + LOGPC(DHO, LOGL_NOTICE, "Handover/Assignment failed, start penalty timer\n"); + + add_penalty_timer(old_lchan->conn, new_bts, + (ho->do_ass) ? old_bts->handover.penalty_as_fail + : old_bts->handover.penalty_ho_fail); osmo_counter_inc(net->stats.handover.failed); @@ -344,6 +446,9 @@ static int ho_rsl_detect(struct gsm_lchan *new_lchan) return -ENODEV; } + log_handover(DHO, LOGL_DEBUG, ho); + LOGPC(DHO, LOGL_DEBUG, "Handover RACH detected\n"); + /* FIXME: do we actually want to do something here ? */ return 0; @@ -367,8 +472,10 @@ static int ho_logic_sig_cb(unsigned int subsys, unsigned int signal, case S_LCHAN_HANDOVER_DETECT: return ho_rsl_detect(lchan); case S_LCHAN_HANDOVER_COMPL: + case S_LCHAN_ASSIGNMENT_COMPL: return ho_gsm48_ho_compl(lchan); case S_LCHAN_HANDOVER_FAIL: + case S_LCHAN_ASSIGNMENT_FAIL: return ho_gsm48_ho_fail(lchan); } break; diff --git a/openbsc/src/libbsc/handover_vty.c b/openbsc/src/libbsc/handover_vty.c index e20773353..ee449e9f3 100644 --- a/openbsc/src/libbsc/handover_vty.c +++ b/openbsc/src/libbsc/handover_vty.c @@ -156,7 +156,7 @@ DEFUN(cfg_net_ho_algorithm, init_ho_1(); break; case 2: -// init_ho_2(); + init_ho_2(); break; } return CMD_SUCCESS; @@ -176,7 +176,7 @@ DEFUN(cfg_net_ho_congest_timer, gsmnet->ho_congest_timeout = atoi(argv[0]); switch (gsmnet->ho_algorithm) { case 2: -// init_ho_timer_2(); + init_ho_timer_2(); break; } return CMD_SUCCESS; diff --git a/openbsc/src/libcommon/gsm_data.c b/openbsc/src/libcommon/gsm_data.c index b1b1ca30c..d78f4d759 100644 --- a/openbsc/src/libcommon/gsm_data.c +++ b/openbsc/src/libcommon/gsm_data.c @@ -35,6 +35,7 @@ #include #include #include +#include void *tall_bsc_ctx; @@ -161,7 +162,7 @@ struct gsm_bts *gsm_bts_num(struct gsm_network *net, int num) struct gsm_bts *gsm_bts_neighbor(const struct gsm_bts *bts, uint16_t arfcn, uint8_t bsic) { - struct gsm_bts *neigh; + struct gsm_bts *neigh, *selected = NULL; /* FIXME: use some better heuristics here to determine which cell * using this ARFCN really is closest to the target cell. For * now we simply assume that each ARFCN will only be used by one @@ -169,11 +170,20 @@ struct gsm_bts *gsm_bts_neighbor(const struct gsm_bts *bts, llist_for_each_entry(neigh, &bts->network->bts_list, list) { if (neigh->c0->arfcn == arfcn && - neigh->bsic == bsic) - return neigh; + neigh->bsic == bsic) { + if (selected) + goto error; + selected = neigh; + } } - return NULL; + return selected; + +error: + LOGP(DHO, LOGL_ERROR, "Ambiguous ARFCN (%u) and BSIC (%u). BTS %u and " + "BTS %u share same ARFCN + BSIC, plese fix configuration!\n", + arfcn, bsic, selected->nr, neigh->nr); + return 0; } const struct value_string bts_type_names[_NUM_GSM_BTS_TYPE+1] = { diff --git a/openbsc/src/libmsc/vty_interface_layer3.c b/openbsc/src/libmsc/vty_interface_layer3.c index 163e58e32..9e7a948da 100644 --- a/openbsc/src/libmsc/vty_interface_layer3.c +++ b/openbsc/src/libmsc/vty_interface_layer3.c @@ -631,7 +631,8 @@ DEFUN(ena_subscr_handover, } /* now start the handover */ - ret = bsc_handover_start(conn->lchan, bts); + ret = bsc_handover_start(conn->lchan, bts, + (conn->lchan->type != GSM_LCHAN_TCH_H)); if (ret != 0) { vty_out(vty, "%% Handover failed with errno %d.%s", ret, VTY_NEWLINE); -- cgit v1.2.3