aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/osmocom/bsc/Makefile.am2
-rw-r--r--include/osmocom/bsc/gsm_data.h17
-rw-r--r--include/osmocom/bsc/gsm_data_shared.h2
-rw-r--r--include/osmocom/bsc/handover_cfg.h112
-rw-r--r--include/osmocom/bsc/handover_vty.h7
-rw-r--r--src/libbsc/Makefile.am1
-rw-r--r--src/libbsc/bsc_vty.c148
-rw-r--r--src/libbsc/handover_decision.c26
-rw-r--r--src/libbsc/handover_vty.c101
-rw-r--r--src/libbsc/net_init.c9
-rw-r--r--src/libcommon/Makefile.am1
-rw-r--r--src/libcommon/gsm_data.c1
-rw-r--r--src/libcommon/gsm_data_shared.c3
-rw-r--r--src/libcommon/handover_cfg.c83
-rw-r--r--tests/Makefile.am11
-rw-r--r--tests/handover_cfg.vty279
16 files changed, 653 insertions, 150 deletions
diff --git a/include/osmocom/bsc/Makefile.am b/include/osmocom/bsc/Makefile.am
index b067fc2b2..699aeb339 100644
--- a/include/osmocom/bsc/Makefile.am
+++ b/include/osmocom/bsc/Makefile.am
@@ -25,7 +25,9 @@ noinst_HEADERS = \
gsm_data.h \
gsm_data_shared.h \
handover.h \
+ handover_cfg.h \
handover_decision.h \
+ handover_vty.h \
ipaccess.h \
meas_feed.h \
meas_rep.h \
diff --git a/include/osmocom/bsc/gsm_data.h b/include/osmocom/bsc/gsm_data.h
index ed2a95c1c..c09d54680 100644
--- a/include/osmocom/bsc/gsm_data.h
+++ b/include/osmocom/bsc/gsm_data.h
@@ -227,22 +227,7 @@ struct gsm_network {
uint16_t network_code;
int a5_encryption;
int neci;
- struct {
- int active;
- /* Window RXLEV averaging */
- unsigned int win_rxlev_avg; /* number of SACCH frames */
- /* Window RXQUAL averaging */
- unsigned int win_rxqual_avg; /* number of SACCH frames */
- /* Window RXLEV neighbouring cells averaging */
- unsigned int win_rxlev_avg_neigh; /* number of SACCH frames */
-
- /* how often should we check for power budget HO */
- unsigned int pwr_interval; /* SACCH frames */
- /* how much better does a neighbor cell have to be ? */
- unsigned int pwr_hysteresis; /* dBm */
- /* maximum distacne before we try a handover */
- unsigned int max_distance; /* TA values */
- } handover;
+ struct handover_cfg *ho;
struct rate_ctr_group *bsc_ctrs;
diff --git a/include/osmocom/bsc/gsm_data_shared.h b/include/osmocom/bsc/gsm_data_shared.h
index db854fd00..fd566d2dd 100644
--- a/include/osmocom/bsc/gsm_data_shared.h
+++ b/include/osmocom/bsc/gsm_data_shared.h
@@ -799,6 +799,8 @@ struct gsm_bts {
struct pcu_sock_state *pcu_state;
struct rate_ctr_group *bts_ctrs;
+
+ struct handover_cfg *ho;
};
diff --git a/include/osmocom/bsc/handover_cfg.h b/include/osmocom/bsc/handover_cfg.h
new file mode 100644
index 000000000..95c0e50ec
--- /dev/null
+++ b/include/osmocom/bsc/handover_cfg.h
@@ -0,0 +1,112 @@
+#pragma once
+
+#include <stdbool.h>
+
+struct vty;
+
+/* handover_cfg is an opaque struct to manage several levels of configuration. There is an overall handover
+ * config on 'network' level and a per-'bts' specific handover config. If the 'bts' level sets no values,
+ * the defaults from 'network' level are used implicitly, and changes take effect immediately. */
+struct handover_cfg;
+
+struct handover_cfg *ho_cfg_init(void *ctx, struct handover_cfg *higher_level_cfg);
+
+#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"
+#define HO_CFG_STR_WIN_RXQUAL HO_CFG_STR_WIN "Received-Quality averaging\n"
+#define HO_CFG_STR_POWER_BUDGET HO_CFG_STR_HANDOVER "Neighbor cell power triggering\n" "Neighbor cell power triggering\n"
+#define HO_CFG_STR_AVG_COUNT "Number of values to average over\n"
+
+#define as_is(x) (x)
+
+static inline bool a2bool(const char *arg)
+{
+ return (bool)(atoi(arg));
+}
+
+static inline int bool2i(bool arg)
+{
+ return arg? 1 : 0;
+}
+
+
+/* The HO_CFG_ONE_MEMBER macro gets redefined, depending on whether to define struct members,
+ * function declarations or definitions... It is of the format
+ * HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL,
+ * VTY_CMD, VTY_CMD_ARG, VTY_ARG_EVAL,
+ * VTY_WRITE_FMT, VTY_WRITE_CONV,
+ * VTY_DOC)
+ * Then using HO_CFG_ALL_MEMBERS can save a lot of code dup in defining API declaration, API
+ * definitions, VTY commands and VTY write code. Of course this doesn't prevent us from adding manual
+ * members as well, in case future additions don't fit in this scheme.
+ *
+ * TYPE: a type name like int.
+ * NAME: a variable name suitable for a struct member.
+ * DEFAULT_VAL: default value, as passed to the VTY, e.g. '0' or 'foo', without quotes.
+ * VTY_CMD: a command string for VTY without any arguments.
+ * VTY_CMD_ARG: VTY value range like '<0-23>' or 'foo|bar', will become '(VTY_CMD_ARG|default)'.
+ * VTY_ARG_EVAL: function name for parsing the VTY arg[0], e.g. 'atoi'.
+ * VTY_WRITE_FMT: printf-like string format for vty_out().
+ * VTY_WRITE_CONV: function name to convert struct value to VTY_WRITE_FMT, e.g. 'as_is'.
+ * VTY_DOC: VTY documentation strings to match VTY_CMD and VTY_CMD_ARGs.
+ */
+#define HO_CFG_ALL_MEMBERS \
+ \
+ HO_CFG_ONE_MEMBER(bool, ho_active, 0, \
+ "handover", "0|1", a2bool, "%d", bool2i, \
+ HO_CFG_STR_HANDOVER \
+ "Disable in-call handover\n" \
+ "Enable in-call handover\n" \
+ "Enable/disable handover: ") \
+ \
+ HO_CFG_ONE_MEMBER(unsigned int, rxlev_avg_win, 10, \
+ "handover window rxlev averaging", "<1-10>", atoi, "%u", as_is, \
+ HO_CFG_STR_WIN_RXLEV \
+ "How many RxLev measurements are used for averaging\n" \
+ "RxLev averaging: " HO_CFG_STR_AVG_COUNT) \
+ \
+ HO_CFG_ONE_MEMBER(unsigned int, rxqual_avg_win, 1, \
+ "handover window rxqual averaging", "<1-10>", atoi, "%u", as_is, \
+ HO_CFG_STR_WIN_RXQUAL \
+ "How many RxQual measurements are used for averaging\n" \
+ "RxQual averaging: " HO_CFG_STR_AVG_COUNT) \
+ \
+ HO_CFG_ONE_MEMBER(unsigned int, rxlev_neigh_avg_win, 10, \
+ "handover window rxlev neighbor averaging", "<1-10>", atoi, "%u", as_is, \
+ HO_CFG_STR_WIN_RXLEV \
+ "How many Neighbor RxLev measurements are used for averaging\n" \
+ "How many Neighbor RxLev measurements are used for averaging\n" \
+ "Neighbor RxLev averaging: " HO_CFG_STR_AVG_COUNT) \
+ \
+ HO_CFG_ONE_MEMBER(unsigned int, pwr_interval, 6, \
+ "handover power budget interval", "<1-99>", atoi, "%u", as_is, \
+ HO_CFG_STR_POWER_BUDGET \
+ "How often to check for a better cell (SACCH frames)\n" \
+ "Check for stronger neighbor every N number of SACCH frames\n") \
+ \
+ HO_CFG_ONE_MEMBER(unsigned int, pwr_hysteresis, 3, \
+ "handover power budget hysteresis", "<0-999>", atoi, "%u", as_is, \
+ HO_CFG_STR_POWER_BUDGET \
+ "How many dBm stronger must a neighbor be to become a HO candidate\n" \
+ "Neighbor's strength difference in dBm\n") \
+ \
+ HO_CFG_ONE_MEMBER(unsigned int, max_distance, 9999, \
+ "handover maximum distance" , "<0-9999>", atoi, "%u", as_is, \
+ HO_CFG_STR_HANDOVER \
+ "Maximum Timing-Advance value (i.e. MS distance) before triggering HO\n" \
+ "Maximum Timing-Advance value (i.e. MS distance) before triggering HO\n" \
+ "Maximum Timing-Advance value (i.e. MS distance) before triggering HO\n") \
+
+
+/* Declare public API for handover cfg parameters... */
+
+#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, VTY1, VTY2, VTY3, VTY4, VTY5, VTY6) \
+ TYPE ho_get_##NAME(struct handover_cfg *ho); \
+ void ho_set_##NAME(struct handover_cfg *ho, TYPE val); \
+ bool ho_isset_##NAME(struct handover_cfg *ho); \
+ void ho_clear_##NAME(struct handover_cfg *ho); \
+ bool ho_isset_on_parent_##NAME(struct handover_cfg *ho);
+
+HO_CFG_ALL_MEMBERS
+#undef HO_CFG_ONE_MEMBER
diff --git a/include/osmocom/bsc/handover_vty.h b/include/osmocom/bsc/handover_vty.h
new file mode 100644
index 000000000..48af136e5
--- /dev/null
+++ b/include/osmocom/bsc/handover_vty.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/bsc/handover_cfg.h>
+
+void ho_vty_init();
+void ho_vty_write(struct vty *vty, const char *indent, struct handover_cfg *ho);
diff --git a/src/libbsc/Makefile.am b/src/libbsc/Makefile.am
index 3ac1c0b80..d118f443b 100644
--- a/src/libbsc/Makefile.am
+++ b/src/libbsc/Makefile.am
@@ -57,5 +57,6 @@ libbsc_a_SOURCES = \
net_init.c \
bsc_dyn_ts.c \
bts_ipaccess_nanobts_omlattr.c \
+ handover_vty.c \
$(NULL)
diff --git a/src/libbsc/bsc_vty.c b/src/libbsc/bsc_vty.c
index 75b0b9a5c..b4a8e2ddc 100644
--- a/src/libbsc/bsc_vty.c
+++ b/src/libbsc/bsc_vty.c
@@ -58,6 +58,8 @@
#include <osmocom/bsc/pcu_if.h>
#include <osmocom/bsc/common_cs.h>
#include <osmocom/bsc/handover.h>
+#include <osmocom/bsc/handover_cfg.h>
+#include <osmocom/bsc/handover_vty.h>
#include <osmocom/bsc/gsm_04_08_utils.h>
#include <inttypes.h>
@@ -178,8 +180,27 @@ static void net_dump_vty(struct vty *vty, struct gsm_network *net)
VTY_NEWLINE);
vty_out(vty, " Use TCH for Paging any: %d%s", net->pag_any_tch,
VTY_NEWLINE);
- vty_out(vty, " Handover: %s%s", net->handover.active ? "On" : "Off",
- VTY_NEWLINE);
+
+ {
+ struct gsm_bts *bts;
+ unsigned int ho_active_count = 0;
+ unsigned int ho_inactive_count = 0;
+
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ if (ho_get_ho_active(bts->ho))
+ ho_active_count ++;
+ else
+ ho_inactive_count ++;
+ }
+
+ if (ho_active_count && ho_inactive_count)
+ vty_out(vty, " Handover: On at %u BTS, Off at %u BTS%s",
+ ho_active_count, ho_inactive_count, VTY_NEWLINE);
+ else
+ vty_out(vty, " Handover: %s%s", ho_active_count ? "On" : "Off",
+ VTY_NEWLINE);
+ }
+
network_chan_load(&pl, net);
vty_out(vty, " Current Channel Load:%s", VTY_NEWLINE);
dump_pchan_load_vty(vty, " ", &pl);
@@ -778,6 +799,8 @@ 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);
+
config_write_bts_model(vty, bts);
}
@@ -807,19 +830,9 @@ static int config_write_net(struct vty *vty)
vty_out(vty, " encryption a5 %u%s", gsmnet->a5_encryption, VTY_NEWLINE);
vty_out(vty, " neci %u%s", gsmnet->neci, VTY_NEWLINE);
vty_out(vty, " paging any use tch %d%s", gsmnet->pag_any_tch, VTY_NEWLINE);
- vty_out(vty, " handover %u%s", gsmnet->handover.active, VTY_NEWLINE);
- vty_out(vty, " handover window rxlev averaging %u%s",
- gsmnet->handover.win_rxlev_avg, VTY_NEWLINE);
- vty_out(vty, " handover window rxqual averaging %u%s",
- gsmnet->handover.win_rxqual_avg, VTY_NEWLINE);
- vty_out(vty, " handover window rxlev neighbor averaging %u%s",
- gsmnet->handover.win_rxlev_avg_neigh, VTY_NEWLINE);
- vty_out(vty, " handover power budget interval %u%s",
- gsmnet->handover.pwr_interval, VTY_NEWLINE);
- vty_out(vty, " handover power budget hysteresis %u%s",
- gsmnet->handover.pwr_hysteresis, VTY_NEWLINE);
- vty_out(vty, " handover maximum distance %u%s",
- gsmnet->handover.max_distance, VTY_NEWLINE);
+
+ ho_vty_write(vty, " ", gsmnet->ho);
+
VTY_OUT_TIMER(3101);
VTY_OUT_TIMER(3103);
VTY_OUT_TIMER(3105);
@@ -1649,100 +1662,6 @@ DEFUN(cfg_net_neci,
return CMD_SUCCESS;
}
-#define HANDOVER_STR "Handover Options\n"
-
-DEFUN(cfg_net_handover, cfg_net_handover_cmd,
- "handover (0|1)",
- HANDOVER_STR
- "Don't perform in-call handover\n"
- "Perform in-call handover\n")
-{
- int enable = atoi(argv[0]);
- struct gsm_network *gsmnet = gsmnet_from_vty(vty);
-
- if (enable && ipacc_rtp_direct) {
- vty_out(vty, "%% Cannot enable handover unless RTP Proxy mode "
- "is enabled by using the -P command line option%s",
- VTY_NEWLINE);
- return CMD_WARNING;
- }
- gsmnet->handover.active = enable;
-
- return CMD_SUCCESS;
-}
-
-#define HO_WIN_STR HANDOVER_STR "Measurement Window\n"
-#define HO_WIN_RXLEV_STR HO_WIN_STR "Received Level Averaging\n"
-#define HO_WIN_RXQUAL_STR HO_WIN_STR "Received Quality Averaging\n"
-#define HO_PBUDGET_STR HANDOVER_STR "Power Budget\n"
-#define HO_AVG_COUNT_STR "Amount to use for Averaging\n"
-
-DEFUN(cfg_net_ho_win_rxlev_avg, cfg_net_ho_win_rxlev_avg_cmd,
- "handover window rxlev averaging <1-10>",
- HO_WIN_RXLEV_STR
- "How many RxLev measurements are used for averaging\n"
- HO_AVG_COUNT_STR)
-{
- struct gsm_network *gsmnet = gsmnet_from_vty(vty);
- gsmnet->handover.win_rxlev_avg = atoi(argv[0]);
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_ho_win_rxqual_avg, cfg_net_ho_win_rxqual_avg_cmd,
- "handover window rxqual averaging <1-10>",
- HO_WIN_RXQUAL_STR
- "How many RxQual measurements are used for averaging\n"
- HO_AVG_COUNT_STR)
-{
- struct gsm_network *gsmnet = gsmnet_from_vty(vty);
- gsmnet->handover.win_rxqual_avg = atoi(argv[0]);
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_ho_win_rxlev_neigh_avg, cfg_net_ho_win_rxlev_avg_neigh_cmd,
- "handover window rxlev neighbor averaging <1-10>",
- HO_WIN_RXLEV_STR "Neighbor\n"
- "How many RxQual measurements are used for averaging\n"
- HO_AVG_COUNT_STR)
-{
- struct gsm_network *gsmnet = gsmnet_from_vty(vty);
- gsmnet->handover.win_rxlev_avg_neigh = atoi(argv[0]);
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_ho_pwr_interval, cfg_net_ho_pwr_interval_cmd,
- "handover power budget interval <1-99>",
- HO_PBUDGET_STR
- "How often to check if we have a better cell (SACCH frames)\n"
- "Interval\n" "Number\n")
-{
- struct gsm_network *gsmnet = gsmnet_from_vty(vty);
- gsmnet->handover.pwr_interval = atoi(argv[0]);
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_ho_pwr_hysteresis, cfg_net_ho_pwr_hysteresis_cmd,
- "handover power budget hysteresis <0-999>",
- HO_PBUDGET_STR
- "How many dB does a neighbor to be stronger to become a HO candidate\n"
- "Hysteresis\n" "Number\n")
-{
- struct gsm_network *gsmnet = gsmnet_from_vty(vty);
- gsmnet->handover.pwr_hysteresis = atoi(argv[0]);
- return CMD_SUCCESS;
-}
-
-DEFUN(cfg_net_ho_max_distance, cfg_net_ho_max_distance_cmd,
- "handover maximum distance <0-9999>",
- HANDOVER_STR
- "How big is the maximum timing advance before HO is forced\n"
- "Distance\n" "Number\n")
-{
- struct gsm_network *gsmnet = gsmnet_from_vty(vty);
- gsmnet->handover.max_distance = atoi(argv[0]);
- return CMD_SUCCESS;
-}
-
DEFUN(cfg_net_pag_any_tch,
cfg_net_pag_any_tch_cmd,
"paging any use tch (0|1)",
@@ -4387,13 +4306,6 @@ int bsc_vty_init(struct gsm_network *network)
logging_vty_add_cmds(NULL);
install_element(GSMNET_NODE, &cfg_net_neci_cmd);
- install_element(GSMNET_NODE, &cfg_net_handover_cmd);
- install_element(GSMNET_NODE, &cfg_net_ho_win_rxlev_avg_cmd);
- install_element(GSMNET_NODE, &cfg_net_ho_win_rxqual_avg_cmd);
- install_element(GSMNET_NODE, &cfg_net_ho_win_rxlev_avg_neigh_cmd);
- install_element(GSMNET_NODE, &cfg_net_ho_pwr_interval_cmd);
- install_element(GSMNET_NODE, &cfg_net_ho_pwr_hysteresis_cmd);
- install_element(GSMNET_NODE, &cfg_net_ho_max_distance_cmd);
install_element(GSMNET_NODE, &cfg_net_T3101_cmd);
install_element(GSMNET_NODE, &cfg_net_T3103_cmd);
install_element(GSMNET_NODE, &cfg_net_T3105_cmd);
@@ -4408,6 +4320,7 @@ int bsc_vty_init(struct gsm_network *network)
install_element(GSMNET_NODE, &cfg_net_T3141_cmd);
install_element(GSMNET_NODE, &cfg_net_dtx_cmd);
install_element(GSMNET_NODE, &cfg_net_pag_any_tch_cmd);
+ /* See also handover commands added on net level from handover_vty.c */
install_element(GSMNET_NODE, &cfg_bts_cmd);
install_node(&bts_node, config_write_bts);
@@ -4513,6 +4426,7 @@ int bsc_vty_init(struct gsm_network *network)
install_element(BTS_NODE, &cfg_bts_amr_hr_hyst3_cmd);
install_element(BTS_NODE, &cfg_bts_amr_hr_start_mode_cmd);
install_element(BTS_NODE, &cfg_bts_pcu_sock_cmd);
+ /* See also handover commands added on bts level from handover_vty.c */
install_element(BTS_NODE, &cfg_trx_cmd);
install_node(&trx_node, dummy_config_write);
@@ -4551,6 +4465,8 @@ int bsc_vty_init(struct gsm_network *network)
e1inp_vty_init();
osmo_fsm_vty_add_cmds();
+ ho_vty_init();
+
bsc_vty_init_extra();
return 0;
diff --git a/src/libbsc/handover_decision.c b/src/libbsc/handover_decision.c
index 3bca05fa0..a2abb391a 100644
--- a/src/libbsc/handover_decision.c
+++ b/src/libbsc/handover_decision.c
@@ -30,9 +30,11 @@
#include <osmocom/bsc/meas_rep.h>
#include <osmocom/bsc/signal.h>
#include <osmocom/core/talloc.h>
-#include <osmocom/bsc/handover.h>
#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/bsc/handover.h>
+#include <osmocom/bsc/handover_cfg.h>
+
/* Get reference to a neighbor cell on a given BCCH ARFCN */
static struct gsm_bts *gsm_bts_neighbor(const struct gsm_bts *bts,
uint16_t arfcn, uint8_t bsic)
@@ -187,7 +189,7 @@ static void process_meas_neigh(struct gsm_meas_rep *mr)
/* attempt to do a handover */
static int attempt_handover(struct gsm_meas_rep *mr)
{
- struct gsm_network *net = mr->lchan->ts->trx->bts->network;
+ struct gsm_bts *bts = mr->lchan->ts->trx->bts;
struct neigh_meas_proc *best_cell = NULL;
unsigned int best_better_db = 0;
int i, rc;
@@ -204,10 +206,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, net->handover.win_rxlev_avg_neigh);
+ avg = neigh_meas_avg(nmp, ho_get_rxlev_neigh_avg_win(bts->ho));
/* check if hysteresis is fulfilled */
- if (avg < mr->dl.full.rx_lev + net->handover.pwr_hysteresis)
+ if (avg < mr->dl.full.rx_lev + ho_get_pwr_hysteresis(bts->ho))
continue;
better = avg - mr->dl.full.rx_lev;
@@ -222,7 +224,7 @@ static int attempt_handover(struct gsm_meas_rep *mr)
LOGP(DHO, LOGL_INFO, "%s: Cell on ARFCN %u is better: ",
gsm_ts_name(mr->lchan->ts), best_cell->arfcn);
- if (!net->handover.active) {
+ if (!ho_get_ho_active(bts->ho)) {
LOGPC(DHO, LOGL_INFO, "Skipping, Handover disabled\n");
return 0;
}
@@ -248,9 +250,10 @@ static int attempt_handover(struct gsm_meas_rep *mr)
* attempt a handover */
static int process_meas_rep(struct gsm_meas_rep *mr)
{
- struct gsm_network *net = mr->lchan->ts->trx->bts->network;
+ struct gsm_bts *bts = mr->lchan->ts->trx->bts;
enum meas_rep_field dlev, dqual;
int av_rxlev;
+ unsigned int pwr_interval;
/* we currently only do handover for TCH channels */
switch (mr->lchan->type) {
@@ -274,7 +277,7 @@ static int process_meas_rep(struct gsm_meas_rep *mr)
process_meas_neigh(mr);
av_rxlev = get_meas_rep_avg(mr->lchan, dlev,
- net->handover.win_rxlev_avg);
+ ho_get_rxlev_avg_win(bts->ho));
/* Interference HO */
if (rxlev2dbm(av_rxlev) > -85 &&
@@ -297,14 +300,15 @@ static int process_meas_rep(struct gsm_meas_rep *mr)
}
/* Distance */
- if (mr->ms_l1.ta > net->handover.max_distance) {
- LOGPC(DHO, LOGL_INFO, "HO cause: Distance av_rxlev=%d dBm ta=%u\n",
- rxlev2dbm(av_rxlev), mr->ms_l1.ta);
+ if (mr->ms_l1.ta > ho_get_max_distance(bts->ho)) {
+ 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 % net->handover.pwr_interval) == net->handover.pwr_interval - 1)
+ pwr_interval = ho_get_pwr_interval(bts->ho);
+ if ((mr->nr % pwr_interval) == pwr_interval - 1)
return attempt_handover(mr);
return 0;
diff --git a/src/libbsc/handover_vty.c b/src/libbsc/handover_vty.c
new file mode 100644
index 000000000..225e9a909
--- /dev/null
+++ b/src/libbsc/handover_vty.c
@@ -0,0 +1,101 @@
+/* OsmoBSC interface to quagga VTY for handover parameters */
+/* (C) 2017 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * Author: 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 <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/vty.h>
+#include <osmocom/bsc/handover_cfg.h>
+
+static struct handover_cfg *ho_cfg_from_vty(struct vty *vty)
+{
+ switch (vty->node) {
+ case GSMNET_NODE:
+ return gsmnet_from_vty(vty)->ho;
+ case BTS_NODE:
+ OSMO_ASSERT(vty->index);
+ return ((struct gsm_bts *)vty->index)->ho;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+
+#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, \
+ VTY_CMD, VTY_CMD_ARG, VTY_ARG_EVAL, \
+ VTY_WRITE_FMT, VTY_WRITE_CONV, \
+ VTY_DOC) \
+DEFUN(cfg_ho_##NAME, cfg_ho_##NAME##_cmd, \
+ VTY_CMD " (" VTY_CMD_ARG "|default)", \
+ VTY_DOC \
+ "Use default (" #DEFAULT_VAL "), remove explicit setting on this node\n") \
+{ \
+ struct handover_cfg *ho = ho_cfg_from_vty(vty); \
+ const char *val = argv[0]; \
+ if (!strcmp(val, "default")) { \
+ const char *msg; \
+ if (ho_isset_##NAME(ho)) {\
+ ho_clear_##NAME(ho); \
+ msg = "setting removed, now is"; \
+ } else \
+ msg = "already was unset, still is"; \
+ vty_out(vty, "%% '" VTY_CMD "' %s " VTY_WRITE_FMT "%s%s", \
+ msg, VTY_WRITE_CONV( ho_get_##NAME(ho) ), \
+ ho_isset_on_parent_##NAME(ho)? " (set on higher level node)" : "", \
+ VTY_NEWLINE); \
+ } \
+ else \
+ ho_set_##NAME(ho, VTY_ARG_EVAL(val)); \
+ return CMD_SUCCESS; \
+}
+
+HO_CFG_ALL_MEMBERS
+#undef HO_CFG_ONE_MEMBER
+
+
+void ho_vty_write(struct vty *vty, const char *indent, struct handover_cfg *ho)
+{
+#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, \
+ VTY_CMD, VTY_CMD_ARG, VTY_ARG_EVAL, \
+ VTY_WRITE_FMT, VTY_WRITE_CONV, \
+ VTY_DOC) \
+ if (ho_isset_##NAME(ho)) \
+ vty_out(vty, "%s" VTY_CMD " " VTY_WRITE_FMT "%s", indent, \
+ VTY_WRITE_CONV( ho_get_##NAME(ho) ), VTY_NEWLINE);
+
+ HO_CFG_ALL_MEMBERS
+#undef HO_CFG_ONE_MEMBER
+}
+
+static void ho_vty_init_cmds(int parent_node)
+{
+#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, VTY1, VTY2, VTY3, VTY4, VTY5, VTY6) \
+ install_element(parent_node, &cfg_ho_##NAME##_cmd);
+
+ HO_CFG_ALL_MEMBERS
+#undef HO_CFG_ONE_MEMBER
+}
+
+void ho_vty_init()
+{
+ ho_vty_init_cmds(GSMNET_NODE);
+ ho_vty_init_cmds(BTS_NODE);
+}
+
diff --git a/src/libbsc/net_init.c b/src/libbsc/net_init.c
index 30de0cce9..57d824156 100644
--- a/src/libbsc/net_init.c
+++ b/src/libbsc/net_init.c
@@ -21,6 +21,7 @@
#include <osmocom/bsc/osmo_bsc.h>
#include <osmocom/bsc/bsc_msc_data.h>
#include <osmocom/bsc/gsm_04_08_utils.h>
+#include <osmocom/bsc/handover_cfg.h>
struct gsm_network *bsc_network_init(void *ctx,
uint16_t country_code,
@@ -55,13 +56,7 @@ struct gsm_network *bsc_network_init(void *ctx,
net->T3122 = GSM_T3122_DEFAULT;
net->T3141 = GSM_T3141_DEFAULT;
- /* default set of handover parameters */
- net->handover.win_rxlev_avg = 10;
- net->handover.win_rxqual_avg = 1;
- net->handover.win_rxlev_avg_neigh = 10;
- net->handover.pwr_interval = 6;
- net->handover.pwr_hysteresis = 3;
- net->handover.max_distance = 9999;
+ net->ho = ho_cfg_init(net, NULL);
INIT_LLIST_HEAD(&net->bts_list);
diff --git a/src/libcommon/Makefile.am b/src/libcommon/Makefile.am
index c66cbcdcd..9f7e7b9e9 100644
--- a/src/libcommon/Makefile.am
+++ b/src/libcommon/Makefile.am
@@ -27,4 +27,5 @@ libcommon_a_SOURCES = \
gsm_data_shared.c \
socket.c \
talloc_ctx.c \
+ handover_cfg.c \
$(NULL)
diff --git a/src/libcommon/gsm_data.c b/src/libcommon/gsm_data.c
index 87d954ad8..92ebbfe0c 100644
--- a/src/libcommon/gsm_data.c
+++ b/src/libcommon/gsm_data.c
@@ -37,6 +37,7 @@
#include <osmocom/bsc/gsm_data.h>
#include <osmocom/bsc/bsc_msc_data.h>
#include <osmocom/bsc/abis_nm.h>
+#include <osmocom/bsc/handover_cfg.h>
void *tall_bsc_ctx;
diff --git a/src/libcommon/gsm_data_shared.c b/src/libcommon/gsm_data_shared.c
index c14047d49..e4ec594f8 100644
--- a/src/libcommon/gsm_data_shared.c
+++ b/src/libcommon/gsm_data_shared.c
@@ -33,6 +33,7 @@
#include <osmocom/core/statistics.h>
#include <osmocom/bsc/gsm_data.h>
+#include <osmocom/bsc/handover_cfg.h>
void gsm_abis_mo_reset(struct gsm_abis_mo *mo)
{
@@ -374,6 +375,8 @@ 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, net->ho);
+
return bts;
}
diff --git a/src/libcommon/handover_cfg.c b/src/libcommon/handover_cfg.c
new file mode 100644
index 000000000..8c208f669
--- /dev/null
+++ b/src/libcommon/handover_cfg.c
@@ -0,0 +1,83 @@
+/* OsmoBSC handover configuration implementation */
+/* (C) 2017 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * Author: 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 <talloc.h>
+
+#include <osmocom/bsc/vty.h>
+#include <osmocom/bsc/handover_cfg.h>
+#include <osmocom/bsc/gsm_data.h>
+
+struct handover_cfg {
+ struct handover_cfg *higher_level_cfg;
+
+#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, VTY1, VTY2, VTY3, VTY4, VTY5, VTY6) \
+ TYPE NAME; \
+ bool has_##NAME;
+
+ HO_CFG_ALL_MEMBERS
+#undef HO_CFG_ONE_MEMBER
+};
+
+struct handover_cfg *ho_cfg_init(void *ctx, struct handover_cfg *higher_level_cfg)
+{
+ struct handover_cfg *ho = talloc_zero(ctx, struct handover_cfg);
+ OSMO_ASSERT(ho);
+ ho->higher_level_cfg = higher_level_cfg;
+ return ho;
+}
+
+#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, VTY1, VTY2, VTY_ARG_EVAL, VTY4, VTY5, VTY6) \
+TYPE ho_get_##NAME(struct handover_cfg *ho) \
+{ \
+ if (ho->has_##NAME) \
+ return ho->NAME; \
+ if (ho->higher_level_cfg) \
+ return ho_get_##NAME(ho->higher_level_cfg); \
+ return VTY_ARG_EVAL(#DEFAULT_VAL); \
+} \
+\
+void ho_set_##NAME(struct handover_cfg *ho, TYPE value) \
+{ \
+ ho->NAME = value; \
+ ho->has_##NAME = true; \
+} \
+\
+bool ho_isset_##NAME(struct handover_cfg *ho) \
+{ \
+ return ho->has_##NAME; \
+} \
+\
+void ho_clear_##NAME(struct handover_cfg *ho) \
+{ \
+ ho->has_##NAME = false; \
+} \
+\
+bool ho_isset_on_parent_##NAME(struct handover_cfg *ho) \
+{ \
+ return ho->higher_level_cfg \
+ && (ho_isset_##NAME(ho->higher_level_cfg) \
+ || ho_isset_on_parent_##NAME(ho->higher_level_cfg)); \
+}
+
+HO_CFG_ALL_MEMBERS
+#undef HO_CFG_ONE_MEMBER
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 9207434f0..ba8a5e140 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -34,6 +34,7 @@ EXTRA_DIST = \
$(TESTSUITE) \
vty_test_runner.py \
ctrl_test_runner.py \
+ handover_cfg.vty \
$(NULL)
TESTSUITE = $(srcdir)/testsuite
@@ -44,11 +45,21 @@ DISTCLEANFILES = \
if ENABLE_EXT_TESTS
python-tests: $(BUILT_SOURCES)
+ $(MAKE) vty-test
osmotestvty.py -p $(abs_top_srcdir) -w $(abs_top_builddir) -v
osmotestconfig.py -p $(abs_top_srcdir) -w $(abs_top_builddir) -v
$(srcdir)/vty_test_runner.py -w $(abs_top_builddir) -v
$(srcdir)/ctrl_test_runner.py -w $(abs_top_builddir) -v
rm -f $(top_builddir)/sms.db $(top_builddir)/gsn_restart $(top_builddir)/gtphub_restart_count
+
+# To update the VTY script from current application behavior,
+# pass -u to vty_script_runner.py by doing:
+# make vty-test U=-u
+vty-test:
+ osmo_verify_transcript_vty.py -v \
+ -n OsmoBSC -p 4242 \
+ -r "$(top_builddir)/src/osmo-bsc/osmo-bsc -c $(top_srcdir)/doc/examples/osmo-bsc/osmo-bsc-minimal.cfg" \
+ $(U) $(srcdir)/*.vty
else
python-tests: $(BUILT_SOURCES)
echo "Not running python-based tests (determined at configure-time)"
diff --git a/tests/handover_cfg.vty b/tests/handover_cfg.vty
new file mode 100644
index 000000000..e181797f6
--- /dev/null
+++ b/tests/handover_cfg.vty
@@ -0,0 +1,279 @@
+OsmoBSC> show network
+...
+ Handover: Off
+...
+OsmoBSC> enable
+
+OsmoBSC# ### No handover config present
+OsmoBSC# show running-config
+... !handover
+
+OsmoBSC# ### Toggling handover on network level affects 'show network':
+OsmoBSC# configure terminal
+OsmoBSC(config)# network
+OsmoBSC(config-net)# do show network
+...
+ Handover: Off
+...
+OsmoBSC(config-net)# handover 1
+OsmoBSC(config-net)# do show network
+...
+ Handover: On
+...
+
+OsmoBSC(config-net)# ### If network level default is 'on', bts level can still override to 'off':
+OsmoBSC(config-net)# bts 0
+OsmoBSC(config-net-bts)# handover 0
+OsmoBSC(config-net-bts)# do show network
+...
+ Handover: Off
+...
+OsmoBSC(config-net-bts)# exit
+
+OsmoBSC(config-net)# ### Create a *second* BTS that is not explicitly 'off':
+OsmoBSC(config-net)# bts 1
+OsmoBSC(config-net-bts)# do show network
+...
+ Handover: On at 1 BTS, Off at 1 BTS
+...
+
+OsmoBSC(config-net-bts)# ### Add arbitrary handover config item for bts 1:
+OsmoBSC(config-net-bts)# handover power budget interval 23
+OsmoBSC(config-net-bts)# exit
+OsmoBSC(config-net)# ### HO is 'on' globally, bts 0 disables it, bts 1 tweaks a param:
+OsmoBSC(config-net)# show running-config
+...
+network
+... !handover
+ handover 1
+... !handover
+ bts 0
+... !handover
+ handover 0
+... !handover
+ bts 1
+... !handover
+ handover power budget interval 23
+... !handover
+
+OsmoBSC(config-net)# ### Set global default to 'off', now bts 1 also uses the global default of 'off':
+OsmoBSC(config-net)# handover 0
+OsmoBSC(config-net)# do show network
+...
+ Handover: Off
+...
+OsmoBSC(config-net)# show running-config
+...
+network
+... !handover
+ handover 0
+... !handover
+ bts 0
+... !handover
+ handover 0
+... !handover
+ bts 1
+... !handover
+ handover power budget interval 23
+... !handover
+
+OsmoBSC(config-net)# ### Remove the global setting, i.e. use the factory default net level, with same effect:
+OsmoBSC(config-net)# handover default
+% 'handover' setting removed, now is 0
+OsmoBSC(config-net)# handover default
+% 'handover' already was unset, still is 0
+OsmoBSC(config-net)# do show network
+...
+ Handover: Off
+...
+OsmoBSC(config-net)# show running-config
+...
+network
+... !handover
+ bts 0
+... !handover
+ handover 0
+... !handover
+ bts 1
+... !handover
+ handover power budget interval 23
+... !handover
+
+OsmoBSC(config-net)# ### Re-enable net-level handover, but bts 0 remains disabled explicitly
+OsmoBSC(config-net)# handover 1
+OsmoBSC(config-net)# do show network
+...
+ Handover: On at 1 BTS, Off at 1 BTS
+...
+OsmoBSC(config-net)# show running-config
+...
+network
+... !handover
+ handover 1
+... !handover
+ bts 0
+... !handover
+ handover 0
+... !handover
+ bts 1
+... !handover
+ handover power budget interval 23
+... !handover
+
+OsmoBSC(config-net)# ### Remove explicit setting of bts 0 to also use the global setting:
+OsmoBSC(config-net)# bts 0
+OsmoBSC(config-net-bts)# handover default
+% 'handover' setting removed, now is 1 (set on higher level node)
+OsmoBSC(config-net-bts)# handover default
+% 'handover' already was unset, still is 1 (set on higher level node)
+OsmoBSC(config-net-bts)# do show network
+...
+ Handover: On
+...
+OsmoBSC(config-net-bts)# show running-config
+...
+network
+... !handover
+ handover 1
+... !handover
+ bts 0
+... !handover
+ bts 1
+... !handover
+ handover power budget interval 23
+... !handover
+
+
+OsmoBSC(config-net-bts)# ### Checking online help
+OsmoBSC(config-net-bts)# exit
+OsmoBSC(config-net)# list
+...
+ handover (0|1|default)
+ handover window rxlev averaging (<1-10>|default)
+ handover window rxqual averaging (<1-10>|default)
+ handover window rxlev neighbor averaging (<1-10>|default)
+ handover power budget interval (<1-99>|default)
+ handover power budget hysteresis (<0-999>|default)
+ handover maximum distance (<0-9999>|default)
+...
+
+OsmoBSC(config-net)# handover?
+ handover Handover options
+
+OsmoBSC(config-net)# handover ?
+ 0 Disable in-call handover
+ 1 Enable in-call handover
+ default Enable/disable handover: Use default (0), remove explicit setting on this node
+ window Measurement averaging settings
+ power Neighbor cell power triggering
+ maximum Maximum Timing-Advance value (i.e. MS distance) before triggering HO
+
+OsmoBSC(config-net)# handover window ?
+ rxlev Received-Level averaging
+ rxqual Received-Quality averaging
+
+OsmoBSC(config-net)# handover window rxlev ?
+ averaging How many RxLev measurements are used for averaging
+ neighbor How many Neighbor RxLev measurements are used for averaging
+
+OsmoBSC(config-net)# handover window rxlev averaging ?
+ <1-10> RxLev averaging: Number of values to average over
+ default Use default (10), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover window rxlev neighbor ?
+ averaging How many Neighbor RxLev measurements are used for averaging
+
+OsmoBSC(config-net)# handover window rxlev neighbor averaging ?
+ <1-10> Neighbor RxLev averaging: Number of values to average over
+ default Use default (10), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover window rxqual ?
+ averaging How many RxQual measurements are used for averaging
+
+OsmoBSC(config-net)# handover window rxqual averaging ?
+ <1-10> RxQual averaging: Number of values to average over
+ default Use default (1), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover power ?
+ budget Neighbor cell power triggering
+
+OsmoBSC(config-net)# handover power budget ?
+ interval How often to check for a better cell (SACCH frames)
+ hysteresis How many dBm stronger must a neighbor be to become a HO candidate
+
+OsmoBSC(config-net)# handover power budget interval ?
+ <1-99> Check for stronger neighbor every N number of SACCH frames
+ default Use default (6), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover power budget hysteresis ?
+ <0-999> Neighbor's strength difference in dBm
+ default Use default (3), remove explicit setting on this node
+
+OsmoBSC(config-net)# handover maximum ?
+ distance Maximum Timing-Advance value (i.e. MS distance) before triggering HO
+
+OsmoBSC(config-net)# handover maximum distance ?
+ <0-9999> Maximum Timing-Advance value (i.e. MS distance) before triggering HO
+ default Use default (9999), remove explicit setting on this node
+
+
+OsmoBSC(config-net)# ### Same on BTS level
+OsmoBSC(config-net)# bts 0
+OsmoBSC(config-net-bts)# handover?
+ handover Handover options
+
+OsmoBSC(config-net-bts)# handover ?
+ 0 Disable in-call handover
+ 1 Enable in-call handover
+ default Enable/disable handover: Use default (0), remove explicit setting on this node
+ window Measurement averaging settings
+ power Neighbor cell power triggering
+ maximum Maximum Timing-Advance value (i.e. MS distance) before triggering HO
+
+OsmoBSC(config-net-bts)# handover window ?
+ rxlev Received-Level averaging
+ rxqual Received-Quality averaging
+
+OsmoBSC(config-net-bts)# handover window rxlev ?
+ averaging How many RxLev measurements are used for averaging
+ neighbor How many Neighbor RxLev measurements are used for averaging
+
+OsmoBSC(config-net-bts)# handover window rxlev averaging ?
+ <1-10> RxLev averaging: Number of values to average over
+ default Use default (10), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover window rxlev neighbor ?
+ averaging How many Neighbor RxLev measurements are used for averaging
+
+OsmoBSC(config-net-bts)# handover window rxlev neighbor averaging ?
+ <1-10> Neighbor RxLev averaging: Number of values to average over
+ default Use default (10), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover window rxqual ?
+ averaging How many RxQual measurements are used for averaging
+
+OsmoBSC(config-net-bts)# handover window rxqual averaging ?
+ <1-10> RxQual averaging: Number of values to average over
+ default Use default (1), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover power ?
+ budget Neighbor cell power triggering
+
+OsmoBSC(config-net-bts)# handover power budget ?
+ interval How often to check for a better cell (SACCH frames)
+ hysteresis How many dBm stronger must a neighbor be to become a HO candidate
+
+OsmoBSC(config-net-bts)# handover power budget interval ?
+ <1-99> Check for stronger neighbor every N number of SACCH frames
+ default Use default (6), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover power budget hysteresis ?
+ <0-999> Neighbor's strength difference in dBm
+ default Use default (3), remove explicit setting on this node
+
+OsmoBSC(config-net-bts)# handover maximum ?
+ distance Maximum Timing-Advance value (i.e. MS distance) before triggering HO
+
+OsmoBSC(config-net-bts)# handover maximum distance ?
+ <0-9999> Maximum Timing-Advance value (i.e. MS distance) before triggering HO
+ default Use default (9999), remove explicit setting on this node