/* 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 * * 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 #include #include #include #include #include #include #include #include #include #include const struct value_string handover_scope_names[] = { { HO_NO_HANDOVER, "HO-none" }, { HO_INTRA_CELL, "AS" }, { HO_INTRA_BSC, "HO-intraBSC" }, { HO_INTER_BSC_OUT, "HO-interBSC-Out" }, { HO_INTER_BSC_IN, "HO-interBSC-In" }, { HO_SCOPE_ALL, "HO-any" }, {} }; const struct value_string handover_result_names[] = { { HO_RESULT_OK, "Complete" }, { HO_RESULT_FAIL_NO_CHANNEL, "Failure (no channel could be allocated)" }, { HO_RESULT_FAIL_RR_HO_FAIL, "Failure (MS sent RR Handover Failure)" }, { HO_RESULT_FAIL_TIMEOUT, "Failure (timeout)" }, { HO_RESULT_CONN_RELEASE, "Connection released" }, { HO_RESULT_ERROR, "Failure" }, {} }; static LLIST_HEAD(handover_decision_callbacks); 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; } static void 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; hdc->on_measurement_report(mr); } /* Count ongoing handovers within the given BTS. * ho_scopes is an OR'd combination of enum handover_scope values to include in the count. */ int bts_handover_count(struct gsm_bts *bts, int ho_scopes) { struct gsm_bts_trx *trx; int count = 0; llist_for_each_entry(trx, &bts->trx_list, list) { int i; for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { struct gsm_bts_trx_ts *ts = &trx->ts[i]; struct gsm_lchan *lchan; /* skip administratively deactivated timeslots */ if (!nm_is_running(&ts->mo.nm_state)) continue; ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) { if (!lchan->conn) continue; if (!lchan->conn->ho.fi) continue; if (lchan->conn->ho.scope & ho_scopes) count++; } } } return count; } /* Find out a handover target cell for the given arfcn_bsic, * and make sure there are no ambiguous matches. * Given a source BTS and a target ARFCN+BSIC, find which cell is the right handover target. * ARFCN+BSIC may be re-used within and/or across BSS, so make sure that only those cells that are explicitly * listed as neighbor of the source cell are viable handover targets. * The (legacy) default configuration is that, when no explicit neighbors are listed, that all local cells are * neighbors, in which case each ARFCN+BSIC must exist at most once. * If there is more than one viable handover target cell found for the given ARFCN+BSIC, that constitutes a * configuration error and should not result in handover, so that the system's misconfiguration is more likely * to be found. */ int find_handover_target_cell(struct gsm_bts **local_target_cell_p, struct gsm0808_cell_id_list2 *remote_target_cells, struct gsm_subscriber_connection *conn, const struct cell_ab *search_for, bool log_errors) { struct gsm_network *net = conn->network; struct gsm_bts *local_target_cell = NULL; bool ho_active; bool as_active; struct gsm_bts *from_bts = conn->lchan->ts->trx->bts; *remote_target_cells = (struct gsm0808_cell_id_list2){}; if (local_target_cell_p) *local_target_cell_p = NULL; if (!search_for) { if (log_errors) LOG_HO(conn, LOGL_ERROR, "Handover without target cell\n"); return -EINVAL; } if (!from_bts) { if (log_errors) LOG_HO(conn, LOGL_ERROR, "Handover without source cell\n"); return -EINVAL; } ho_active = ho_get_ho_active(from_bts->ho); as_active = (ho_get_algorithm(from_bts->ho) == 2) && ho_get_hodec2_as_active(from_bts->ho); if (!ho_active && !as_active) { if (log_errors) LOG_HO(conn, LOGL_ERROR, "Cannot start Handover: Handover and Assignment disabled for this source cell (%s)\n", cell_ab_to_str_c(OTC_SELECT, search_for)); return -EINVAL; } if (llist_empty(&from_bts->neighbors)) { /* No explicit neighbor entries exist for this BTS. Hence apply the legacy default behavior that all * local cells are neighbors. */ struct gsm_bts *bts; int i; LOG_HO(conn, LOGL_DEBUG, "No explicit neighbors, regarding all local cells as neighbors\n"); /* For i == 0, look for an exact 1:1 match of all ident_key fields. * For i == 1, interpret wildcard values, when no exact match exists. */ for (i = 0; i < 2; i++) { bool exact_match = !i; llist_for_each_entry(bts, &net->bts_list, list) { struct cell_ab bts_ab; bts_cell_ab(&bts_ab, bts); if (cell_ab_match(&bts_ab, search_for, exact_match)) { if (local_target_cell) { if (log_errors) LOG_HO(conn, LOGL_ERROR, "NEIGHBOR CONFIGURATION ERROR: Multiple local cells match %s" " (BTS %d and BTS %d)." " Aborting Handover because of ambiguous network topology.\n", cell_ab_to_str_c(OTC_SELECT, search_for), local_target_cell->nr, bts->nr); return -EINVAL; } local_target_cell = bts; } } if (local_target_cell) break; } if (!local_target_cell) { if (log_errors) LOG_HO(conn, LOGL_ERROR, "Cannot Handover, no cell matches %s\n", cell_ab_to_str_c(OTC_SELECT, search_for)); return -EINVAL; } if (local_target_cell == from_bts && !as_active) { if (log_errors) LOG_HO(conn, LOGL_ERROR, "Cannot start re-assignment, Assignment disabled for this cell (%s)\n", cell_ab_to_str_c(OTC_SELECT, search_for)); return -EINVAL; } if (local_target_cell != from_bts && !ho_active) { if (log_errors) LOG_HO(conn, LOGL_ERROR, "Cannot start Handover, Handover disabled for this cell (%s)\n", cell_ab_to_str_c(OTC_SELECT, search_for)); return -EINVAL; } if (local_target_cell_p) *local_target_cell_p = local_target_cell; return 0; } /* One or more local- or remote-BSS cell neighbors are configured. Find a match among those, but also detect * ambiguous matches (if multiple cells match, it is a configuration error). */ LOG_HO(conn, LOGL_DEBUG, "There are explicit neighbors configured for this cell\n"); if (resolve_neighbors(&local_target_cell, remote_target_cells, from_bts, search_for, log_errors)) { LOG_HO(conn, LOGL_ERROR, "Cannot handover BTS %u -> %s: neighbor unknown\n", from_bts->nr, cell_ab_to_str_c(OTC_SELECT, search_for)); return -ENOENT; } /* We have found possibly a local_target_cell (when != NULL), and / or remote_target_cells (when .id_list_len > * 0). Figure out what to do with them. */ if (remote_target_cells->id_list_len) LOG_HO(conn, LOGL_DEBUG, "Found remote target cell(s) %s\n", gsm0808_cell_id_list_name_c(OTC_SELECT, remote_target_cells)); if (local_target_cell && remote_target_cells->id_list_len) { if (log_errors) LOG_HO(conn, LOGL_ERROR, "NEIGHBOR CONFIGURATION ERROR: Both a local and a remote-BSS cell" " match BTS %u -> %s (BTS %d and remote %s)." " Aborting Handover because of ambiguous network topology.\n", from_bts->nr, cell_ab_to_str_c(OTC_SELECT, search_for), local_target_cell->bts_nr, gsm0808_cell_id_list_name_c(OTC_SELECT, remote_target_cells)); return -EINVAL; } if (local_target_cell == from_bts && !as_active) { if (log_errors) LOG_HO(conn, LOGL_ERROR, "Cannot start re-assignment, Assignment disabled for this cell (BTS %u)\n", from_bts->nr); return -EINVAL; } if (((local_target_cell && local_target_cell != from_bts) || remote_target_cells->id_list_len) && !ho_active) { if (log_errors) LOG_HO(conn, LOGL_ERROR, "Cannot start Handover, Handover disabled for this cell (BTS %u -> %s)\n", from_bts->bts_nr, cell_ab_to_str_c(OTC_SELECT, search_for)); return -EINVAL; } /* Return the result. After above checks, only one of local or remote cell has been found. */ if (local_target_cell) { if (local_target_cell_p) *local_target_cell_p = local_target_cell; return 0; } if (remote_target_cells->id_list_len) return 0; if (log_errors) LOG_HO(conn, LOGL_ERROR, "Cannot handover %s: neighbor unknown\n", cell_ab_to_str_c(OTC_SELECT, search_for)); return -ENODEV; } 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: OSMO_ASSERT(lchan_data); lchan = lchan_data->lchan; OSMO_ASSERT(lchan); switch (signal) { case S_LCHAN_MEAS_REP: ho_meas_rep(lchan_data->mr); break; } default: break; } return 0; } static __attribute__((constructor)) void on_dso_load_ho_logic(void) { osmo_signal_register_handler(SS_LCHAN, ho_logic_sig_cb, NULL); }