/* 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 /* issue handover to a cell identified by ARFCN and BSIC */ static int handover_to_arfcn_bsic(struct gsm_lchan *lchan, u_int16_t arfcn, u_int8_t bsic) { struct gsm_bts *new_bts; /* resolve the gsm_bts structure for the best neighbor */ new_bts = gsm_bts_neighbor(lchan->ts->trx->bts, arfcn, bsic); if (!new_bts) { LOGP(DHO, LOGL_NOTICE, "unable to determine neighbor BTS " "for ARFCN %u BSIC %u ?!?\n", arfcn, bsic); return -EINVAL; } /* and actually try to handover to that cell */ return bsc_handover_start(lchan, new_bts); } /* 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, u_int16_t arfcn, u_int8_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; 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; /* 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 (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->nr; } 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; idx = nmp->rxlev_cnt % ARRAY_SIZE(nmp->rxlev); nmp->rxlev[idx] = mrc->rxlev; nmp->rxlev_cnt++; nmp->last_seen_nr = mr->nr; mrc->flags |= MRC_F_PROCESSED; } } /* attempt to do a handover */ static int attempt_handover(struct gsm_meas_rep *mr) { struct gsm_network *net = mr->lchan->ts->trx->bts->network; struct neigh_meas_proc *best_cell = NULL; unsigned int best_better_db = 0; int i, rc; /* find the best cell in this report that is at least RXLEV_HYST * better than the current serving cell */ for (i = 0; i < ARRAY_SIZE(mr->lchan->neigh_meas); i++) { struct neigh_meas_proc *nmp = &mr->lchan->neigh_meas[i]; int avg, better; /* skip empty slots */ if (nmp->arfcn == 0) continue; /* caculate average rxlev for this cell over the window */ avg = neigh_meas_avg(nmp, net->handover.win_rxlev_avg_neigh); /* check if hysteresis is fulfilled */ if (avg < mr->dl.full.rx_lev + net->handover.pwr_hysteresis) continue; better = avg - mr->dl.full.rx_lev; if (better > best_better_db) { best_cell = nmp; best_better_db = better; } } if (!best_cell) return 0; LOGP(DHO, LOGL_INFO, "%s: Cell on ARFCN %u is better: ", gsm_ts_name(mr->lchan->ts), best_cell->arfcn); if (!net->handover.active) { LOGPC(DHO, LOGL_INFO, "Skipping, Handover disabled\n"); return 0; } rc = handover_to_arfcn_bsic(mr->lchan, best_cell->arfcn, best_cell->bsic); switch (rc) { case 0: LOGPC(DHO, LOGL_INFO, "Starting handover\n"); break; case -ENOSPC: LOGPC(DHO, LOGL_INFO, "No channel available\n"); break; case -EBUSY: LOGPC(DHO, LOGL_INFO, "Handover already active\n"); break; default: LOGPC(DHO, LOGL_ERROR, "Unknown error\n"); } return rc; } /* process an already parsed measurement report and decide if we want to * attempt a handover */ static int process_meas_rep(struct gsm_meas_rep *mr) { struct gsm_network *net = mr->lchan->ts->trx->bts->network; int av_rxlev; /* 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); av_rxlev = get_meas_rep_avg(mr->lchan, MEAS_REP_DL_RXLEV_FULL, net->handover.win_rxlev_avg); /* Interference HO */ if (rxlev2dbm(av_rxlev) > -85 && meas_rep_n_out_of_m_be(mr->lchan, MEAS_REP_DL_RXQUAL_FULL, 3, 4, 5)) return attempt_handover(mr); /* Bad Quality */ if (meas_rep_n_out_of_m_be(mr->lchan, MEAS_REP_DL_RXQUAL_FULL, 3, 4, 5)) return attempt_handover(mr); /* Low Level */ if (rxlev2dbm(av_rxlev) <= -110) return attempt_handover(mr); /* Distance */ if (mr->ms_l1.ta > net->handover.max_distance) return attempt_handover(mr); /* Power Budget AKA Better Cell */ if ((mr->nr % net->handover.pwr_interval) == 0) return attempt_handover(mr); return 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: process_meas_rep(lchan_data->mr); break; } return 0; } void on_dso_load_ho_dec(void) { register_signal_handler(SS_LCHAN, ho_dec_sig_cb, NULL); }