aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeels Hofmeyr <neels@hofmeyr.de>2017-12-07 03:54:01 +0100
committerNeels Hofmeyr <neels@hofmeyr.de>2018-01-12 05:26:30 +0100
commit20c1d61a23da869eed49db0c8f901d8f61fff5bd (patch)
tree8791b456ca01002b150ca837ff7096eb0e4bbf41
parent5fd11987629984a7b31c40d2312bebf48eca6341 (diff)
HO: Implement load based handover, as handover_decision_2.c
-rw-r--r--configure.ac1
-rw-r--r--include/osmocom/bsc/Makefile.am1
-rw-r--r--include/osmocom/bsc/gsm_data.h22
-rw-r--r--include/osmocom/bsc/gsm_data_shared.h6
-rw-r--r--include/osmocom/bsc/handover.h2
-rw-r--r--include/osmocom/bsc/handover_cfg.h10
-rw-r--r--include/osmocom/bsc/handover_decision_2.h8
-rw-r--r--include/osmocom/bsc/signal.h1
-rw-r--r--src/libbsc/Makefile.am1
-rw-r--r--src/libbsc/bsc_api.c13
-rw-r--r--src/libbsc/bsc_vty.c4
-rw-r--r--src/libbsc/chan_alloc.c1
-rw-r--r--src/libbsc/handover_decision.c14
-rw-r--r--src/libbsc/handover_decision_2.c1728
-rw-r--r--src/libbsc/handover_logic.c136
-rw-r--r--src/libbsc/handover_vty.c2
-rw-r--r--src/libcommon/gsm_data.c75
-rw-r--r--src/libcommon/gsm_data_shared.c2
-rw-r--r--src/libcommon/handover_cfg.c5
-rw-r--r--src/osmo-bsc/osmo_bsc_bssap.c7
-rw-r--r--src/osmo-bsc/osmo_bsc_main.c3
-rw-r--r--tests/Makefile.am1
-rw-r--r--tests/handover/Makefile.am37
-rw-r--r--tests/handover/handover_test.c1549
-rw-r--r--tests/handover/handover_test.ok1
-rw-r--r--tests/testsuite.at168
26 files changed, 3740 insertions, 58 deletions
diff --git a/configure.ac b/configure.ac
index d756970dc..fc4678f1a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -166,6 +166,7 @@ AC_OUTPUT(
tests/subscr/Makefile
tests/nanobts_omlattr/Makefile
tests/bssap/Makefile
+ tests/handover/Makefile
doc/Makefile
doc/examples/Makefile
Makefile)
diff --git a/include/osmocom/bsc/Makefile.am b/include/osmocom/bsc/Makefile.am
index 699aeb339..a3d9adfd8 100644
--- a/include/osmocom/bsc/Makefile.am
+++ b/include/osmocom/bsc/Makefile.am
@@ -27,6 +27,7 @@ noinst_HEADERS = \
handover.h \
handover_cfg.h \
handover_decision.h \
+ handover_decision_2.h \
handover_vty.h \
ipaccess.h \
meas_feed.h \
diff --git a/include/osmocom/bsc/gsm_data.h b/include/osmocom/bsc/gsm_data.h
index cd4997c8a..28314835c 100644
--- a/include/osmocom/bsc/gsm_data.h
+++ b/include/osmocom/bsc/gsm_data.h
@@ -10,6 +10,7 @@
#include <osmocom/core/rate_ctr.h>
#include <osmocom/core/select.h>
#include <osmocom/core/stats.h>
+#include <osmocom/gsm/protocol/gsm_08_08.h>
#include <osmocom/crypt/auth.h>
#include <osmocom/sigtran/sccp_sap.h>
@@ -72,8 +73,8 @@ struct gsm_classmark {
/* penalty timers for handover */
struct ho_penalty_timer {
struct llist_head entry;
- uint8_t bts;
- time_t timeout;
+ uint8_t bts_nr;
+ unsigned int timeout;
};
/* active radio connection of a mobile subscriber */
@@ -108,10 +109,20 @@ struct gsm_subscriber_connection {
/* penalty timers for handover */
struct llist_head ho_penalty_timers;
+ int ho_failure;
/* Cache DTAP messages during handover/assignment (msgb_enqueue()/msgb_dequeue())*/
struct llist_head ho_dtap_cache;
unsigned int ho_dtap_cache_len;
+
+ /* "Codec List (MSC Preferred)" as received by the BSSAP Assignment Request. 3GPP 48.008
+ * 3.2.2.103 says:
+ * The "Codec List (MSC Preferred)" shall not include codecs
+ * that are not supported by the MS.
+ * i.e. by heeding the "Codec list (MSC Preferred)", we inherently heed the MS bearer
+ * capabilities, which the MSC is required to translate into the codec list. */
+ struct gsm0808_speech_codec_list codec_list;
+ bool codec_list_present;
};
static inline struct gsm_bts *conn_get_bts(struct gsm_subscriber_connection *conn) {
@@ -441,4 +452,11 @@ void gsm_bts_set_radio_link_timeout(struct gsm_bts *bts, int value);
bool classmark_is_r99(struct gsm_classmark *cm);
+void conn_penalty_timer_add(struct gsm_subscriber_connection *conn,
+ struct gsm_bts *bts, int timeout);
+unsigned int conn_penalty_timer_remaining(struct gsm_subscriber_connection *conn,
+ struct gsm_bts *bts);
+void conn_penalty_timer_clear(struct gsm_subscriber_connection *conn,
+ struct gsm_bts *bts);
+
#endif /* _GSM_DATA_H */
diff --git a/include/osmocom/bsc/gsm_data_shared.h b/include/osmocom/bsc/gsm_data_shared.h
index 277366dd9..0f325ecba 100644
--- a/include/osmocom/bsc/gsm_data_shared.h
+++ b/include/osmocom/bsc/gsm_data_shared.h
@@ -801,7 +801,11 @@ struct gsm_bts {
struct rate_ctr_group *bts_ctrs;
- struct handover_cfg *ho;
+ struct {
+ struct handover_cfg *cfg;
+ struct osmo_timer_list congestion_check_timer;
+ /* todo: move them here */ //struct llist_head penalty_timeouts;
+ } ho;
};
diff --git a/include/osmocom/bsc/handover.h b/include/osmocom/bsc/handover.h
index a9349eeda..f76445695 100644
--- a/include/osmocom/bsc/handover.h
+++ b/include/osmocom/bsc/handover.h
@@ -5,6 +5,8 @@ struct gsm_bts;
struct gsm_subscriber_connection;
int bsc_handover_start(struct gsm_lchan *old_lchan, struct gsm_bts *bts);
+int bsc_handover_start_lchan_change(struct gsm_lchan *old_lchan, struct gsm_bts *bts,
+ enum gsm_chan_t new_lchan_type);
void bsc_clear_handover(struct gsm_subscriber_connection *conn, int free_lchan);
struct gsm_lchan *bsc_handover_pending(struct gsm_lchan *new_lchan);
diff --git a/include/osmocom/bsc/handover_cfg.h b/include/osmocom/bsc/handover_cfg.h
index b5a0d1b7d..ff73fa328 100644
--- a/include/osmocom/bsc/handover_cfg.h
+++ b/include/osmocom/bsc/handover_cfg.h
@@ -20,6 +20,13 @@ struct handover_cfg *ho_cfg_init(void *ctx, enum handover_cfg_ctx_type ctx_type,
typedef void (*ho_cfg_on_change_cb_t)(void *ctx, enum handover_cfg_ctx_type ctx_type);
+/* ho_cfg_* code gets called during initialization of the global gsm_network struct, which is included in
+ * various utility programs that don't need most of gsm_data.c, definitely no handover. The on_change
+ * callback from the ho_cfg touches internals of the handover decision, which would cause utility
+ * programs to require linking of most of the handover code. To break this linking cascade, have the
+ * on_change callbacks as function pointers. */
+extern ho_cfg_on_change_cb_t ho_cfg_on_change_congestion_check_interval_cb;
+
#define HO_CFG_STR_HANDOVER "Handover options\n"
#define HO_CFG_STR_WIN HO_CFG_STR_HANDOVER "Measurement averaging settings\n"
#define HO_CFG_STR_WIN_RXLEV HO_CFG_STR_WIN "Received-Level averaging\n"
@@ -157,7 +164,8 @@ static inline const char *congestion_check_interval2a(int val)
"Disable in-call assignment\n" \
"Enable in-call assignment\n") \
\
- HO_CFG_ONE_MEMBER(int, congestion_check_interval, 10, NULL, \
+ HO_CFG_ONE_MEMBER(int, congestion_check_interval, 10, \
+ ho_cfg_on_change_congestion_check_interval_cb, \
"handover congestion-check", "disabled|<1-60>", \
a2congestion_check_interval, "%s", congestion_check_interval2a, \
HO_CFG_STR_HANDOVER \
diff --git a/include/osmocom/bsc/handover_decision_2.h b/include/osmocom/bsc/handover_decision_2.h
new file mode 100644
index 000000000..94e9c8663
--- /dev/null
+++ b/include/osmocom/bsc/handover_decision_2.h
@@ -0,0 +1,8 @@
+/* Handover Decision Algorithm 2 for intra-BSC (inter-BTS) handover, public API for OsmoBSC */
+
+#pragma once
+struct gsm_bts;
+
+void handover_decision_2_init(struct gsm_network *net);
+
+void handover_decision_2_bts_congestion_check(struct gsm_bts *bts);
diff --git a/include/osmocom/bsc/signal.h b/include/osmocom/bsc/signal.h
index 9c0d5a3de..1c8b51e3c 100644
--- a/include/osmocom/bsc/signal.h
+++ b/include/osmocom/bsc/signal.h
@@ -104,6 +104,7 @@ enum signal_ipaccess {
enum signal_global {
S_GLOBAL_BTS_CLOSE_OM,
+ S_GLOBAL_BTS_NEW,
};
/* SS_RF signals */
diff --git a/src/libbsc/Makefile.am b/src/libbsc/Makefile.am
index 23a527a55..347dce766 100644
--- a/src/libbsc/Makefile.am
+++ b/src/libbsc/Makefile.am
@@ -56,5 +56,6 @@ libbsc_a_SOURCES = \
bsc_dyn_ts.c \
bts_ipaccess_nanobts_omlattr.c \
handover_vty.c \
+ handover_decision_2.c \
$(NULL)
diff --git a/src/libbsc/bsc_api.c b/src/libbsc/bsc_api.c
index 0def5e861..7cbe129d1 100644
--- a/src/libbsc/bsc_api.c
+++ b/src/libbsc/bsc_api.c
@@ -323,8 +323,6 @@ static void ho_dtap_cache_flush(struct gsm_subscriber_connection *conn, int send
void bsc_subscr_con_free(struct gsm_subscriber_connection *conn)
{
- struct ho_penalty_timer *penalty;
-
if (!conn)
return;
@@ -346,12 +344,7 @@ void bsc_subscr_con_free(struct gsm_subscriber_connection *conn)
conn->secondary_lchan->conn = NULL;
}
- /* flush handover penalty timers */
- while ((penalty = llist_first_entry_or_null(&conn->ho_penalty_timers,
- struct ho_penalty_timer, entry))) {
- llist_del(&penalty->entry);
- talloc_free(penalty);
- }
+ conn_penalty_timer_clear(conn, NULL);
/* drop pending messages */
ho_dtap_cache_flush(conn, 0);
@@ -672,8 +665,8 @@ static void handle_rr_ho_fail(struct msgb *msg)
struct lchan_signal_data sig;
struct gsm48_hdr *gh = msgb_l3(msg);
- DEBUGP(DRR, "HANDOVER FAILED cause = %s\n",
- rr_cause_name(gh->data[0]));
+ DEBUGP(DRR, "HANDOVER FAILED cause = %s\n", rr_cause_name(gh->data[0]));
+ DEBUGP(DHO, "HANDOVER FAILED cause = %s\n", rr_cause_name(gh->data[0]));
sig.lchan = msg->lchan;
sig.mr = NULL;
diff --git a/src/libbsc/bsc_vty.c b/src/libbsc/bsc_vty.c
index a304e864e..1cb051925 100644
--- a/src/libbsc/bsc_vty.c
+++ b/src/libbsc/bsc_vty.c
@@ -183,7 +183,7 @@ static void net_dump_vty(struct vty *vty, struct gsm_network *net)
unsigned int ho_inactive_count = 0;
llist_for_each_entry(bts, &net->bts_list, list) {
- if (ho_get_ho_active(bts->ho))
+ if (ho_get_ho_active(bts->ho.cfg))
ho_active_count ++;
else
ho_inactive_count ++;
@@ -795,7 +795,7 @@ static void config_write_bts_single(struct vty *vty, struct gsm_bts *bts)
if (bts->pcu_sock_path)
vty_out(vty, " pcu-socket %s%s", bts->pcu_sock_path, VTY_NEWLINE);
- ho_vty_write(vty, " ", bts->ho);
+ ho_vty_write(vty, " ", bts->ho.cfg);
config_write_bts_model(vty, bts);
}
diff --git a/src/libbsc/chan_alloc.c b/src/libbsc/chan_alloc.c
index e37d39c87..d5235c7af 100644
--- a/src/libbsc/chan_alloc.c
+++ b/src/libbsc/chan_alloc.c
@@ -132,6 +132,7 @@ int bts_count_free_ts(struct gsm_bts *bts, enum gsm_phys_chan_config pchan)
return count;
}
+
static struct gsm_lchan *
_lc_find_trx(struct gsm_bts_trx *trx, enum gsm_phys_chan_config pchan,
enum gsm_phys_chan_config dyn_as_pchan)
diff --git a/src/libbsc/handover_decision.c b/src/libbsc/handover_decision.c
index 39b42a678..b1a04d1bb 100644
--- a/src/libbsc/handover_decision.c
+++ b/src/libbsc/handover_decision.c
@@ -215,10 +215,10 @@ static int attempt_handover(struct gsm_meas_rep *mr)
continue;
/* caculate average rxlev for this cell over the window */
- avg = neigh_meas_avg(nmp, ho_get_rxlev_neigh_avg_win(bts->ho));
+ avg = neigh_meas_avg(nmp, ho_get_rxlev_neigh_avg_win(bts->ho.cfg));
/* check if hysteresis is fulfilled */
- if (avg < mr->dl.full.rx_lev + ho_get_pwr_hysteresis(bts->ho))
+ if (avg < mr->dl.full.rx_lev + ho_get_pwr_hysteresis(bts->ho.cfg))
continue;
better = avg - mr->dl.full.rx_lev;
@@ -233,7 +233,7 @@ static int attempt_handover(struct gsm_meas_rep *mr)
LOGP(DHODEC, LOGL_INFO, "%s: Cell on ARFCN %u is better: ",
gsm_ts_name(mr->lchan->ts), best_cell->arfcn);
- if (!ho_get_ho_active(bts->ho)) {
+ if (!ho_get_ho_active(bts->ho.cfg)) {
LOGPC(DHODEC, LOGL_INFO, "Skipping, Handover disabled\n");
return 0;
}
@@ -264,7 +264,7 @@ static int process_meas_rep(struct gsm_meas_rep *mr)
int av_rxlev;
/* If this cell does not use handover algorithm 1, then we're not responsible. */
- if (ho_get_algorithm(bts->ho) != 1)
+ if (ho_get_algorithm(bts->ho.cfg) != 1)
return 0;
/* we currently only do handover for TCH channels */
@@ -289,7 +289,7 @@ static int process_meas_rep(struct gsm_meas_rep *mr)
process_meas_neigh(mr);
av_rxlev = get_meas_rep_avg(mr->lchan, dlev,
- ho_get_rxlev_avg_win(bts->ho));
+ ho_get_rxlev_avg_win(bts->ho.cfg));
/* Interference HO */
if (rxlev2dbm(av_rxlev) > -85 &&
@@ -314,14 +314,14 @@ static int process_meas_rep(struct gsm_meas_rep *mr)
}
/* Distance */
- if (mr->ms_l1.ta > ho_get_max_distance(bts->ho))
+ if (mr->ms_l1.ta > ho_get_max_distance(bts->ho.cfg)) {
LOGPC(DHO, LOGL_INFO, "HO cause: Distance av_rxlev=%d dbm ta=%d \n",
rxlev2dbm(av_rxlev), mr->ms_l1.ta);
return attempt_handover(mr);
}
/* Power Budget AKA Better Cell */
- if ((mr->nr % ho_get_pwr_interval(bts->ho)) == 0)
+ if ((mr->nr % ho_get_pwr_interval(bts->ho.cfg)) == 0)
return attempt_handover(mr);
return 0;
diff --git a/src/libbsc/handover_decision_2.c b/src/libbsc/handover_decision_2.c
new file mode 100644
index 000000000..561d3bd56
--- /dev/null
+++ b/src/libbsc/handover_decision_2.c
@@ -0,0 +1,1728 @@
+/* Handover Decision Algorithm 2 for intra-BSC (inter-BTS) handover, public API for OsmoBSC. */
+
+/* (C) 2017 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * Author: Andreas Eversberg <jolly@eversberg.eu>
+ * Neels Hofmeyr <nhofmeyr@sysmocom.de>
+ *
+ * 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 <stdbool.h>
+#include <errno.h>
+
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/handover.h>
+#include <osmocom/bsc/handover_decision.h>
+#include <osmocom/bsc/handover_decision_2.h>
+#include <osmocom/bsc/handover_cfg.h>
+#include <osmocom/bsc/bsc_subscriber.h>
+#include <osmocom/bsc/chan_alloc.h>
+#include <osmocom/bsc/signal.h>
+
+#define LOGPHOBTS(bts, level, fmt, args...) \
+ LOGP(DHODEC, level, "(BTS %u) " fmt, bts->nr, ## args)
+#define LOGPHOLCHAN(lchan, level, fmt, args...) \
+ LOGP(DHODEC, level, "(BTS %u trx %u ts %u lchan %u %s) (subscr %s) " fmt, \
+ lchan->ts->trx->bts->nr, \
+ lchan->ts->trx->nr, \
+ lchan->ts->nr, \
+ lchan->nr, \
+ gsm_pchan_name(lchan->ts->pchan), \
+ bsc_subscr_name(lchan->conn->bsub), \
+ ## args)
+#define LOGPHOLCHANTOBTS(lchan, new_bts, level, fmt, args...) \
+ LOGP(DHODEC, level, "(BTS %u trx %u ts %u lchan %u %s)->(BTS %u) (subscr %s) " fmt, \
+ lchan->ts->trx->bts->nr, \
+ lchan->ts->trx->nr, \
+ lchan->ts->nr, \
+ lchan->nr, \
+ gsm_pchan_name(lchan->ts->pchan), \
+ new_bts->nr, \
+ bsc_subscr_name(lchan->conn->bsub), \
+ ## args)
+
+#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 (REQUIREMENT_A_TCHF | REQUIREMENT_B_TCHF | REQUIREMENT_C_TCHF)
+#define REQUIREMENT_TCHH_MASK (REQUIREMENT_A_TCHH | REQUIREMENT_B_TCHH | REQUIREMENT_C_TCHH)
+#define REQUIREMENT_A_MASK (REQUIREMENT_A_TCHF | REQUIREMENT_A_TCHH)
+#define REQUIREMENT_B_MASK (REQUIREMENT_B_TCHF | REQUIREMENT_B_TCHH)
+#define REQUIREMENT_C_MASK (REQUIREMENT_C_TCHF | REQUIREMENT_C_TCHH)
+
+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 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 *ho_reason_name(int value)
+{
+ return get_value_string(ho_reason_names, value);
+}
+
+
+static bool ho2_initialized = false;
+static enum ho_reason global_ho_reason;
+
+static void congestion_check_cb(void *arg);
+
+/* This function gets called from various events:
+ * - When a new BTS is registered; if ho2 is not initialized at that point, nothing happens.
+ * - When ho2 is being initialized, all BTS get their congestion timer started (if applicable).
+ * - When a ho config is changed, all BTS that get touched by the change are bumped.
+ * - When a BTS is torn down, the timer could be stopped by setting the interval to 0,
+ * however, osmo-bsc currently has no code path that actually tears down a BTS struct.
+ */
+static void bts_reinit_congestion_timer(struct gsm_bts *bts)
+{
+ int congestion_check_interval_s;
+ int interval_spread_ms = (bts->nr * 10) % 1000;
+ bool was_active;
+
+ /* Don't setup timers from VTY config parsing before the main program has actually initialized
+ * the data structures. */
+ if (!ho2_initialized)
+ return;
+
+ was_active = bts->ho.congestion_check_timer.active;
+ if (was_active)
+ osmo_timer_del(&bts->ho.congestion_check_timer);
+
+ congestion_check_interval_s = ho_get_congestion_check_interval(bts->ho.cfg);
+ if (ho_get_algorithm(bts->ho.cfg) != 2
+ || congestion_check_interval_s < 1) {
+ if (was_active)
+ LOGPHOBTS(bts, LOGL_NOTICE, "HO algorithm 2: Disabling congestion check\n");
+ return;
+ }
+
+ LOGPHOBTS(bts, LOGL_NOTICE, "HO algorithm 2: %s periodical congestion check,"
+ " every %d seconds (+ 0.%03d seconds to spread BTS checks)\n",
+ was_active? "Starting" : "Restarting",
+ congestion_check_interval_s, interval_spread_ms);
+
+ osmo_timer_setup(&bts->ho.congestion_check_timer,
+ congestion_check_cb, bts);
+ osmo_timer_schedule(&bts->ho.congestion_check_timer,
+ congestion_check_interval_s,
+ interval_spread_ms);
+}
+
+static void on_change_congestion_check_interval(void *ctx, enum handover_cfg_ctx_type ctx_type)
+{
+ struct gsm_network *net = NULL;
+ struct gsm_bts *bts;
+
+ switch (ctx_type) {
+ default:
+ LOGP(DHODEC, LOGL_ERROR, "Invalid HO config context type: %d\n", ctx_type);
+ return;
+ case HO_CFG_CTX_BTS:
+ bts_reinit_congestion_timer((struct gsm_bts*)ctx);
+ return;
+ case HO_CFG_CTX_NET:
+ /* Restart HO timers for all BTS */
+ net = ctx;
+ break;
+ }
+
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ /* If the BTS has its own value, the network level config cannot change it. */
+ if (!ho_isset_congestion_check_interval(bts->ho.cfg))
+ continue;
+ bts_reinit_congestion_timer(bts);
+ }
+}
+
+/* did we get a RXLEV for a given cell in the given report? Mark matches as MRC_F_PROCESSED. */
+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];
+
+ if (mrc->arfcn != arfcn)
+ continue;
+ if (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 slot or the worst neighbor. */
+static struct neigh_meas_proc *find_unused_or_worst_neigh(struct gsm_lchan *lchan)
+{
+ struct neigh_meas_proc *nmp_worst = NULL;
+ int worst;
+ int j;
+
+ /* 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. Return worst neighbor to be evicted. */
+ worst = 0; /* (overwritten on first loop, but avoid compiler warning) */
+ 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)
+ continue;
+ 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_unused_or_worst_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;
+ }
+}
+
+static bool codec_type_is_supported(struct gsm_subscriber_connection *conn,
+ enum gsm0808_speech_codec_type type)
+{
+ int i;
+ struct gsm0808_speech_codec_list *clist = &conn->codec_list;
+
+ if (!conn->codec_list_present) {
+ /* We don't have a list of supported codecs. This should never happen. */
+ LOGPHOLCHAN(conn->lchan, LOGL_ERROR,
+ "No Speech Codec List present, accepting all codecs\n");
+ return true;
+ }
+
+ for (i = 0; i < clist->len; i++) {
+ if (clist->codec[i].type == type)
+ return true;
+ }
+ LOGPHOLCHAN(conn->lchan, LOGL_DEBUG, "Codec not supported by MS or not allowed by MSC: %s\n",
+ gsm0808_speech_codec_type_name(type));
+ return false;
+}
+
+/*
+ * Check what requirements the given cell fulfills.
+ * A bit mask of fulfilled requirements is returned.
+ *
+ * Target cell requirement A -- ability to service the call
+ *
+ * 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 -- avoid congestion
+ *
+ * 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 -- balance congestion
+ *
+ * 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)
+{
+ int count;
+ uint8_t requirement = 0;
+ unsigned int penalty_time;
+ struct gsm_bts *current_bts = lchan->ts->trx->bts;
+
+ /* Requirement A */
+
+ /* the handover/assignment must not be disabled */
+ if (current_bts == bts) {
+ if (!ho_get_as_active(bts->ho.cfg)) {
+ LOGPHOLCHAN(lchan, LOGL_DEBUG, "Assignment disabled\n");
+ return 0;
+ }
+ } else {
+ if (!ho_get_ho_active(bts->ho.cfg)) {
+ LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG,
+ "not a candidate, handover is disabled in target BTS\n");
+ return 0;
+ }
+ }
+
+ /* the handover penalty timer must not run for this bts */
+ penalty_time = conn_penalty_timer_remaining(lchan->conn, bts);
+ if (penalty_time) {
+ LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "not a candidate, target BTS still in penalty time"
+ " (%u seconds left)\n", penalty_time);
+ 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) {
+ LOGPHOBTS(bts, LOGL_DEBUG, "HR not supported\n");
+ break;
+ }
+ if (codec_type_is_supported(lchan->conn, GSM0808_SCT_HR1))
+ requirement |= REQUIREMENT_A_TCHH;
+ break;
+ default:
+ LOGPHOLCHAN(lchan, LOGL_ERROR, "Unexpected channel type: neither TCH/F nor TCH/H for %s\n",
+ get_value_string(gsm48_chan_mode_names, lchan->tch_mode));
+ return 0;
+ }
+ break;
+ case GSM48_CMODE_SPEECH_EFR:
+ if (!bts->codec.efr) {
+ LOGPHOBTS(bts, LOGL_DEBUG, "EFR not supported\n");
+ break;
+ }
+ if (codec_type_is_supported(lchan->conn, GSM0808_SCT_FR2))
+ requirement |= REQUIREMENT_A_TCHF;
+ break;
+ case GSM48_CMODE_SPEECH_AMR:
+ if (!bts->codec.amr) {
+ LOGPHOBTS(bts, LOGL_DEBUG, "AMR not supported\n");
+ break;
+ }
+ if (codec_type_is_supported(lchan->conn, GSM0808_SCT_FR3))
+ requirement |= REQUIREMENT_A_TCHF;
+ if (codec_type_is_supported(lchan->conn, GSM0808_SCT_HR3))
+ requirement |= REQUIREMENT_A_TCHH;
+ break;
+ default:
+ LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "Not even considering: src is not a SPEECH mode lchan\n");
+ return 0;
+ }
+
+ /* no candidate, because new cell is incompatible */
+ if (!requirement) {
+ LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "not a candidate, because codec of MS and BTS are incompatible\n");
+ return 0;
+ }
+
+ /* remove slot types that are not available */
+ if (!tchf_count)
+ requirement &= ~(REQUIREMENT_A_TCHF);
+ if (!tchh_count)
+ requirement &= ~(REQUIREMENT_A_TCHH);
+
+ if (!requirement) {
+ LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG, "not a candidate, because no suitable slots available\n");
+ 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;
+ }
+
+ if (!requirement) {
+ LOGPHOLCHAN(lchan, LOGL_DEBUG,
+ "Reassignment within cell not an option, no differing channel types available\n");
+ return 0;
+ }
+ }
+
+#ifdef LEGACY
+ // This was useful in osmo-nitb. We're in osmo-bsc now and have no idea whether the osmo-msc does
+ // internal or external call control. Maybe a future config switch wants to add this behavior?
+ /* 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(&current_bts->mr_full, &bts->mr_full,
+ sizeof(struct amr_multirate_conf)))
+ requirement &= ~(REQUIREMENT_A_TCHF);
+ if ((requirement & REQUIREMENT_A_TCHH)
+ && !!memcmp(&current_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(&current_bts->mr_half, &bts->mr_full,
+ sizeof(struct amr_multirate_conf)))
+ requirement &= ~(REQUIREMENT_A_TCHF);
+ if ((requirement & REQUIREMENT_A_TCHH)
+ && !!memcmp(&current_bts->mr_half, &bts->mr_half,
+ sizeof(struct amr_multirate_conf)))
+ requirement &= ~(REQUIREMENT_A_TCHH);
+ break;
+ default:
+ break;
+ }
+
+ if (!requirement) {
+ LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG,
+ "not a candidate, cannot provide identical codec rate\n");
+ return 0;
+ }
+ }
+#endif
+
+ /* the maximum number of unsynchonized handovers must no be exceeded */
+ if (current_bts != bts
+ && bsc_ho_count(bts, true) >= ho_get_ho_max(bts->ho.cfg)) {
+ LOGPHOLCHANTOBTS(lchan, bts, LOGL_DEBUG,
+ "not a candidate, number of allowed handovers (%d) would be exceeded\n",
+ ho_get_ho_max(bts->ho.cfg));
+ 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 >= ho_get_tchf_min_slots(bts->ho.cfg))
+ requirement |= REQUIREMENT_B_TCHF;
+ if ((requirement & REQUIREMENT_A_TCHH)
+ && tchh_count - 1 >= ho_get_tchh_min_slots(bts->ho.cfg))
+ requirement |= REQUIREMENT_B_TCHH;
+
+ /* Requirement C */
+
+ /* the nr of free timeslots of the target cell must be >= the
+ * free slots of the current cell _after_ handover/assignment */
+ count = bts_count_free_ts(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 afs_bias = 0;
+ bool full_rate = false;
+
+ if (current_bts == new_bts)
+ LOGPHOLCHAN(lchan, LOGL_NOTICE, "Triggering Assignment\n");
+ else
+ LOGPHOLCHANTOBTS(lchan, new_bts, LOGL_NOTICE, "Triggering Handover\n");
+
+ /* afs_bias becomes > 0, if AFS is used and is improved */
+ if (lchan->tch_mode == GSM48_CMODE_SPEECH_AMR)
+ afs_bias = ho_get_afs_bias_rxlev(new_bts->ho.cfg);
+
+ /* select TCH rate, prefer TCH/F if AFS is improved */
+ switch (lchan->type) {
+ case GSM_LCHAN_TCH_F:
+ /* keep on full rate, if TCH/F is a candidate */
+ if ((requirements & REQUIREMENT_TCHF_MASK)) {
+ if (current_bts == new_bts) {
+ LOGPHOLCHAN(lchan, LOGL_INFO, "Not performing assignment: Already on target type\n");
+ return 0;
+ }
+ full_rate = true;
+ break;
+ }
+ /* change to half rate */
+ if (!(requirements & REQUIREMENT_TCHH_MASK)) {
+ LOGPHOLCHANTOBTS(lchan, new_bts, LOGL_ERROR,
+ "neither TCH/F nor TCH/H requested, aborting ho/as\n");
+ return -EINVAL;
+ }
+ break;
+ case GSM_LCHAN_TCH_H:
+ /* change to full rate if AFS is improved and a candidate */
+ if (afs_bias > 0 && (requirements & REQUIREMENT_TCHF_MASK)) {
+ full_rate = true;
+ LOGPHOLCHAN(lchan, LOGL_DEBUG, "[Improve AHS->AFS]\n");
+ break;
+ }
+ /* change to full rate if the only candidate */
+ if ((requirements & REQUIREMENT_TCHF_MASK)
+ && !(requirements & REQUIREMENT_TCHH_MASK)) {
+ full_rate = true;
+ break;
+ }
+ /* keep on half rate */
+ if (!(requirements & REQUIREMENT_TCHH_MASK)) {
+ LOGPHOLCHANTOBTS(lchan, new_bts, LOGL_ERROR,
+ "neither TCH/F nor TCH/H requested, aborting ho/as\n");
+ return -EINVAL;
+ }
+ if (current_bts == new_bts) {
+ LOGPHOLCHAN(lchan, LOGL_INFO, "Not performing assignment: Already on target type\n");
+ return 0;
+ }
+ break;
+ default:
+ LOGPHOLCHANTOBTS(lchan, new_bts, LOGL_ERROR, "lchan is neither TCH/F nor TCH/H, aborting ho/as\n");
+ return -EINVAL;
+ }
+
+ /* trigger handover or assignment */
+ if (current_bts == new_bts)
+ LOGPHOLCHAN(lchan, LOGL_NOTICE, "Triggering assignment to %s, due to %s\n",
+ full_rate ? "TCH/F" : "TCH/H",
+ ho_reason_name(global_ho_reason));
+ else
+ LOGPHOLCHANTOBTS(lchan, new_bts, LOGL_NOTICE,
+ "Triggering handover to %s, due to %s\n",
+ full_rate ? "TCH/F" : "TCH/H",
+ ho_reason_name(global_ho_reason));
+
+ return bsc_handover_start_lchan_change(lchan, current_bts == new_bts? NULL : new_bts,
+ full_rate? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H);
+}
+
+/* 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, ho_get_tchf_min_slots(candidate->bts->ho.cfg));
+ LOGP(DHODEC, LOGL_DEBUG, " o free TCH/H slots %d, minimum required "
+ "%d\n", tchh_count, ho_get_tchh_min_slots(candidate->bts->ho.cfg));
+
+ 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");
+}
+
+/* add candidate for re-assignment within the current cell */
+static void collect_assignment_candidate(struct gsm_lchan *lchan, struct ho_candidate *clist,
+ unsigned int *candidates, int av_rxlev)
+{
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ int tchf_count, tchh_count;
+ struct ho_candidate *c;
+
+ tchf_count = bts_count_free_ts(bts, GSM_PCHAN_TCH_F);
+ tchh_count = bts_count_free_ts(bts, GSM_PCHAN_TCH_H);
+
+ c = &clist[*candidates];
+ c->lchan = lchan;
+ c->bts = bts;
+ c->requirements = check_requirements(lchan, bts, tchf_count, tchh_count);
+ c->avg = av_rxlev;
+ debug_candidate(c, 0, 0, tchf_count, tchh_count);
+ (*candidates)++;
+}
+
+/* add candidates for handover to all neighbor cells */
+static void collect_handover_candidate(struct gsm_lchan *lchan, struct neigh_meas_proc *nmp,
+ struct ho_candidate *clist, unsigned int *candidates,
+ bool require_better_level, bool check_hyst, int av_rxlev)
+{
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ int tchf_count, tchh_count;
+ struct gsm_bts *neighbor_bts;
+ int avg;
+ struct ho_candidate *c;
+ int min_rxlev;
+
+ /* skip empty slots */
+ if (nmp->arfcn == 0)
+ return;
+
+ /* skip if measurement report is old */
+ if (nmp->last_seen_nr != lchan->meas_rep_cnt - 1) {
+ LOGPHOBTS(bts, LOGL_DEBUG, "neighbor ARFCN %u measurement report is old\n",
+ nmp->arfcn);
+ return;
+ }
+
+ neighbor_bts = bts_by_arfcn_bsic(bts->network, nmp->arfcn, nmp->bsic);
+ if (!neighbor_bts) {
+ LOGPHOBTS(bts, LOGL_DEBUG, "neighbor ARFCN %u does not belong to this network\n",
+ nmp->arfcn);
+ return;
+ }
+
+ /* in case we have measurements of our bts, due to misconfiguration */
+ if (neighbor_bts == bts) {
+ LOGPHOBTS(bts, LOGL_ERROR, "Configuration error: this BTS appears as its own neighbor\n");
+ return;
+ }
+
+ /* caculate average rxlev for this cell over the window */
+ avg = neigh_meas_avg(nmp, ho_get_rxlev_neigh_avg_win(bts->ho.cfg));
+
+ /* check if rx level is better */
+ if (require_better_level && check_hyst) {
+ unsigned int pwr_hyst = ho_get_pwr_hysteresis(bts->ho.cfg);
+ if (avg <= (av_rxlev + pwr_hyst)) {
+ LOGPHOLCHAN(lchan, 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), pwr_hyst);
+ return;
+ }
+ }
+
+ if (require_better_level && (avg <= av_rxlev)) {
+ LOGPHOLCHAN(lchan, LOGL_DEBUG,
+ "BTS %d is not a candidate, because its RX level (%d) is not better"
+ " than the current RX level (%d)\n",
+ neighbor_bts->nr, rxlev2dbm(avg), rxlev2dbm(av_rxlev));
+ return;
+ }
+
+ /* if the minimum level is not reached */
+ min_rxlev = ho_get_min_rxlev(neighbor_bts->ho.cfg);
+ if (rxlev2dbm(avg) < min_rxlev) {
+ LOGPHOLCHAN(lchan, LOGL_DEBUG,
+ "BTS %d is not a candidate, because RX level (%d) is lower"
+ " than its minimum required RX level (%d)\n",
+ neighbor_bts->nr, rxlev2dbm(avg), min_rxlev);
+ return;
+ }
+
+ tchf_count = bts_count_free_ts(neighbor_bts, GSM_PCHAN_TCH_F);
+ tchh_count = bts_count_free_ts(neighbor_bts, GSM_PCHAN_TCH_H);
+ c = &clist[*candidates];
+ c->lchan = lchan;
+ c->bts = neighbor_bts;
+ c->requirements = check_requirements(lchan, neighbor_bts, tchf_count,
+ tchh_count);
+ c->avg = avg;
+ debug_candidate(c, 1, av_rxlev, tchf_count, tchh_count);
+ (*candidates)++;
+}
+
+static void collect_candidates_for_lchan(struct gsm_lchan *lchan,
+ struct ho_candidate *clist, unsigned int *candidates,
+ int *_av_rxlev, bool require_better_level, bool check_hyst)
+{
+ struct gsm_bts *bts = lchan->ts->trx->bts;
+ int av_rxlev;
+ unsigned int candidates_was;
+ bool assignment;
+ bool handover;
+
+ OSMO_ASSERT(candidates);
+ candidates_was = *candidates;
+
+ /* caculate average rxlev for this cell over the window */
+ av_rxlev = get_meas_rep_avg(lchan,
+ ho_get_full_tdma(bts->ho.cfg) ?
+ MEAS_REP_DL_RXLEV_FULL : MEAS_REP_DL_RXLEV_SUB,
+ ho_get_rxlev_avg_win(bts->ho.cfg));
+ if (_av_rxlev)
+ *_av_rxlev = av_rxlev;
+
+ /* in case there is no measurment report (yet) */
+ if (av_rxlev < 0) {
+ LOGPHOLCHAN(lchan, LOGL_DEBUG, "Not collecting candidates, not enough measurements\n");
+ return;
+ }
+
+ assignment = ho_get_as_active(bts->ho.cfg);
+ handover = ho_get_ho_active(bts->ho.cfg);
+
+ LOGPHOLCHAN(lchan, LOGL_DEBUG, "Collecting candidates for%s%s%s\n",
+ assignment ? " Assignment" : "",
+ assignment && handover ? " and" : "",
+ handover ? " Handover" : "");
+
+ if (assignment)
+ collect_assignment_candidate(lchan, clist, candidates, av_rxlev);
+
+ if (handover) {
+ int i;
+ for (i = 0; i < ARRAY_SIZE(lchan->neigh_meas); i++) {
+ collect_handover_candidate(lchan, &lchan->neigh_meas[i],
+ clist, candidates,
+ require_better_level, check_hyst, av_rxlev);
+ }
+ }
+
+ LOGPHOLCHAN(lchan, LOGL_DEBUG, "adding %u candidates, total %u\n", *candidates - candidates_was, *candidates);
+}
+
+/*
+ * Search for a alternative / 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 find_alternative_lchan(struct gsm_lchan *lchan, bool require_better_cell)
+{
+ struct gsm_bts *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)];
+ unsigned int candidates = 0;
+ int i;
+ struct ho_candidate *best_cand = NULL;
+ unsigned int best_better_db;
+ bool best_applied_afs_bias = false;
+ int better;
+
+ /* check for disabled handover/assignment at the current cell */
+ if (!ho_get_as_active(bts->ho.cfg)
+ && !ho_get_ho_active(bts->ho.cfg)) {
+ LOGP(DHODEC, LOGL_INFO, "Skipping, Handover and Assignment both disabled in this cell\n");
+ return 0;
+ }
+
+ /* collect candidates */
+ collect_candidates_for_lchan(lchan, clist, &candidates, &av_rxlev, require_better_cell, require_better_cell);
+
+ /* If assignment is disabled and no neighbor cell report exists, or no neighbor cell qualifies, we may not even
+ * have any candidates. */
+ if (!candidates)
+ goto no_candidates;
+
+ /* select best candidate that fulfills requirement B */
+ best_better_db = 0;
+ for (i = 0; i < candidates; i++) {
+ int afs_bias;
+ if (!(clist[i].requirements & REQUIREMENT_B_MASK))
+ continue;
+
+ better = clist[i].avg - av_rxlev;
+ /* Apply AFS bias? */
+ afs_bias = 0;
+ if (ahs && (clist[i].requirements & REQUIREMENT_B_TCHF))
+ afs_bias = ho_get_afs_bias_rxlev(clist[i].bts->ho.cfg);
+ better += afs_bias;
+ if (better > best_better_db) {
+ best_cand = &clist[i];
+ best_better_db = better;
+ best_applied_afs_bias = afs_bias? true : false;
+ }
+ }
+
+ /* perform handover, if there is a candidate */
+ if (best_cand) {
+ LOGPHOLCHANTOBTS(lchan, best_cand->bts, LOGL_INFO, "Best candidate, RX level %d%s\n",
+ rxlev2dbm(best_cand->avg),
+ best_applied_afs_bias ? " (applied AHS -> AFS rxlev bias)" : "");
+ 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++) {
+ int afs_bias;
+ if (!(clist[i].requirements & REQUIREMENT_C_MASK))
+ continue;
+
+ better = clist[i].avg - av_rxlev;
+ /* Apply AFS bias? */
+ afs_bias = 0;
+ if (ahs && (clist[i].requirements & REQUIREMENT_C_TCHF))
+ afs_bias = ho_get_afs_bias_rxlev(clist[i].bts->ho.cfg);
+ better += afs_bias;
+ if (better > best_better_db) {
+ best_cand = &clist[i];
+ best_better_db = better;
+ best_applied_afs_bias = afs_bias? true : false;
+ }
+ }
+
+ /* perform handover, if there is a candidate */
+ if (best_cand) {
+ LOGPHOLCHANTOBTS(lchan, best_cand->bts, LOGL_INFO, "Best candidate, RX level %d%s\n",
+ rxlev2dbm(best_cand->avg),
+ best_applied_afs_bias? " (applied AHS -> AFS rxlev bias)" : "");
+ 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 (require_better_cell)
+ goto no_candidates;
+
+ /* select best candidate that fulfills requirement A */
+ best_better_db = 0;
+ for (i = 0; i < candidates; i++) {
+ int afs_bias;
+ if (!(clist[i].requirements & REQUIREMENT_A_MASK))
+ continue;
+
+ better = clist[i].avg - av_rxlev;
+ /* Apply AFS bias? */
+ afs_bias = 0;
+ if (ahs && (clist[i].requirements & REQUIREMENT_A_TCHF))
+ afs_bias = ho_get_afs_bias_rxlev(clist[i].bts->ho.cfg);
+ better += afs_bias;
+ if (better > best_better_db) {
+ best_cand = &clist[i];
+ best_better_db = better;
+ best_applied_afs_bias = afs_bias? true : false;
+ }
+ }
+
+ /* perform handover, if there is a candidate */
+ if (best_cand) {
+ LOGPHOLCHANTOBTS(lchan, best_cand->bts, LOGL_INFO, "Best candidate, RX level %d"
+ " with greater congestion found%s\n",
+ rxlev2dbm(best_cand->avg),
+ best_applied_afs_bias ? " (applied AHS -> AFS rxlev bias)" : "");
+ return trigger_handover_or_assignment(lchan, best_cand->bts,
+ best_cand->requirements & REQUIREMENT_A_MASK);
+ }
+
+no_candidates:
+ if (require_better_cell)
+ LOGPHOLCHAN(lchan, LOGL_INFO, "No better neighbor cell found\n");
+ else
+ LOGPHOLCHAN(lchan, LOGL_INFO, "No alternative lchan 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) {
+ LOGPHOLCHAN(lchan, LOGL_ERROR, "Skipping, No subscriber connection???\n");
+ return 0;
+ }
+ if (lchan->conn->secondary_lchan) {
+ LOGPHOLCHAN(lchan, LOGL_INFO, "Skipping, Initial Assignment is still ongoing\n");
+ return 0;
+ }
+ if (lchan->conn->ho_lchan) {
+ LOGPHOLCHAN(lchan, 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,
+ ho_get_full_tdma(bts->ho.cfg) ?
+ MEAS_REP_DL_RXLEV_FULL : MEAS_REP_DL_RXLEV_SUB,
+ ho_get_rxlev_avg_win(bts->ho.cfg));
+ av_rxqual = get_meas_rep_avg(lchan,
+ ho_get_full_tdma(bts->ho.cfg) ?
+ MEAS_REP_DL_RXQUAL_FULL : MEAS_REP_DL_RXQUAL_SUB,
+ ho_get_rxqual_avg_win(bts->ho.cfg));
+ if (av_rxlev < 0 && av_rxqual < 0) {
+ LOGPHOLCHAN(lchan, LOGL_INFO, "Skipping, Not enough recent measuements\n");
+ return 0;
+ }
+ if (av_rxlev >= 0) {
+ LOGPHOLCHAN(lchan, LOGL_DEBUG, "Measurement report: average RX level = %d\n",
+ rxlev2dbm(av_rxlev));
+ }
+ if (av_rxqual >= 0) {
+ LOGPHOLCHAN(lchan, 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) {
+ int rxlev_bias = ho_get_afs_bias_rxlev(bts->ho.cfg);
+ int rxqual_bias = ho_get_afs_bias_rxqual(bts->ho.cfg);
+ if (av_rxlev >= 0 && rxlev_bias) {
+ int imp = av_rxlev + rxlev_bias;
+ LOGPHOLCHAN(lchan, LOGL_INFO, "Virtually improving RX level from %d to %d,"
+ " due to AFS bias\n", rxlev2dbm(av_rxlev), rxlev2dbm(imp));
+ av_rxlev = imp;
+ }
+ if (av_rxqual >= 0 && rxqual_bias) {
+ int imp = av_rxqual - rxqual_bias;
+ if (imp < 0)
+ imp = 0;
+ LOGPHOLCHAN(lchan, LOGL_INFO, "Virtually improving RX quality from %d to %d,"
+ " due to AFS bias\n", rxlev2dbm(av_rxqual), rxlev2dbm(imp));
+ av_rxqual = imp;
+ }
+ }
+
+ /* Bad Quality */
+ if (av_rxqual >= 0 && av_rxqual > ho_get_min_rxqual(bts->ho.cfg)) {
+ if (rxlev2dbm(av_rxlev) > -85) {
+ global_ho_reason = HO_REASON_INTERFERENCE;
+ LOGPHOLCHAN(lchan, LOGL_INFO, "Trying handover/assignment"
+ " due to interference (bad quality)\n");
+ } else {
+ global_ho_reason = HO_REASON_BAD_QUALITY;
+ LOGPHOLCHAN(lchan, LOGL_INFO, "Trying handover/assignment due to bad quality\n");
+ }
+ rc = find_alternative_lchan(lchan, false);
+ if (lchan->conn->ho_lchan || lchan->conn->secondary_lchan)
+ return rc;
+ return 0;
+ }
+
+ /* Low Level */
+ if (av_rxlev >= 0 && rxlev2dbm(av_rxlev) < ho_get_min_rxlev(bts->ho.cfg)) {
+ global_ho_reason = HO_REASON_LOW_RXLEVEL;
+ LOGPHOLCHAN(lchan, LOGL_INFO, "Attempting handover/assignment due to low rxlev\n");
+ rc = find_alternative_lchan(lchan, false);
+ if (lchan->conn->ho_lchan || lchan->conn->secondary_lchan)
+ return rc;
+ return 0;
+ }
+
+ /* Max Distance */
+ if (lchan->meas_rep_cnt > 0
+ && lchan->rqd_ta > ho_get_max_distance(bts->ho.cfg)) {
+ global_ho_reason = HO_REASON_MAX_DISTANCE;
+ LOGPHOLCHAN(lchan, LOGL_INFO, "Attempting handover due to 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. */
+ conn_penalty_timer_add(lchan->conn, bts, ho_get_penalty_max_dist(bts->ho.cfg));
+ rc = find_alternative_lchan(lchan, false);
+ 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 % ho_get_pwr_interval(bts->ho.cfg)) == 0) {
+ LOGPHOLCHAN(lchan, LOGL_INFO, "Looking for cell with less or equal congestion after handover\n");
+ global_ho_reason = HO_REASON_BETTER_CELL;
+ rc = find_alternative_lchan(lchan, true);
+ 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 bts_resolve_congestion(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;
+ unsigned int candidates;
+ 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;
+
+ LOGPHOBTS(bts, LOGL_INFO, "congested: %d TCH/F and %d TCH/H should be moved\n",
+ 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;
+
+ candidates = 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;
+
+ /* (Do not consider dynamic TS that are in PDCH mode) */
+ switch (ts_pchan(ts)) {
+ 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 if there is an ongoing ho/as */
+ if (!lc->conn || lc->conn->secondary_lchan
+ || lc->conn->ho_lchan)
+ break;
+ /* collect candidates */
+ collect_candidates_for_lchan(lc, clist, &candidates, NULL,
+ /* better level */ false,
+ /* check hysteresis */ false);
+ 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;
+ collect_candidates_for_lchan(lc, clist, &candidates, NULL,
+ /* better level */ false,
+ /* check hysteresis */ false);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if (!candidates) {
+ LOGPHOBTS(bts, LOGL_DEBUG, "No neighbor cells qualify to solve congestion\n");
+ goto exit;
+ }
+ LOGPHOBTS(bts, LOGL_DEBUG, "Considering %u candidates to solve congestion\n", candidates);
+
+#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 += ho_get_afs_bias_rxlev(clist[i].bts->ho.cfg);
+ 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;
+ LOGPHOLCHAN(best_cand->lchan, LOGL_INFO,
+ "Best candidate BTS %u (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 += ho_get_afs_bias_rxlev(clist[i].bts->ho.cfg);
+ 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 += ho_get_afs_bias_rxlev(clist[i].bts->ho.cfg);
+ 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 += ho_get_afs_bias_rxlev(clist[i].bts->ho.cfg);
+ 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 handover_decision_2_bts_congestion_check(struct gsm_bts *bts)
+{
+ int min_free_tchf, min_free_tchh;
+ int tchf_count, tchh_count;
+
+ global_ho_reason = HO_REASON_CONGESTION;
+
+ /* only check BTS if TRX 0 is usable */
+ if (!trx_is_usable(bts->c0)) {
+ LOGPHOBTS(bts, LOGL_DEBUG, "No congestion check: TRX 0 not usable\n");
+ return;
+ }
+
+ /* only check BTS if handover or assignment is enabled */
+ if (!ho_get_as_active(bts->ho.cfg)
+ && !ho_get_ho_active(bts->ho.cfg)) {
+ LOGPHOBTS(bts, LOGL_DEBUG, "No congestion check: Assignment and Handover both disabled\n");
+ return;
+ }
+
+ min_free_tchf = ho_get_tchf_min_slots(bts->ho.cfg);
+ min_free_tchh = ho_get_tchh_min_slots(bts->ho.cfg);
+
+ /* only check BTS with congestion level set */
+ if (!min_free_tchf && !min_free_tchh) {
+ LOGPHOBTS(bts, LOGL_DEBUG, "No congestion check: no minimum for free TCH/F nor TCH/H set\n");
+ return;
+ }
+
+ tchf_count = bts_count_free_ts(bts, GSM_PCHAN_TCH_F);
+ tchh_count = bts_count_free_ts(bts, GSM_PCHAN_TCH_H);
+ LOGPHOBTS(bts, LOGL_INFO, "Congestion check: (free/want-free) TCH/F=%d/%d TCH/H=%d/%d\n",
+ tchf_count, min_free_tchf, tchh_count, min_free_tchh);
+
+ /* only check BTS if congested */
+ if (tchf_count >= min_free_tchf && tchh_count >= min_free_tchh) {
+ LOGPHOBTS(bts, LOGL_DEBUG, "Not congested\n");
+ return;
+ }
+
+ LOGPHOBTS(bts, LOGL_DEBUG, "Attempting to resolve congestion...\n");
+ bts_resolve_congestion(bts, min_free_tchf - tchf_count, min_free_tchh - tchh_count);
+}
+
+static void congestion_check_cb(void *arg)
+{
+ struct gsm_bts *bts = arg;
+ handover_decision_2_bts_congestion_check(bts);
+ bts_reinit_congestion_timer(bts);
+}
+
+static int ho_dec_2_sig_lchan(unsigned int signal, void *handler_data, void *signal_data)
+{
+ struct lchan_signal_data *lchan_data = signal_data;
+
+ switch (signal) {
+ case S_LCHAN_MEAS_REP:
+ /* This is Handover Algorithm 2. If we're not responsible, drop it. */
+ if (ho_get_algorithm(lchan_data->lchan->ts->trx->bts->ho.cfg) != 2)
+ return 0;
+
+ attempt_handover_after_mr(lchan_data->mr);
+ break;
+ }
+
+ return 0;
+}
+
+static int ho_dec_2_sig_global(unsigned int signal, void *handler_data, void *signal_data)
+{
+ switch (signal) {
+ case S_GLOBAL_BTS_NEW:
+ bts_reinit_congestion_timer((struct gsm_bts*)signal_data);
+ return 0;
+ default:
+ return 0;
+ }
+}
+
+static int ho_dec_2_sig_cb(unsigned int subsys, unsigned int signal, void *handler_data,
+ void *signal_data)
+{
+ switch (subsys) {
+ case SS_LCHAN:
+ return ho_dec_2_sig_lchan(signal, handler_data, signal_data);
+ case SS_L_GLOBAL:
+ return ho_dec_2_sig_global(signal, handler_data, signal_data);
+ default:
+ return 0;
+ }
+}
+
+
+void handover_decision_2_init(struct gsm_network *net)
+{
+ struct gsm_bts *bts;
+ ho_cfg_on_change_congestion_check_interval_cb = on_change_congestion_check_interval;
+ osmo_signal_register_handler(SS_LCHAN, ho_dec_2_sig_cb, NULL);
+ ho2_initialized = true;
+
+ llist_for_each_entry(bts, &net->bts_list, list)
+ bts_reinit_congestion_timer(bts);
+}
diff --git a/src/libbsc/handover_logic.c b/src/libbsc/handover_logic.c
index 997ca9c21..12cc08920 100644
--- a/src/libbsc/handover_logic.c
+++ b/src/libbsc/handover_logic.c
@@ -38,6 +38,29 @@
#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>
+
+#define LOGPHOLCHANTOLCHAN(lchan, new_lchan, level, fmt, args...) \
+ LOGP(DHODEC, level, "(BTS %u trx %u arfcn %u ts %u lchan %u %s)->(BTS %u trx %u arfcn %u ts %u lchan %u %s) (subscr %s) " fmt, \
+ lchan->ts->trx->bts->nr, \
+ lchan->ts->trx->nr, \
+ lchan->ts->trx->arfcn, \
+ lchan->ts->nr, \
+ lchan->nr, \
+ gsm_pchan_name(lchan->ts->pchan), \
+ new_lchan->ts->trx->bts->nr, \
+ new_lchan->ts->trx->nr, \
+ new_lchan->ts->trx->arfcn, \
+ new_lchan->ts->nr, \
+ new_lchan->nr, \
+ gsm_pchan_name(new_lchan->ts->pchan), \
+ bsc_subscr_name(lchan->conn->bsub), \
+ ## args)
+
+#define LOGPHO(struct_bsc_handover, level, fmt, args ...) \
+ LOGPHOLCHANTOLCHAN(struct_bsc_handover->old_lchan, struct_bsc_handover->new_lchan, level, fmt, ## args)
+
struct bsc_handover {
struct llist_head list;
@@ -86,36 +109,56 @@ static struct bsc_handover *bsc_ho_by_old_lchan(struct gsm_lchan *old_lchan)
return NULL;
}
-/*! \brief Hand over the specified logical channel to the specified new BTS.
- * This is the main entry point for the actual handover algorithm, after the
- * decision whether to initiate HO to a specific BTS. */
+/*! Hand over the specified logical channel to the specified new BTS. */
int bsc_handover_start(struct gsm_lchan *old_lchan, struct gsm_bts *bts)
{
+ return bsc_handover_start_lchan_change(old_lchan, bts, old_lchan->type);
+}
+
+/*! 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. */
+int bsc_handover_start_lchan_change(struct gsm_lchan *old_lchan, struct gsm_bts *new_bts,
+ enum gsm_chan_t new_lchan_type)
+{
+ struct gsm_network *network;
struct gsm_lchan *new_lchan;
struct bsc_handover *ho;
static uint8_t ho_ref = 0;
int rc;
+ bool do_assignment = false;
/* 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, "Beginning with handover operation"
- "(old_lchan on BTS %u, new BTS %u) ...\n",
- old_lchan->ts->trx->bts->nr, bts->nr);
+ if (!new_bts)
+ new_bts = old_lchan->ts->trx->bts;
+ do_assignment = (new_bts == old_lchan->ts->trx->bts);
- rate_ctr_inc(&bts->network->bsc_ctrs->ctr[BSC_CTR_HANDOVER_ATTEMPTED]);
+ network = new_bts->network;
+
+ rate_ctr_inc(&network->bsc_ctrs->ctr[BSC_CTR_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);
+ DEBUGP(DHO, "(BTS %u trx %u ts %u lchan %u %s)->(BTS %u lchan %s) Beginning with handover operation...\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));
+
+ new_lchan = lchan_alloc(new_bts, new_lchan_type, 0);
if (!new_lchan) {
- LOGP(DHO, LOGL_NOTICE, "No free channel\n");
- rate_ctr_inc(&bts->network->bsc_ctrs->ctr[BSC_CTR_HANDOVER_NO_CHANNEL]);
+ LOGP(DHO, LOGL_NOTICE, "No free channel for %s\n", gsm_lchant_name(new_lchan_type));
+ rate_ctr_inc(&network->bsc_ctrs->ctr[BSC_CTR_HANDOVER_NO_CHANNEL]);
return -ENOSPC;
}
@@ -128,31 +171,41 @@ int bsc_handover_start(struct gsm_lchan *old_lchan, struct gsm_bts *bts)
ho->old_lchan = old_lchan;
ho->new_lchan = new_lchan;
ho->ho_ref = ho_ref++;
- if (old_lchan->ts->trx->bts != bts) {
+ if (!do_assignment) {
ho->inter_cell = true;
ho->async = true;
}
+ LOGPHO(ho, LOGL_INFO, "Triggering %s\n", do_assignment? "Assignment" : "Handover");
+
/* 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->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, ARRAY_SIZE(new_lchan->mr_ms_lv));
- memcpy(&new_lchan->mr_bts_lv, &old_lchan->mr_bts_lv, ARRAY_SIZE(new_lchan->mr_bts_lv));
+ 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? */
rc = rsl_chan_activate_lchan(new_lchan,
ho->inter_cell
? (ho->async ? RSL_ACT_INTER_ASYNC : RSL_ACT_INTER_SYNC)
: RSL_ACT_INTRA_IMM_ASS,
ho->ho_ref);
if (rc < 0) {
- LOGP(DHO, LOGL_ERROR, "could not activate channel\n");
+ LOGPHO(ho, LOGL_INFO, "%s Failure: activate lchan rc = %d\n",
+ do_assignment? "Assignment" : "Handover", rc);
new_lchan->conn->ho_lchan = NULL;
new_lchan->conn = NULL;
talloc_free(ho);
@@ -218,12 +271,16 @@ static int ho_chan_activ_ack(struct gsm_lchan *new_lchan)
if (!ho)
return -ENODEV;
- DEBUGP(DHO, "handover activate ack, send HO Command\n");
+ 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, 0, ho->ho_ref);
+ if (ho->inter_cell) {
+ 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 */
@@ -241,6 +298,7 @@ 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) {
@@ -248,6 +306,11 @@ static int ho_chan_activ_nack(struct gsm_lchan *new_lchan)
return -ENODEV;
}
+ LOGPHO(ho, LOGL_ERROR, "Channel Activate Nack for %s, starting penalty timer\n", ho->inter_cell? "Handover" : "Assignment");
+
+ /* if channel failed, wait 10 seconds befor allowing to retry handover */
+ conn_penalty_timer_add(ho->old_lchan->conn, new_bts, 10); /* FIXME configurable */
+
new_lchan->conn->ho_lchan = NULL;
new_lchan->conn = NULL;
handover_free(ho);
@@ -269,22 +332,19 @@ 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 HANDOVER COMPLETE\n", bsc_subscr_name(ho->old_lchan->conn->bsub),
- ho->old_lchan->ts->trx->bts->nr, new_lchan->ts->trx->bts->nr,
- ho->old_lchan->ts->trx->arfcn, new_lchan->ts->trx->arfcn);
+ LOGPHO(ho, LOGL_INFO, "%s Complete\n", ho->inter_cell ? "Handover" : "Assignment");
+ net = new_lchan->ts->trx->bts->network;
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)
- LOGP(DHO, LOGL_ERROR, "Primary lchan changed during handover.\n");
+ LOGPHO(ho, LOGL_ERROR, "Primary lchan changed during handover.\n");
if (new_lchan != new_lchan->conn->ho_lchan)
- LOGP(DHO, LOGL_ERROR, "Handover channel changed during this handover.\n");
+ LOGPHO(ho, LOGL_ERROR, "Handover channel changed during this handover.\n");
new_lchan->conn->ho_lchan = NULL;
new_lchan->conn->lchan = new_lchan;
@@ -301,6 +361,8 @@ static int ho_gsm48_ho_compl(struct gsm_lchan *new_lchan)
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;
+ struct gsm_bts *new_bts;
struct bsc_handover *ho;
struct gsm_lchan *new_lchan;
@@ -310,8 +372,24 @@ static int ho_gsm48_ho_fail(struct gsm_lchan *old_lchan)
return -ENODEV;
}
- LOGP(DHO, LOGL_ERROR, "%s -> %s HANDOVER FAIL\n",
- gsm_lchan_name(old_lchan), gsm_lchan_name(ho->new_lchan));
+ old_bts = old_lchan->ts->trx->bts;
+ new_bts = ho->new_lchan->ts->trx->bts;
+
+ if (old_lchan->conn->ho_failure >= ho_get_retries(old_bts->ho.cfg)) {
+ int penalty = ho->inter_cell
+ ? ho_get_penalty_failed_ho(old_bts->ho.cfg)
+ : ho_get_penalty_failed_as(old_bts->ho.cfg);
+ LOGPHO(ho, LOGL_NOTICE, "%s failed, starting penalty timer (%d)\n",
+ ho->inter_cell ? "Handover" : "Assignment",
+ penalty);
+ old_lchan->conn->ho_failure = 0;
+ conn_penalty_timer_add(old_lchan->conn, new_bts, penalty);
+ } else {
+ old_lchan->conn->ho_failure++;
+ LOGPHO(ho, LOGL_NOTICE, "%s failed, trying again (%d/%d)\n",
+ ho->inter_cell ? "Handover" : "Assignment",
+ old_lchan->conn->ho_failure, ho_get_retries(old_bts->ho.cfg));
+ }
rate_ctr_inc(&net->bsc_ctrs->ctr[BSC_CTR_HANDOVER_FAILED]);
@@ -324,7 +402,6 @@ static int ho_gsm48_ho_fail(struct gsm_lchan *old_lchan)
lchan_release(new_lchan, 0, RSL_REL_LOCAL_END);
-
return 0;
}
@@ -339,8 +416,7 @@ static int ho_rsl_detect(struct gsm_lchan *new_lchan)
return -ENODEV;
}
- LOGP(DHO, LOGL_DEBUG, "HANDOVER DETECT %s -> %s\n",
- gsm_lchan_name(ho->old_lchan), gsm_lchan_name(ho->new_lchan));
+ LOGPHO(ho, LOGL_DEBUG, "Handover RACH detected\n");
/* FIXME: do we actually want to do something here ? */
//rsl_ipacc_mdcx(new_lchan,
diff --git a/src/libbsc/handover_vty.c b/src/libbsc/handover_vty.c
index 4905f35c7..bbbe95378 100644
--- a/src/libbsc/handover_vty.c
+++ b/src/libbsc/handover_vty.c
@@ -31,7 +31,7 @@ static struct handover_cfg *ho_cfg_from_vty(struct vty *vty)
return gsmnet_from_vty(vty)->ho;
case BTS_NODE:
OSMO_ASSERT(vty->index);
- return ((struct gsm_bts *)vty->index)->ho;
+ return ((struct gsm_bts *)vty->index)->ho.cfg;
default:
OSMO_ASSERT(false);
}
diff --git a/src/libcommon/gsm_data.c b/src/libcommon/gsm_data.c
index 92ebbfe0c..17858f6c0 100644
--- a/src/libcommon/gsm_data.c
+++ b/src/libcommon/gsm_data.c
@@ -25,9 +25,11 @@
#include <ctype.h>
#include <stdbool.h>
#include <netinet/in.h>
+#include <time.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/talloc.h>
+#include <osmocom/core/signal.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/gsm/abis_nm.h>
#include <osmocom/core/statistics.h>
@@ -38,6 +40,7 @@
#include <osmocom/bsc/bsc_msc_data.h>
#include <osmocom/bsc/abis_nm.h>
#include <osmocom/bsc/handover_cfg.h>
+#include <osmocom/bsc/signal.h>
void *tall_bsc_ctx;
@@ -266,6 +269,8 @@ struct gsm_bts *gsm_bts_alloc_register(struct gsm_network *net, enum gsm_bts_typ
INIT_LLIST_HEAD(&bts->loc_list);
+ osmo_signal_dispatch(SS_L_GLOBAL, S_GLOBAL_BTS_NEW, bts);
+
return bts;
}
@@ -416,3 +421,73 @@ bool classmark_is_r99(struct gsm_classmark *cm)
rev_lev = (cm->classmark2[0] >> 5) & 0x3;
return rev_lev >= 2;
}
+
+static unsigned int time_now(void)
+{
+ time_t now;
+ time(&now);
+ return (unsigned int)now;
+}
+
+void conn_penalty_timer_add(struct gsm_subscriber_connection *conn,
+ struct gsm_bts *bts, int timeout)
+{
+ struct ho_penalty_timer *timer;
+ unsigned int now;
+ unsigned int then;
+ now = time_now();
+
+ /* no not add timer, if there is no timeout set */
+ if (!timeout)
+ return;
+
+ then = now + timeout;
+
+ /* timer already running for that BTS? */
+ llist_for_each_entry(timer, &conn->ho_penalty_timers, entry) {
+ if (timer->bts_nr != 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_nr = bts->nr;
+ timer->timeout = then;
+
+ llist_add_tail(&timer->entry, &conn->ho_penalty_timers);
+}
+
+unsigned int conn_penalty_timer_remaining(struct gsm_subscriber_connection *conn,
+ struct gsm_bts *bts)
+{
+ struct ho_penalty_timer *timer;
+ unsigned int now = time_now();
+ llist_for_each_entry(timer, &conn->ho_penalty_timers, entry) {
+ if (timer->bts_nr != bts->nr)
+ continue;
+ if (now > timer->timeout)
+ continue;
+ return timer->timeout - now;
+ }
+ return 0;
+}
+
+void conn_penalty_timer_clear(struct gsm_subscriber_connection *conn,
+ struct gsm_bts *bts)
+{
+ struct ho_penalty_timer *timer, *timer2;
+ llist_for_each_entry_safe(timer, timer2, &conn->ho_penalty_timers, entry) {
+ if (bts && timer->bts_nr != bts->nr)
+ continue;
+ llist_del(&timer->entry);
+ talloc_free(timer);
+ }
+}
diff --git a/src/libcommon/gsm_data_shared.c b/src/libcommon/gsm_data_shared.c
index df6cf4ea4..fadcfcc9a 100644
--- a/src/libcommon/gsm_data_shared.c
+++ b/src/libcommon/gsm_data_shared.c
@@ -375,7 +375,7 @@ struct gsm_bts *gsm_bts_alloc(struct gsm_network *net, uint8_t bts_num)
/* si handling */
bts->bcch_change_mark = 1;
- bts->ho = ho_cfg_init(bts, HO_CFG_CTX_BTS, net->ho);
+ bts->ho.cfg = ho_cfg_init(bts, HO_CFG_CTX_BTS, net->ho);
return bts;
}
diff --git a/src/libcommon/handover_cfg.c b/src/libcommon/handover_cfg.c
index 08e94e56f..d6cb8b336 100644
--- a/src/libcommon/handover_cfg.c
+++ b/src/libcommon/handover_cfg.c
@@ -23,10 +23,15 @@
#include <stdbool.h>
#include <talloc.h>
+#include <osmocom/bsc/debug.h>
+
#include <osmocom/bsc/vty.h>
+#include <osmocom/bsc/handover_decision_2.h>
#include <osmocom/bsc/handover_cfg.h>
#include <osmocom/bsc/gsm_data.h>
+ho_cfg_on_change_cb_t ho_cfg_on_change_congestion_check_interval_cb = NULL;
+
struct handover_cfg {
struct handover_cfg *higher_level_cfg;
diff --git a/src/osmo-bsc/osmo_bsc_bssap.c b/src/osmo-bsc/osmo_bsc_bssap.c
index 0ecc11c6b..a86476393 100644
--- a/src/osmo-bsc/osmo_bsc_bssap.c
+++ b/src/osmo-bsc/osmo_bsc_bssap.c
@@ -514,7 +514,6 @@ static int bssmap_handle_assignm_req(struct osmo_bsc_sccp_con *conn,
bool aoip = false;
struct sockaddr_storage rtp_addr;
struct gsm0808_channel_type ct;
- struct gsm0808_speech_codec_list scl;
struct gsm0808_speech_codec_list *scl_ptr = NULL;
int rc;
const uint8_t *data;
@@ -560,6 +559,7 @@ static int bssmap_handle_assignm_req(struct osmo_bsc_sccp_con *conn,
}
/* Decode speech codec list (AoIP) */
+ conn->conn->codec_list_present = false;
if (aoip) {
/* Check for speech codec list element */
if (!TLVP_PRESENT(&tp, GSM0808_IE_SPEECH_CODEC_LIST)) {
@@ -571,13 +571,14 @@ static int bssmap_handle_assignm_req(struct osmo_bsc_sccp_con *conn,
/* Decode Speech Codec list */
data = TLVP_VAL(&tp, GSM0808_IE_SPEECH_CODEC_LIST);
len = TLVP_LEN(&tp, GSM0808_IE_SPEECH_CODEC_LIST);
- rc = gsm0808_dec_speech_codec_list(&scl, data, len);
+ rc = gsm0808_dec_speech_codec_list(&conn->conn->codec_list, data, len);
if (rc < 0) {
LOGP(DMSC, LOGL_ERROR,
"Unable to decode speech codec list\n");
goto reject;
}
- scl_ptr = &scl;
+ conn->conn->codec_list_present = true;
+ scl_ptr = &conn->conn->codec_list;
}
/* Decode Channel Type element */
diff --git a/src/osmo-bsc/osmo_bsc_main.c b/src/osmo-bsc/osmo_bsc_main.c
index 977f90677..081289784 100644
--- a/src/osmo-bsc/osmo_bsc_main.c
+++ b/src/osmo-bsc/osmo_bsc_main.c
@@ -29,6 +29,8 @@
#include <osmocom/bsc/ipaccess.h>
#include <osmocom/bsc/ctrl.h>
#include <osmocom/bsc/osmo_bsc_sigtran.h>
+#include <osmocom/bsc/handover_decision.h>
+#include <osmocom/bsc/handover_decision_2.h>
#include <osmocom/ctrl/control_cmd.h>
#include <osmocom/ctrl/control_if.h>
@@ -294,6 +296,7 @@ int main(int argc, char **argv)
}
handover_decision_1_init();
+ handover_decision_2_init(bsc_gsmnet);
signal(SIGINT, &signal_handler);
signal(SIGTERM, &signal_handler);
diff --git a/tests/Makefile.am b/tests/Makefile.am
index ba8a5e140..652dfe194 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -8,6 +8,7 @@ SUBDIRS = \
bsc-nat \
bsc-nat-trie \
bssap \
+ handover \
$(NULL)
# The `:;' works around a Bash 3.2 bug when the output is not writeable.
diff --git a/tests/handover/Makefile.am b/tests/handover/Makefile.am
new file mode 100644
index 000000000..69fda8a10
--- /dev/null
+++ b/tests/handover/Makefile.am
@@ -0,0 +1,37 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ -ggdb3 \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(NULL)
+
+AM_LDFLAGS = \
+ $(COVERAGE_LDFLAGS) \
+ $(NULL)
+
+EXTRA_DIST = \
+ handover_test.ok \
+ $(NULL)
+
+noinst_PROGRAMS = \
+ handover_test \
+ $(NULL)
+
+handover_test_SOURCES = \
+ handover_test.c \
+ $(NULL)
+
+handover_test_LDADD = \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOABIS_LIBS) \
+ $(top_builddir)/src/libcommon/libcommon.a \
+ $(top_builddir)/src/libbsc/libbsc.a \
+ $(top_builddir)/src/libcommon-cs/libcommon-cs.a \
+ $(NULL)
diff --git a/tests/handover/handover_test.c b/tests/handover/handover_test.c
new file mode 100644
index 000000000..f7b408be6
--- /dev/null
+++ b/tests/handover/handover_test.c
@@ -0,0 +1,1549 @@
+/*
+ * (C) 2013 by Andreas Eversberg <jolly@eversberg.eu>
+ * 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 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 <errno.h>
+
+#include <assert.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/talloc.h>
+
+#include <osmocom/bsc/abis_rsl.h>
+#include <osmocom/bsc/debug.h>
+#include <osmocom/bsc/bsc_subscriber.h>
+#include <osmocom/bsc/chan_alloc.h>
+#include <osmocom/bsc/handover_decision.h>
+#include <osmocom/bsc/system_information.h>
+#include <osmocom/bsc/handover_cfg.h>
+#include <osmocom/bsc/handover_decision_2.h>
+#include <osmocom/bsc/common_bsc.h>
+#include <osmocom/bsc/bss.h>
+#include <osmocom/bsc/bsc_api.h>
+#include <osmocom/bsc/osmo_bsc.h>
+
+struct gsm_network *bsc_gsmnet;
+
+/* measurement report */
+
+uint8_t meas_rep_ba = 0, meas_rep_valid = 1, meas_valid = 1, meas_multi_rep = 0;
+uint8_t meas_dl_rxlev = 0, meas_dl_rxqual = 0;
+uint8_t meas_ul_rxlev = 0, meas_ul_rxqual = 0;
+uint8_t meas_tx_power_ms = 0, meas_tx_power_bs = 0, meas_ta_ms = 0;
+uint8_t meas_dtx_ms = 0, meas_dtx_bs = 0, meas_nr = 0;
+uint8_t meas_num_nc = 0, meas_rxlev_nc[6], meas_bsic_nc[6], meas_bcch_f_nc[6];
+
+static void gen_meas_rep(struct gsm_lchan *lchan)
+{
+ struct msgb *msg = msgb_alloc_headroom(256, 64, "RSL");
+ struct abis_rsl_dchan_hdr *dh;
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+ uint8_t ulm[3], l1i[2], *buf;
+ struct gsm48_hdr *gh;
+ struct gsm48_meas_res *mr;
+
+ dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+ dh->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN;
+ dh->c.msg_type = RSL_MT_MEAS_RES;
+ dh->ie_chan = RSL_IE_CHAN_NR;
+ dh->chan_nr = chan_nr;
+
+ msgb_tv_put(msg, RSL_IE_MEAS_RES_NR, meas_nr++);
+
+ ulm[0] = meas_ul_rxlev | (meas_dtx_bs << 7);
+ ulm[1] = meas_ul_rxlev;
+ ulm[2] = (meas_ul_rxqual << 3) | meas_ul_rxqual;
+ msgb_tlv_put(msg, RSL_IE_UPLINK_MEAS, sizeof(ulm), ulm);
+
+ msgb_tv_put(msg, RSL_IE_BS_POWER, meas_tx_power_bs);
+
+ l1i[0] = 0;
+ l1i[1] = meas_ta_ms;
+ msgb_tv_fixed_put(msg, RSL_IE_L1_INFO, sizeof(l1i), l1i);
+
+ buf = msgb_put(msg, 3);
+ buf[0] = RSL_IE_L3_INFO;
+ buf[1] = (sizeof(*gh) + sizeof(*mr)) >> 8;
+ buf[2] = (sizeof(*gh) + sizeof(*mr)) & 0xff;
+
+ gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+ mr = (struct gsm48_meas_res *) msgb_put(msg, sizeof(*mr));
+
+ gh->proto_discr = GSM48_PDISC_RR;
+ gh->msg_type = GSM48_MT_RR_MEAS_REP;
+
+ /* measurement results */
+ mr->rxlev_full = meas_dl_rxlev;
+ mr->rxlev_sub = meas_dl_rxlev;
+ mr->rxqual_full = meas_dl_rxqual;
+ mr->rxqual_sub = meas_dl_rxqual;
+ mr->dtx_used = meas_dtx_ms;
+ mr->ba_used = meas_rep_ba;
+ mr->meas_valid = !meas_valid; /* 0 = valid */
+ if (meas_rep_valid) {
+ mr->no_nc_n_hi = meas_num_nc >> 2;
+ mr->no_nc_n_lo = meas_num_nc & 3;
+ } else {
+ /* no results for serving cells */
+ mr->no_nc_n_hi = 1;
+ mr->no_nc_n_lo = 3;
+ }
+ mr->rxlev_nc1 = meas_rxlev_nc[0];
+ mr->rxlev_nc2_hi = meas_rxlev_nc[1] >> 1;
+ mr->rxlev_nc2_lo = meas_rxlev_nc[1] & 1;
+ mr->rxlev_nc3_hi = meas_rxlev_nc[2] >> 2;
+ mr->rxlev_nc3_lo = meas_rxlev_nc[2] & 3;
+ mr->rxlev_nc4_hi = meas_rxlev_nc[3] >> 3;
+ mr->rxlev_nc4_lo = meas_rxlev_nc[3] & 7;
+ mr->rxlev_nc5_hi = meas_rxlev_nc[4] >> 4;
+ mr->rxlev_nc5_lo = meas_rxlev_nc[4] & 15;
+ mr->rxlev_nc6_hi = meas_rxlev_nc[5] >> 5;
+ mr->rxlev_nc6_lo = meas_rxlev_nc[5] & 31;
+ mr->bsic_nc1_hi = meas_bsic_nc[0] >> 3;
+ mr->bsic_nc1_lo = meas_bsic_nc[0] & 7;
+ mr->bsic_nc2_hi = meas_bsic_nc[1] >> 4;
+ mr->bsic_nc2_lo = meas_bsic_nc[1] & 15;
+ mr->bsic_nc3_hi = meas_bsic_nc[2] >> 5;
+ mr->bsic_nc3_lo = meas_bsic_nc[2] & 31;
+ mr->bsic_nc4 = meas_bsic_nc[3];
+ mr->bsic_nc5 = meas_bsic_nc[4];
+ mr->bsic_nc6 = meas_bsic_nc[5];
+ mr->bcch_f_nc1 = meas_bcch_f_nc[0];
+ mr->bcch_f_nc2 = meas_bcch_f_nc[1];
+ mr->bcch_f_nc3 = meas_bcch_f_nc[2];
+ mr->bcch_f_nc4 = meas_bcch_f_nc[3];
+ mr->bcch_f_nc5_hi = meas_bcch_f_nc[4] >> 1;
+ mr->bcch_f_nc5_lo = meas_bcch_f_nc[4] & 1;
+ mr->bcch_f_nc6_hi = meas_bcch_f_nc[5] >> 2;
+ mr->bcch_f_nc6_lo = meas_bcch_f_nc[5] & 3;
+
+ msg->dst = lchan->ts->trx->bts->c0->rsl_link;
+ msg->l2h = (unsigned char *)dh;
+ msg->l3h = (unsigned char *)gh;
+
+ abis_rsl_rcvmsg(msg);
+}
+
+static struct gsm_bts *create_bts(int arfcn)
+{
+ struct gsm_bts *bts;
+ struct e1inp_sign_link *rsl_link;
+ int i;
+
+ bts = gsm_bts_alloc_register(bsc_gsmnet, GSM_BTS_TYPE_OSMOBTS, 0x3f);
+ if (!bts) {
+ printf("No resource for bts1\n");
+ return NULL;
+ }
+
+ bts->location_area_code = 23;
+ bts->c0->arfcn = arfcn;
+
+ bts->codec.efr = 1;
+ bts->codec.hr = 1;
+ bts->codec.amr = 1;
+
+ rsl_link = talloc_zero(0, struct e1inp_sign_link);
+ rsl_link->trx = bts->c0;
+ bts->c0->rsl_link = rsl_link;
+
+ bts->c0->mo.nm_state.operational = NM_OPSTATE_ENABLED;
+ bts->c0->mo.nm_state.availability = NM_AVSTATE_OK;
+ bts->c0->bb_transc.mo.nm_state.operational = NM_OPSTATE_ENABLED;
+ bts->c0->bb_transc.mo.nm_state.availability = NM_AVSTATE_OK;
+
+ /* 4 full rate and 4 half rate channels */
+ for (i = 1; i <= 6; i++) {
+ bts->c0->ts[i].pchan =
+ (i < 5) ? GSM_PCHAN_TCH_F : GSM_PCHAN_TCH_H;
+ bts->c0->ts[i].mo.nm_state.operational = NM_OPSTATE_ENABLED;
+ bts->c0->ts[i].mo.nm_state.availability = NM_AVSTATE_OK;
+ bts->c0->ts[i].lchan[0].type = GSM_LCHAN_NONE;
+ bts->c0->ts[i].lchan[0].state = LCHAN_S_NONE;
+ bts->c0->ts[i].lchan[1].type = GSM_LCHAN_NONE;
+ bts->c0->ts[i].lchan[1].state = LCHAN_S_NONE;
+ }
+ return bts;
+}
+
+void create_conn(struct gsm_lchan *lchan)
+{
+ lchan->conn = bsc_subscr_con_allocate(lchan);
+}
+
+/* create lchan */
+struct gsm_lchan *create_lchan(struct gsm_bts *bts, int full_rate, char *codec)
+{
+ struct gsm_lchan *lchan;
+
+ lchan = lchan_alloc(bts,
+ (full_rate) ? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H, 0);
+ if (!lchan) {
+ printf("No resource for lchan\n");
+ exit(EXIT_FAILURE);
+ }
+ lchan->state = LCHAN_S_ACTIVE;
+ create_conn(lchan);
+ if (!strcasecmp(codec, "FR") && full_rate)
+ lchan->tch_mode = GSM48_CMODE_SPEECH_V1;
+ else if (!strcasecmp(codec, "HR") && !full_rate)
+ lchan->tch_mode = GSM48_CMODE_SPEECH_V1;
+ else if (!strcasecmp(codec, "EFR") && full_rate)
+ lchan->tch_mode = GSM48_CMODE_SPEECH_EFR;
+ else if (!strcasecmp(codec, "AMR"))
+ lchan->tch_mode = GSM48_CMODE_SPEECH_AMR;
+ else {
+ printf("Given codec unknown\n");
+ exit(EXIT_FAILURE);
+ }
+
+ lchan->conn->codec_list = (struct gsm0808_speech_codec_list){
+ .codec = {
+ { .fi=true, .type=GSM0808_SCT_FR1, },
+ { .fi=true, .type=GSM0808_SCT_FR2, },
+ { .fi=true, .type=GSM0808_SCT_FR3, },
+ { .fi=true, .type=GSM0808_SCT_HR1, },
+ { .fi=true, .type=GSM0808_SCT_HR3, },
+ },
+ .len = 5,
+ };
+
+ return lchan;
+}
+
+/* parse channel request */
+
+static int got_chan_req = 0;
+static struct gsm_lchan *chan_req_lchan = NULL;
+
+static int parse_chan_act(struct gsm_lchan *lchan, uint8_t *data)
+{
+ chan_req_lchan = lchan;
+ return 0;
+}
+
+static int parse_chan_rel(struct gsm_lchan *lchan, uint8_t *data)
+{
+ chan_req_lchan = lchan;
+ return 0;
+}
+
+/* parse handover request */
+
+static int got_ho_req = 0;
+static struct gsm_lchan *ho_req_lchan = NULL;
+
+static int parse_ho_command(struct gsm_lchan *lchan, uint8_t *data, int len)
+{
+ struct gsm48_hdr *gh = (struct gsm48_hdr *) data;
+ struct gsm48_ho_cmd *ho = (struct gsm48_ho_cmd *) gh->data;
+ int arfcn;
+ struct gsm_bts *neigh;
+
+ switch (gh->msg_type) {
+ case GSM48_MT_RR_HANDO_CMD:
+ arfcn = (ho->cell_desc.arfcn_hi << 8) | ho->cell_desc.arfcn_lo;
+
+ /* look up trx. since every dummy bts uses different arfcn and
+ * only one trx, it is simple */
+ llist_for_each_entry(neigh, &bsc_gsmnet->bts_list, list) {
+ if (neigh->c0->arfcn != arfcn)
+ continue;
+ ho_req_lchan = lchan;
+ return 0;
+ }
+ break;
+ case GSM48_MT_RR_ASS_CMD:
+ ho_req_lchan = lchan;
+ return 0;
+ break;
+ default:
+ fprintf(stderr, "Error, expecting HO or AS command\n");
+ return -EINVAL;
+ }
+
+ return -1;
+}
+
+/* send channel activation ack */
+static void send_chan_act_ack(struct gsm_lchan *lchan, int act)
+{
+ struct msgb *msg = msgb_alloc_headroom(256, 64, "RSL");
+ struct abis_rsl_dchan_hdr *dh;
+
+ dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
+ dh->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN;
+ dh->c.msg_type = (act) ? RSL_MT_CHAN_ACTIV_ACK : RSL_MT_RF_CHAN_REL_ACK;
+ dh->ie_chan = RSL_IE_CHAN_NR;
+ dh->chan_nr = gsm_lchan2chan_nr(lchan);
+
+ msg->dst = lchan->ts->trx->bts->c0->rsl_link;
+ msg->l2h = (unsigned char *)dh;
+
+ abis_rsl_rcvmsg(msg);
+}
+
+/* send handover complete */
+static void send_ho_complete(struct gsm_lchan *lchan, bool success)
+{
+ struct msgb *msg = msgb_alloc_headroom(256, 64, "RSL");
+ struct abis_rsl_rll_hdr *rh;
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+ uint8_t *buf;
+ struct gsm48_hdr *gh;
+ struct gsm48_ho_cpl *hc;
+
+ rh = (struct abis_rsl_rll_hdr *) msgb_put(msg, sizeof(*rh));
+ rh->c.msg_discr = ABIS_RSL_MDISC_RLL;
+ rh->c.msg_type = RSL_MT_DATA_IND;
+ rh->ie_chan = RSL_IE_CHAN_NR;
+ rh->chan_nr = chan_nr;
+ rh->ie_link_id = RSL_IE_LINK_IDENT;
+ rh->link_id = 0x00;
+
+ buf = msgb_put(msg, 3);
+ buf[0] = RSL_IE_L3_INFO;
+ buf[1] = (sizeof(*gh) + sizeof(*hc)) >> 8;
+ buf[2] = (sizeof(*gh) + sizeof(*hc)) & 0xff;
+
+ gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+ hc = (struct gsm48_ho_cpl *) msgb_put(msg, sizeof(*hc));
+
+ gh->proto_discr = GSM48_PDISC_RR;
+ gh->msg_type =
+ success ? GSM48_MT_RR_HANDO_COMPL : GSM48_MT_RR_HANDO_FAIL;
+
+ msg->dst = lchan->ts->trx->bts->c0->rsl_link;
+ msg->l2h = (unsigned char *)rh;
+ msg->l3h = (unsigned char *)gh;
+
+ abis_rsl_rcvmsg(msg);
+}
+
+/* RSL messages from BSC */
+int abis_rsl_sendmsg(struct msgb *msg)
+{
+ struct abis_rsl_dchan_hdr *dh = (struct abis_rsl_dchan_hdr *) msg->data;
+ struct e1inp_sign_link *sign_link = msg->dst;
+ int rc;
+ struct gsm_lchan *lchan = rsl_lchan_lookup(sign_link->trx, dh->chan_nr, &rc);
+
+ if (rc) {
+ printf("rsl_lchan_lookup() failed\n");
+ exit(1);
+ }
+
+ switch (dh->c.msg_type) {
+ case RSL_MT_CHAN_ACTIV:
+ rc = parse_chan_act(lchan, dh->data);
+ if (rc == 0)
+ got_chan_req = 1;
+ break;
+ case RSL_MT_RF_CHAN_REL:
+ rc = parse_chan_rel(lchan, dh->data);
+ if (rc == 0)
+ send_chan_act_ack(chan_req_lchan, 0);
+ break;
+ case RSL_MT_DATA_REQ:
+ rc = parse_ho_command(lchan, msg->l3h, msgb_l3len(msg));
+ if (rc == 0)
+ got_ho_req = 1;
+ break;
+ case RSL_MT_IPAC_CRCX:
+ break;
+ default:
+ printf("unknown rsl message=0x%x\n", dh->c.msg_type);
+ }
+ return 0;
+}
+
+/* test cases */
+
+static char *test_case_0[] = {
+ "2",
+
+ "Stay in better cell\n\n"
+ "There are many neighbor cells, but only the current cell is the best\n"
+ "cell, so no handover is performed\n",
+
+ "create-bts", "7",
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "0", "30","0",
+ "6","0","20","1","21","2","18","3","20","4","23","5","19",
+ "expect-no-chan",
+ NULL
+};
+
+static char *test_case_1[] = {
+ "2",
+
+ "Handover to best better cell\n\n"
+ "The best neighbor cell is selected\n",
+
+ "create-bts", "7",
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "0", "10","0",
+ "6","0","20","1","21","2","18","3","20","4","23","5","19",
+ "expect-chan", "5", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_2[] = {
+ "2",
+
+ "Handover and Assignment must be enabled\n\n"
+ "This test will start with disabled assignment and handover. A\n"
+ "better neighbor cell (assignment enabled) will not be selected and \n"
+ "also no assignment from TCH/H to TCH/F to improve quality. There\n"
+ "will be no handover nor assignment. After enabling assignment on the\n"
+ "current cell, the MS will assign to TCH/F. After enabling handover\n"
+ "in the current cell, but disabling in the neighbor cell, handover\n"
+ "will not performed until it is enabled in the neighbor cell too.\n",
+
+ "create-bts", "2",
+ "afs-rxlev-improve", "0", "5",
+ "create-ms", "0", "TCH/H", "AMR",
+ "as-enable", "0", "0",
+ "ho-enable", "0", "0",
+ "meas-rep", "0", "0","0", "1","0","30",
+ "expect-no-chan",
+ "as-enable", "0", "1",
+ "meas-rep", "0", "0","0", "1","0","30",
+ "expect-chan", "0", "1",
+ "ack-chan",
+ "expect-ho", "0", "5",
+ "ho-complete",
+ "ho-enable", "0", "1",
+ "ho-enable", "1", "0",
+ "meas-rep", "0", "0","0", "1","0","30",
+ "expect-no-chan",
+ "ho-enable", "1", "1",
+ "meas-rep", "0", "0","0", "1","0","30",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_3[] = {
+ "2",
+
+ "Penalty timer must not run\n\n"
+ "The MS will try to handover to a better cell, but this will fail.\n"
+ "Even though the cell is still better, handover will not be performed\n"
+ "due to penalty timer after handover failure\n",
+
+ "create-bts", "2",
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-failed",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-no-chan",
+ NULL
+};
+
+static char *test_case_4[] = {
+ "2",
+
+ "TCH/H keeping with HR codec\n\n"
+ "The MS is using half rate V1 codec, but the better cell is congested\n"
+ "at TCH/H slots. As the congestion is removed, the handover takes\n"
+ "place.\n",
+
+ "create-bts", "2",
+ "set-min-free", "1", "TCH/H", "4",
+ "create-ms", "0", "TCH/H", "HR",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-no-chan",
+ "set-min-free", "1", "TCH/H", "3",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-chan", "1", "5",
+ "ack-chan",
+ "expect-ho", "0", "5",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_5[] = {
+ "2",
+
+ "TCH/F keeping with FR codec\n\n"
+ "The MS is using full rate V1 codec, but the better cell is congested\n"
+ "at TCH/F slots. As the congestion is removed, the handover takes\n"
+ "place.\n",
+
+ "create-bts", "2",
+ "set-min-free", "1", "TCH/F", "4",
+ "create-ms", "0", "TCH/F", "FR",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-no-chan",
+ "set-min-free", "1", "TCH/F", "3",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_6[] = {
+ "2",
+
+ "TCH/F keeping with EFR codec\n\n"
+ "The MS is using full rate V2 codec, but the better cell is congested\n"
+ "at TCH/F slots. As the congestion is removed, the handover takes\n"
+ "place.\n",
+
+ "create-bts", "2",
+ "set-min-free", "1", "TCH/F", "4",
+ "create-ms", "0", "TCH/F", "EFR",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-no-chan",
+ "set-min-free", "1", "TCH/F", "3",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_7[] = {
+ "2",
+
+ "TCH/F to TCH/H changing with AMR codec\n\n"
+ "The MS is using AMR V3 codec, the better cell is congested at TCH/F\n"
+ "slots. The handover is performed to non-congested TCH/H slots.\n",
+
+ "create-bts", "2",
+ "set-min-free", "1", "TCH/F", "4",
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-chan", "1", "5",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_8[] = {
+ "2",
+
+ "No handover to a cell with no slots available\n\n"
+ "If no slot is available, no handover is performed\n",
+
+ "create-bts", "2",
+ "create-ms", "0", "TCH/F", "AMR",
+ "create-ms", "1", "TCH/F", "AMR",
+ "create-ms", "1", "TCH/F", "AMR",
+ "create-ms", "1", "TCH/F", "AMR",
+ "create-ms", "1", "TCH/F", "AMR",
+ "create-ms", "1", "TCH/H", "AMR",
+ "create-ms", "1", "TCH/H", "AMR",
+ "create-ms", "1", "TCH/H", "AMR",
+ "create-ms", "1", "TCH/H", "AMR",
+ "meas-rep", "0", "0","0", "1","0","30",
+ "expect-no-chan",
+ NULL
+};
+
+static char *test_case_9[] = {
+ "2",
+
+ "No more parallel handovers, if max_unsync_ho is defined\n\n"
+ "There are tree mobiles that want to handover, but only two can do\n"
+ "it at a time, because the maximum number is limited to two.\n",
+
+ "create-bts", "2",
+ "set-max-ho", "1", "2",
+ "create-ms", "0", "TCH/F", "AMR",
+ "create-ms", "0", "TCH/F", "AMR",
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "0", "0","0", "1","0","30",
+ "expect-chan", "1", "1",
+ "meas-rep", "1", "0","0", "1","0","30",
+ "expect-chan", "1", "2",
+ "meas-rep", "2", "0","0", "1","0","30",
+ "expect-no-chan",
+ NULL
+};
+
+static char *test_case_10[] = {
+ "2",
+
+ "Hysteresis\n\n"
+ "If neighbor cell is better, handover is only performed if the\n"
+ "ammount of improvement is greater or equal hyteresis\n",
+
+ "create-bts", "2",
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "0", "27","0", "1","0","30",
+ "expect-no-chan",
+ "meas-rep", "0", "26","0", "1","0","30",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_11[] = {
+ "2",
+
+ "No Hysteresis and minimum RX level\n\n"
+ "If current cell's RX level is below mimium level, handover must be\n"
+ "performed, no matter of the hysteresis. First do not perform\n"
+ "handover to better neighbor cell, because the hysteresis is not\n"
+ "met. Second do not perform handover because better neighbor cell is\n"
+ "below minimum RX level. Third perform handover because current cell\n"
+ "is below minimum RX level, even if the better neighbor cell (minimum\n"
+ "RX level reached) does not meet the hysteresis.\n",
+
+ "create-bts", "2",
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "0", "10","0", "1","0","11",
+ "expect-no-chan",
+ "meas-rep", "0", "8","0", "1","0","9",
+ "expect-no-chan",
+ "meas-rep", "0", "9","0", "1","0","10",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_12[] = {
+ "2",
+
+ "No handover to congested cell\n\n"
+ "The better neighbor cell is congested, so no handover is performed.\n"
+ "After the congestion is over, handover will be performed.\n",
+
+ "create-bts", "2",
+ "create-ms", "0", "TCH/F", "AMR",
+ "set-min-free", "1", "TCH/F", "4",
+ "set-min-free", "1", "TCH/H", "4",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-no-chan",
+ "set-min-free", "1", "TCH/F", "3",
+ "set-min-free", "1", "TCH/H", "3",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_13[] = {
+ "2",
+
+ "Handover to balance congestion\n\n"
+ "The current and the better cell are congested, so no handover is\n"
+ "performed. This is because handover would congest the neighbor cell\n"
+ "more. After congestion raises in the current cell, the handover is\n"
+ "performed to balance congestion\n",
+
+ "create-bts", "2",
+ "create-ms", "0", "TCH/F", "AMR",
+ "set-min-free", "0", "TCH/F", "4",
+ "set-min-free", "0", "TCH/H", "4",
+ "set-min-free", "1", "TCH/F", "4",
+ "set-min-free", "1", "TCH/H", "4",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-no-chan",
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_14[] = {
+ "2",
+
+ "Handover to congested cell, if RX level is below minimum\n\n"
+ "The better neighbor cell is congested, so no handover is performed.\n"
+ "If the RX level of the current cell drops below minimum acceptable\n"
+ "level, the handover is performed.\n",
+
+ "create-bts", "2",
+ "create-ms", "0", "TCH/F", "AMR",
+ "set-min-free", "1", "TCH/F", "4",
+ "set-min-free", "1", "TCH/H", "4",
+ "meas-rep", "0", "10","0", "1","0","30",
+ "expect-no-chan",
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "0", "9","0", "1","0","30",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_15[] = {
+ "2",
+
+ "Handover to congested cell, if RX quality is below minimum\n\n"
+ "The better neighbor cell is congested, so no handover is performed.\n"
+ "If the RX quality of the current cell drops below minimum acceptable\n"
+ "level, the handover is performed. It is also required that 10\n"
+ "resports are received, before RX quality is checked.\n",
+
+ "create-bts", "2",
+ "create-ms", "0", "TCH/F", "AMR",
+ "set-min-free", "1", "TCH/F", "4",
+ "set-min-free", "1", "TCH/H", "4",
+ "meas-rep", "0", "40","5", "1","0","30",
+ "expect-no-chan",
+ "meas-rep", "0", "40","5", "1","0","30",
+ "expect-no-chan",
+ "meas-rep", "0", "40","5", "1","0","30",
+ "expect-no-chan",
+ "meas-rep", "0", "40","5", "1","0","30",
+ "expect-no-chan",
+ "meas-rep", "0", "40","5", "1","0","30",
+ "expect-no-chan",
+ "meas-rep", "0", "40","5", "1","0","30",
+ "expect-no-chan",
+ "meas-rep", "0", "40","5", "1","0","30",
+ "expect-no-chan",
+ "meas-rep", "0", "40","5", "1","0","30",
+ "expect-no-chan",
+ "meas-rep", "0", "40","5", "1","0","30",
+ "expect-no-chan",
+ "meas-rep", "0", "40","5", "1","0","30",
+ "expect-no-chan",
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "0", "40","6", "1","0","30",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_16[] = {
+ "2",
+
+ "Handover due to maximum TA exceeded\n\n"
+ "The MS in the current (best) cell has reached maximum allowed timing\n"
+ "advance. No handover is performed until the timing advance exceeds\n"
+ "it. The originating cell is still the best, but no handover is\n"
+ "performed back to that cell, because the penalty timer (due to\n"
+ "maximum allowed timing advance) is running.\n",
+
+ "create-bts", "2",
+ "create-ms", "0", "TCH/F", "AMR",
+ "set-max-ta", "0", "5", /* of cell */
+ "set-ta", "0", "5", /* of ms */
+ "meas-rep", "0", "30","0", "1","0","20",
+ "expect-no-chan",
+ "set-ta", "0", "6", /* of ms */
+ "meas-rep", "0", "30","0", "1","0","20",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ "meas-rep", "0", "20","0", "1","0","30",
+ "expect-no-chan",
+ NULL
+};
+
+static char *test_case_17[] = {
+ "2",
+
+ "Congestion check: No congestion\n\n"
+ "Three cells have different number of used slots, but there is no\n"
+ "congestion in any of these cells. No handover is performed.\n",
+
+ "create-bts", "3",
+ "set-min-free", "0", "TCH/F", "2",
+ "set-min-free", "0", "TCH/H", "2",
+ "set-min-free", "1", "TCH/F", "2",
+ "set-min-free", "1", "TCH/H", "2",
+ "set-min-free", "2", "TCH/F", "2",
+ "set-min-free", "2", "TCH/H", "2",
+ "create-ms", "0", "TCH/F", "AMR",
+ "create-ms", "0", "TCH/F", "AMR",
+ "create-ms", "0", "TCH/H", "AMR",
+ "create-ms", "0", "TCH/H", "AMR",
+ "create-ms", "1", "TCH/F", "AMR",
+ "create-ms", "1", "TCH/H", "AMR",
+ "meas-rep", "0", "30","0", "2","0","20","1","20",
+ "expect-no-chan",
+ "meas-rep", "1", "30","0", "2","0","20","1","20",
+ "expect-no-chan",
+ "meas-rep", "2", "30","0", "2","0","20","1","20",
+ "expect-no-chan",
+ "meas-rep", "3", "30","0", "2","0","20","1","20",
+ "expect-no-chan",
+ "meas-rep", "4", "30","0", "2","0","20","1","20",
+ "expect-no-chan",
+ "meas-rep", "5", "30","0", "2","0","20","1","20",
+ "expect-no-chan",
+ "congestion-check",
+ "expect-no-chan",
+ NULL
+};
+
+static char *test_case_18[] = {
+ "2",
+
+ "Congestion check: One out of three cells is congested\n\n"
+ "Three cells have different number of used slots, but there is\n"
+ "congestion at TCH/F in the first cell. Handover is performed with\n"
+ "the best candidate.\n",
+
+ "create-bts", "3",
+ "set-min-free", "0", "TCH/F", "2",
+ "set-min-free", "0", "TCH/H", "2",
+ "set-min-free", "1", "TCH/F", "2",
+ "set-min-free", "1", "TCH/H", "2",
+ "set-min-free", "2", "TCH/F", "2",
+ "set-min-free", "2", "TCH/H", "2",
+ "create-ms", "0", "TCH/F", "AMR",
+ "create-ms", "0", "TCH/F", "AMR",
+ "create-ms", "0", "TCH/F", "AMR",
+ "create-ms", "0", "TCH/H", "AMR",
+ "create-ms", "0", "TCH/H", "AMR",
+ "create-ms", "1", "TCH/F", "AMR",
+ "create-ms", "1", "TCH/H", "AMR",
+ "meas-rep", "0", "30","0", "2","0","20","1","20",
+ "expect-no-chan",
+ "meas-rep", "1", "30","0", "2","0","20","1","20",
+ "expect-no-chan",
+ "meas-rep", "2", "30","0", "2","0","21","1","20",
+ "expect-no-chan",
+ "meas-rep", "3", "30","0", "2","0","20","1","20",
+ "expect-no-chan",
+ "meas-rep", "4", "30","0", "2","0","20","1","20",
+ "expect-no-chan",
+ "meas-rep", "5", "30","0", "2","0","20","1","20",
+ "expect-no-chan",
+ "meas-rep", "6", "30","0", "2","0","20","1","20",
+ "expect-no-chan",
+ "congestion-check",
+ "expect-chan", "1", "2",
+ "ack-chan",
+ "expect-ho", "0", "3", /* best candidate is MS 2 at BTS 1, TS 3 */
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_19[] = {
+ "2",
+
+ "Congestion check: Balancing over congested cells\n\n"
+ "Two cells are congested, but the second cell is more congested.\n"
+ "Handover is performed to solve the congestion.\n",
+
+ "create-bts", "2",
+ "set-min-free", "0", "TCH/F", "4",
+ "set-min-free", "1", "TCH/F", "4",
+ "create-ms", "0", "TCH/F", "FR",
+ "create-ms", "0", "TCH/F", "FR",
+ "create-ms", "0", "TCH/F", "FR",
+ "create-ms", "1", "TCH/F", "FR",
+ "meas-rep", "0", "30","0", "1","0","20",
+ "expect-no-chan",
+ "meas-rep", "1", "30","0", "1","0","21",
+ "expect-no-chan",
+ "meas-rep", "2", "30","0", "1","0","20",
+ "expect-no-chan",
+ "meas-rep", "3", "30","0", "1","0","20",
+ "expect-no-chan",
+ "congestion-check",
+ "expect-chan", "1", "2",
+ "ack-chan",
+ "expect-ho", "0", "2", /* best candidate is MS 1 at BTS 0, TS 2 */
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_20[] = {
+ "2",
+
+ "Congestion check: Solving congestion by handover TCH/F -> TCH/H\n\n"
+ "Two BTS, one MS in the first congested BTS must handover to\n"
+ "non-congested TCH/H of second BTS, in order to solve congestion\n",
+ "create-bts", "2",
+ "set-min-free", "0", "TCH/F", "4",
+ "set-min-free", "0", "TCH/H", "4",
+ "set-min-free", "1", "TCH/F", "4",
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "0", "30","0", "1","0","30",
+ "expect-no-chan",
+ "congestion-check",
+ "expect-chan", "1", "5",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_21[] = {
+ "2",
+
+ "Congestion check: Balancing congestion by handover TCH/F -> TCH/H\n\n"
+ "Two BTS, one MS in the first congested BTS must handover to\n"
+ "less-congested TCH/H of second BTS, in order to balance congestion\n",
+ "create-bts", "2",
+ "set-min-free", "0", "TCH/F", "4",
+ "set-min-free", "0", "TCH/H", "4",
+ "set-min-free", "1", "TCH/F", "4",
+ "set-min-free", "1", "TCH/H", "4",
+ "create-ms", "0", "TCH/F", "AMR",
+ "create-ms", "0", "TCH/F", "AMR",
+ "create-ms", "0", "TCH/H", "AMR",
+ "meas-rep", "0", "30","0", "1","0","30",
+ "expect-no-chan",
+ "congestion-check",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_22[] = {
+ "2",
+
+ "Congestion check: Upgrading worst candidate from TCH/H -> TCH/F\n\n"
+ "There is only one BTS. The TCH/H slots are congested. Since\n"
+ "assignment is performed to less-congested TCH/F, the candidate with\n"
+ "the worst RX level is chosen.\n",
+
+ "create-bts", "1",
+ "set-min-free", "0", "TCH/F", "4",
+ "set-min-free", "0", "TCH/H", "4",
+ "create-ms", "0", "TCH/H", "AMR",
+ "create-ms", "0", "TCH/H", "AMR",
+ "create-ms", "0", "TCH/H", "AMR",
+ "meas-rep", "0", "30","0", "0",
+ "meas-rep", "1", "34","0", "0",
+ "meas-rep", "2", "20","0", "0",
+ "expect-no-chan",
+ "congestion-check",
+ "expect-chan", "0", "1",
+ "ack-chan",
+ "expect-ho", "0", "6",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_23[] = {
+ "2",
+
+ "Story: 'A neighbor is your friend'\n",
+
+ "create-bts", "3",
+
+ "print",
+ "Andreas is driving along the coast, on a sunny june afternoon.\n"
+ "Suddenly he is getting a call from his friend and neighbor Axel.\n"
+ "\n"
+ "What happens: Two MS are created, #0 for Axel, #1 for Andreas.",
+ /* Axel */
+ "create-ms", "2", "TCH/F", "AMR",
+ /* andreas */
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "1", "40","0", "1","0","30",
+ "expect-no-chan",
+
+ "print",
+ "Axel asks Andreas if he would like to join them for a barbecue.\n"
+ "Axel's house is right in the neighborhood and the weather is fine.\n"
+ "Andreas agrees, so he drives to a close store to buy some barbecue\n"
+ "skewers.\n"
+ "\n"
+ "What happens: While driving, a different cell (mounted atop the\n"
+ "store) becomes better.",
+ /* drive to bts 1 */
+ "meas-rep", "1", "20","0", "1","0","35",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+
+ "print",
+ "While Andreas is walking into the store, Axel asks, if he could also\n"
+ "bring some beer. Andreas has problems understanding him: \"I have a\n"
+ "bad reception here. The cell tower is right atop the store, but poor\n"
+ "coverage inside. Can you repeat please?\"\n"
+ "\n"
+ "What happens: Inside the store the close cell is so bad, that\n"
+ "handover back to the previous cell is required.",
+ /* bts 1 becomes bad, so bts 0 helps out */
+ "meas-rep", "1", "5","0", "1","0","20",
+ "expect-chan", "0", "1",
+ "ack-chan",
+ "expect-ho", "1", "1",
+ "ho-complete",
+
+ "print",
+ "After Andreas bought skewers and beer, he leaves the store.\n"
+ "\n"
+ "What happens: Outside the store the close cell is better again, so\n"
+ "handover back to the that cell is performed.",
+ /* bts 1 becomes better again */
+ "meas-rep", "1", "20","0", "1","0","35",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+
+ "print",
+ /* bts 2 becomes better */
+ "Andreas drives down to the lake where Axel's house is.\n"
+ "\n"
+ "What happens: There is a small cell at Axel's house, which becomes\n"
+ "better, because the current cell has no good comverage at the lake.",
+ "meas-rep", "1", "14","0", "2","0","2","1","63",
+ "expect-chan", "2", "2",
+ "ack-chan",
+ "expect-ho", "1", "1",
+ "ho-complete",
+
+ "print",
+ "Andreas wonders why he still has good radio coverage: \"Last time it\n"
+ "was so bad\". Axel sais: \"I installed a pico cell in my house,\n"
+ "now we can use our mobile phones down here at the lake.\"",
+
+ NULL
+};
+
+static char *test_case_24[] = {
+ "2",
+ "No (or not enough) measurements for handover\n\n"
+ "Do not solve congestion in cell, because there is no measurement\n"
+ "As soon as enough measurments available (1 in our case), perform\n"
+ "handover. Afterwards the old cell becomes congested and the new\n"
+ "cell is not. Do not perform handover until new measurements are\n"
+ "received.\n",
+
+ /* two cells, first in congested, but no handover */
+ "create-bts", "2",
+ "set-min-free", "0", "TCH/F", "4",
+ "set-min-free", "0", "TCH/H", "4",
+ "create-ms", "0", "TCH/F", "AMR",
+ "congestion-check",
+ "expect-no-chan",
+
+ /* send measurement and trigger congestion check */
+ "meas-rep", "0", "20","0", "1","0","20",
+ "expect-no-chan",
+ "congestion-check",
+ "expect-chan", "1", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+
+ /* congest the first cell and remove congestion from second cell */
+ "set-min-free", "0", "TCH/F", "0",
+ "set-min-free", "0", "TCH/H", "0",
+ "set-min-free", "1", "TCH/F", "4",
+ "set-min-free", "1", "TCH/H", "4",
+
+ /* no handover until measurements applied */
+ "congestion-check",
+ "expect-no-chan",
+ "meas-rep", "0", "20","0", "1","0","20",
+ "expect-no-chan",
+ "congestion-check",
+ "expect-chan", "0", "1",
+ "ack-chan",
+ "expect-ho", "1", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_25[] = {
+ "1",
+
+ "Stay in better cell\n\n"
+ "There are many neighbor cells, but only the current cell is the best\n"
+ "cell, so no handover is performed\n",
+
+ "create-bts", "7",
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "0", "30","0",
+ "6","0","20","1","21","2","18","3","20","4","23","5","19",
+ "expect-no-chan",
+ NULL
+};
+
+static char *test_case_26[] = {
+ "1",
+
+ "Handover to best better cell\n\n"
+ "The best neighbor cell is selected\n",
+
+ "create-bts", "7",
+ "create-ms", "0", "TCH/F", "AMR",
+ "meas-rep", "0", "10","0",
+ "6","0","20","1","21","2","18","3","20","4","23","5","19",
+ "expect-chan", "5", "1",
+ "ack-chan",
+ "expect-ho", "0", "1",
+ "ho-complete",
+ NULL
+};
+
+static char *test_case_27[] = {
+ "2",
+
+ "Congestion check: Upgrading worst candidate from TCH/H -> TCH/F\n\n"
+ "There is only one BTS. The TCH/H slots are congested. Since\n"
+ "assignment is performed to less-congested TCH/F, the candidate with\n"
+ "the worst RX level is chosen. (So far like test 22.)\n"
+ "After that, trigger more congestion checks to ensure stability.\n",
+
+ "create-bts", "1",
+ "set-min-free", "0", "TCH/F", "2",
+ "set-min-free", "0", "TCH/H", "4",
+ "create-ms", "0", "TCH/H", "AMR",
+ "create-ms", "0", "TCH/H", "AMR",
+ "create-ms", "0", "TCH/H", "AMR",
+ "meas-rep", "0", "30","0", "0",
+ "meas-rep", "1", "34","0", "0",
+ "meas-rep", "2", "20","0", "0",
+ "expect-no-chan",
+ "congestion-check",
+ "expect-chan", "0", "1",
+ "ack-chan",
+ "expect-ho", "0", "6",
+ "ho-complete",
+ "congestion-check",
+ "expect-chan", "0", "2",
+ "ack-chan",
+ "expect-ho", "0", "5",
+ "ho-complete",
+ "congestion-check",
+ "expect-no-chan",
+ "congestion-check",
+ "expect-no-chan",
+ NULL
+};
+
+
+static char **test_cases[] = {
+ test_case_0,
+ test_case_1,
+ test_case_2,
+ test_case_3,
+ test_case_4,
+ test_case_5,
+ test_case_6,
+ test_case_7,
+ test_case_8,
+ test_case_9,
+ test_case_10,
+ test_case_11,
+ test_case_12,
+ test_case_13,
+ test_case_14,
+ test_case_15,
+ test_case_16,
+ test_case_17,
+ test_case_18,
+ test_case_19,
+ test_case_20,
+ test_case_21,
+ test_case_22,
+ test_case_23,
+ test_case_24,
+ test_case_25,
+ test_case_26,
+ test_case_27,
+ NULL
+};
+
+void congestion_check_all_bts()
+{
+ struct gsm_bts *bts;
+ llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
+ handover_decision_2_bts_congestion_check(bts);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ char **test_case;
+ struct gsm_bts *bts[256];
+ int bts_num = 0;
+ struct gsm_lchan *lchan[256];
+ int lchan_num = 0;
+ int test_count = 0;
+ int i;
+ int algorithm;
+ struct bsc_api bsc_api = {};
+
+ for (i = 0; test_cases[i]; i++)
+ test_count++;
+
+ if (argc <= 1 || atoi(argv[1]) >= test_count) {
+ for (i = 0; test_cases[i]; i++) {
+ printf("Test #%d (algorithm %s):\n%s\n", i,
+ test_cases[i][0], test_cases[i][1]);
+ }
+ printf("\nPlease specify test case number 0..%d\n",
+ test_count - 1);
+ return EXIT_FAILURE;
+ }
+
+ osmo_init_logging(&log_info);
+
+ log_set_print_category(osmo_stderr_target, 1);
+
+ log_set_category_filter(osmo_stderr_target, DHO, 1, LOGL_DEBUG);
+ log_set_category_filter(osmo_stderr_target, DHODEC, 1, LOGL_DEBUG);
+ log_set_category_filter(osmo_stderr_target, DMEAS, 1, LOGL_DEBUG);
+ log_set_category_filter(osmo_stderr_target, DREF, 1, LOGL_DEBUG);
+
+ /* Create a dummy network */
+ bsc_gsmnet = bsc_network_init(NULL, 1, 1);
+ if (!bsc_gsmnet)
+ exit(1);
+
+ bsc_api_init(bsc_gsmnet, &bsc_api);
+
+ ho_set_algorithm(bsc_gsmnet->ho, 2);
+ ho_set_ho_active(bsc_gsmnet->ho, true);
+ ho_set_as_active(bsc_gsmnet->ho, true);
+ ho_set_min_rxlev(bsc_gsmnet->ho, -100);
+ ho_set_rxlev_avg_win(bsc_gsmnet->ho, 1);
+ ho_set_rxlev_neigh_avg_win(bsc_gsmnet->ho, 1);
+ ho_set_rxqual_avg_win(bsc_gsmnet->ho, 1);
+ ho_set_pwr_hysteresis(bsc_gsmnet->ho, 3);
+ ho_set_pwr_interval(bsc_gsmnet->ho, 1);
+ ho_set_afs_bias_rxlev(bsc_gsmnet->ho, 0);
+ ho_set_min_rxqual(bsc_gsmnet->ho, 5);
+ ho_set_rxqual_avg_win(bsc_gsmnet->ho, 1);
+ ho_set_afs_bias_rxqual(bsc_gsmnet->ho, 0);
+ ho_set_max_distance(bsc_gsmnet->ho, 9999);
+ ho_set_ho_max(bsc_gsmnet->ho, 9999);
+ ho_set_penalty_max_dist(bsc_gsmnet->ho, 300);
+ ho_set_penalty_failed_ho(bsc_gsmnet->ho, 60);
+ ho_set_penalty_failed_as(bsc_gsmnet->ho, 60);
+
+ bts_model_sysmobts_init();
+
+ test_case = test_cases[atoi(argv[1])];
+
+ fprintf(stderr, "--------------------\n");
+ fprintf(stderr, "Performing the following test %d (algorithm %s):\n%s",
+ atoi(argv[1]), test_case[0], test_case[1]);
+ algorithm = atoi(test_case[0]);
+ test_case += 2;
+ fprintf(stderr, "--------------------\n");
+
+ handover_decision_1_init();
+ handover_decision_2_init(bsc_gsmnet);
+
+ while (*test_case) {
+ if (!strcmp(*test_case, "create-bts")) {
+ static int arfcn = 870;
+ int n = atoi(test_case[1]);
+ fprintf(stderr, "- Creating %d BTS (one TRX each, "
+ "TS(1-4) are TCH/F, TS(5-6) are TCH/H)\n", n);
+ for (i = 0; i < n; i++)
+ bts[bts_num + i] = create_bts(arfcn++);
+ for (i = 0; i < n; i++)
+ gsm_generate_si(bts[bts_num + i],
+ SYSINFO_TYPE_2);
+ bts_num += n;
+ test_case += 2;
+ } else
+ if (!strcmp(*test_case, "as-enable")) {
+ fprintf(stderr, "- Set assignment enable state at "
+ "BTS %s to %s\n", test_case[1], test_case[2]);
+ ho_set_as_active(bts[atoi(test_case[1])]->ho.cfg, atoi(test_case[2]));
+ test_case += 3;
+ } else
+ if (!strcmp(*test_case, "ho-enable")) {
+ fprintf(stderr, "- Set handover enable state at "
+ "BTS %s to %s\n", test_case[1], test_case[2]);
+ ho_set_ho_active(bts[atoi(test_case[1])]->ho.cfg, atoi(test_case[2]));
+ test_case += 3;
+ } else
+ if (!strcmp(*test_case, "afs-rxlev-improve")) {
+ fprintf(stderr, "- Set afs RX level improvement at "
+ "BTS %s to %s\n", test_case[1], test_case[2]);
+ ho_set_afs_bias_rxlev(bts[atoi(test_case[1])]->ho.cfg, atoi(test_case[2]));
+ test_case += 3;
+ } else
+ if (!strcmp(*test_case, "afs-rxqual-improve")) {
+ fprintf(stderr, "- Set afs RX quality improvement at "
+ "BTS %s to %s\n", test_case[1], test_case[2]);
+ ho_set_afs_bias_rxqual(bts[atoi(test_case[1])]->ho.cfg, atoi(test_case[2]));
+ test_case += 3;
+ } else
+ if (!strcmp(*test_case, "set-min-free")) {
+ fprintf(stderr, "- Setting minimum required free %s "
+ "slots at BTS %s to %s\n", test_case[2],
+ test_case[1], test_case[3]);
+ if (!strcmp(test_case[2], "TCH/F"))
+ ho_set_tchf_min_slots(bts[atoi(test_case[1])]->ho.cfg, atoi(test_case[3]));
+ else
+ ho_set_tchh_min_slots(bts[atoi(test_case[1])]->ho.cfg, atoi(test_case[3]));
+ test_case += 4;
+ } else
+ if (!strcmp(*test_case, "set-max-ho")) {
+ fprintf(stderr, "- Setting maximum parallel handovers "
+ "at BTS %s to %s\n", test_case[1],
+ test_case[2]);
+ ho_set_ho_max( bts[atoi(test_case[1])]->ho.cfg, atoi(test_case[2]));
+ test_case += 3;
+ } else
+ if (!strcmp(*test_case, "set-max-ta")) {
+ fprintf(stderr, "- Setting maximum timing advance "
+ "at BTS %s to %s\n", test_case[1],
+ test_case[2]);
+ ho_set_max_distance(bts[atoi(test_case[1])]->ho.cfg, atoi(test_case[2]));
+ test_case += 3;
+ } else
+ if (!strcmp(*test_case, "create-ms")) {
+ fprintf(stderr, "- Creating mobile #%d at BTS %s on "
+ "%s with %s codec\n", lchan_num, test_case[1],
+ test_case[2], test_case[3]);
+ lchan[lchan_num] = create_lchan(bts[atoi(test_case[1])],
+ !strcmp(test_case[2], "TCH/F"), test_case[3]);
+ if (!lchan[lchan_num]) {
+ printf("Failed to create lchan!\n");
+ return EXIT_FAILURE;
+ }
+ fprintf(stderr, " * New MS is at BTS %d TS %d\n",
+ lchan[lchan_num]->ts->trx->bts->nr,
+ lchan[lchan_num]->ts->nr);
+ lchan_num++;
+ test_case += 4;
+ } else
+ if (!strcmp(*test_case, "set-ta")) {
+ fprintf(stderr, "- Setting maximum timing advance "
+ "at MS %s to %s\n", test_case[1],
+ test_case[2]);
+ meas_ta_ms = atoi(test_case[2]);
+ test_case += 3;
+ } else
+ if (!strcmp(*test_case, "meas-rep")) {
+ int n = atoi(test_case[4]);
+ struct gsm_lchan *lc = lchan[atoi(test_case[1])];
+ fprintf(stderr, "- Sending measurement report from "
+ "mobile #%s (rxlev=%s, rxqual=%s)\n",
+ test_case[1], test_case[2], test_case[3]);
+ meas_dl_rxlev = atoi(test_case[2]);
+ meas_dl_rxqual = atoi(test_case[3]);
+ meas_num_nc = n;
+ test_case += 5;
+ for (i = 0; i < n; i++) {
+ int nr = atoi(test_case[0]);
+ /* since our bts is not in the list of neighbor
+ * cells, we need to shift */
+ if (nr >= lc->ts->trx->bts->nr)
+ nr++;
+ fprintf(stderr, " * Neighbor cell #%s, actual "
+ "BTS %d (rxlev=%s)\n", test_case[0], nr,
+ test_case[1]);
+ meas_bcch_f_nc[i] = atoi(test_case[0]);
+ /* bts number, not counting our own */
+ meas_rxlev_nc[i] = atoi(test_case[1]);
+ meas_bsic_nc[i] = 0x3f;
+ test_case += 2;
+ }
+ got_chan_req = 0;
+ gen_meas_rep(lc);
+ } else
+ if (!strcmp(*test_case, "congestion-check")) {
+ fprintf(stderr, "- Triggering congestion check\n");
+ got_chan_req = 0;
+ if (algorithm == 2)
+ congestion_check_all_bts();
+ test_case += 1;
+ } else
+ if (!strcmp(*test_case, "expect-chan")) {
+ fprintf(stderr, "- Expecting channel request at BTS %s "
+ "TS %s\n", test_case[1], test_case[2]);
+ if (!got_chan_req) {
+ printf("Test failed, because no channel was "
+ "requested\n");
+ return EXIT_FAILURE;
+ }
+ fprintf(stderr, " * Got channel request at BTS %d "
+ "TS %d\n", chan_req_lchan->ts->trx->bts->nr,
+ chan_req_lchan->ts->nr);
+ if (chan_req_lchan->ts->trx->bts->nr
+ != atoi(test_case[1])) {
+ printf("Test failed, because channel was not "
+ "requested on expected BTS\n");
+ return EXIT_FAILURE;
+ }
+ if (chan_req_lchan->ts->nr != atoi(test_case[2])) {
+ printf("Test failed, because channel was not "
+ "requested on expected TS\n");
+ return EXIT_FAILURE;
+ }
+ test_case += 3;
+ } else
+ if (!strcmp(*test_case, "expect-no-chan")) {
+ fprintf(stderr, "- Expecting no channel request\n");
+ if (got_chan_req) {
+ fprintf(stderr, " * Got channel request at "
+ "BTS %d TS %d\n",
+ chan_req_lchan->ts->trx->bts->nr,
+ chan_req_lchan->ts->nr);
+ printf("Test failed, because channel was "
+ "requested\n");
+ return EXIT_FAILURE;
+ }
+ fprintf(stderr, " * Got no channel request\n");
+ test_case += 1;
+ } else
+ if (!strcmp(*test_case, "expect-ho")) {
+ fprintf(stderr, "- Expecting handover/assignment "
+ "request at BTS %s TS %s\n", test_case[1],
+ test_case[2]);
+ if (!got_ho_req) {
+ printf("Test failed, because no handover was "
+ "requested\n");
+ return EXIT_FAILURE;
+ }
+ fprintf(stderr, " * Got handover/assignment request at "
+ "BTS %d TS %d\n",
+ ho_req_lchan->ts->trx->bts->nr,
+ ho_req_lchan->ts->nr);
+ if (ho_req_lchan->ts->trx->bts->nr
+ != atoi(test_case[1])) {
+ printf("Test failed, because "
+ "handover/assignment was not commanded "
+ "at the expected BTS\n");
+ return EXIT_FAILURE;
+ }
+ if (ho_req_lchan->ts->nr != atoi(test_case[2])) {
+ printf("Test failed, because "
+ "handover/assignment was not commanded "
+ "at the expected TS\n");
+ return EXIT_FAILURE;
+ }
+ test_case += 3;
+ } else
+ if (!strcmp(*test_case, "ack-chan")) {
+ fprintf(stderr, "- Acknowledging channel request\n");
+ if (!got_chan_req) {
+ printf("Cannot ack channel, because no "
+ "request\n");
+ return EXIT_FAILURE;
+ }
+ test_case += 1;
+ got_ho_req = 0;
+ send_chan_act_ack(chan_req_lchan, 1);
+ } else
+ if (!strcmp(*test_case, "ho-complete")) {
+ fprintf(stderr, "- Acknowledging handover/assignment "
+ "request\n");
+ if (!got_chan_req) {
+ printf("Cannot ack handover/assignment, "
+ "because no chan request\n");
+ return EXIT_FAILURE;
+ }
+ if (!got_ho_req) {
+ printf("Cannot ack handover/assignment, "
+ "because no ho request\n");
+ return EXIT_FAILURE;
+ }
+ test_case += 1;
+ got_chan_req = 0;
+ got_ho_req = 0;
+ /* switch lchan */
+ for (i = 0; i < lchan_num; i++) {
+ if (lchan[i] == ho_req_lchan) {
+ fprintf(stderr, " * MS %d changes from "
+ "BTS=%d TS=%d to BTS=%d "
+ "TS=%d\n", i,
+ lchan[i]->ts->trx->bts->nr,
+ lchan[i]->ts->nr,
+ chan_req_lchan->ts->trx->bts->nr,
+ chan_req_lchan->ts->nr);
+ lchan[i] = chan_req_lchan;
+ }
+ }
+ send_ho_complete(chan_req_lchan, true);
+ } else
+ if (!strcmp(*test_case, "ho-failed")) {
+ fprintf(stderr, "- Making handover fail\n");
+ if (!got_chan_req) {
+ printf("Cannot fail handover, because no chan "
+ "request\n");
+ return EXIT_FAILURE;
+ }
+ test_case += 1;
+ got_chan_req = 0;
+ got_ho_req = 0;
+ send_ho_complete(ho_req_lchan, false);
+ } else
+ if (!strcmp(*test_case, "print")) {
+ fprintf(stderr, "\n%s\n\n", test_case[1]);
+ test_case += 2;
+ } else {
+ printf("Unknown test command '%s', please fix!\n",
+ *test_case);
+ return EXIT_FAILURE;
+ }
+ }
+
+ for (i = 0; i < lchan_num; i++) {
+ struct gsm_subscriber_connection *conn = lchan[i]->conn;
+ lchan[i]->conn = NULL;
+ conn->lchan = NULL;
+ bsc_subscr_con_free(conn);
+ lchan_free(lchan[i]);
+ }
+
+ fprintf(stderr, "--------------------\n");
+
+ printf("Test OK\n");
+
+ fprintf(stderr, "--------------------\n");
+
+ return EXIT_SUCCESS;
+}
+
+void rtp_socket_free() {}
+void rtp_send_frame() {}
+void rtp_socket_upstream() {}
+void rtp_socket_create() {}
+void rtp_socket_connect() {}
+void rtp_socket_proxy() {}
+void trau_mux_unmap() {}
+void trau_mux_map_lchan() {}
+void trau_recv_lchan() {}
+void trau_send_frame() {}
diff --git a/tests/handover/handover_test.ok b/tests/handover/handover_test.ok
new file mode 100644
index 000000000..678f9a34e
--- /dev/null
+++ b/tests/handover/handover_test.ok
@@ -0,0 +1 @@
+Test OK
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 6ef3f2910..73b684659 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -51,3 +51,171 @@ cat $abs_srcdir/bssap/bssap_test.ok > expout
cat $abs_srcdir/bssap/bssap_test.err > experr
AT_CHECK([$abs_top_builddir/tests/bssap/bssap_test], [], [expout], [experr])
AT_CLEANUP
+
+AT_SETUP([handover test 0])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 0], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 1])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 1], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 2])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 2], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 3])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 3], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 4])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 4], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 5])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 5], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 6])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 6], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 7])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 7], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 8])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 8], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 9])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 9], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 10])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 10], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 11])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 11], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 12])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 12], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 13])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 13], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 14])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 14], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 15])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 15], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 16])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 16], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 17])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 17], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 18])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 18], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 19])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 19], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 20])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 20], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 21])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 21], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 22])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 22], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 23])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 23], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 24])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 24], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 25])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 25], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 26])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 26], [], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([handover test 27])
+AT_KEYWORDS([handover])
+cat $abs_srcdir/handover/handover_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/handover/handover_test 27], [], [expout], [ignore])
+AT_CLEANUP