diff options
-rw-r--r-- | include/osmocom/bsc/Makefile.am | 2 | ||||
-rw-r--r-- | include/osmocom/bsc/gsm_data.h | 17 | ||||
-rw-r--r-- | include/osmocom/bsc/gsm_data_shared.h | 2 | ||||
-rw-r--r-- | include/osmocom/bsc/handover_cfg.h | 112 | ||||
-rw-r--r-- | include/osmocom/bsc/handover_vty.h | 7 | ||||
-rw-r--r-- | src/libbsc/Makefile.am | 1 | ||||
-rw-r--r-- | src/libbsc/bsc_vty.c | 148 | ||||
-rw-r--r-- | src/libbsc/handover_decision.c | 26 | ||||
-rw-r--r-- | src/libbsc/handover_vty.c | 101 | ||||
-rw-r--r-- | src/libbsc/net_init.c | 9 | ||||
-rw-r--r-- | src/libcommon/Makefile.am | 1 | ||||
-rw-r--r-- | src/libcommon/gsm_data.c | 1 | ||||
-rw-r--r-- | src/libcommon/gsm_data_shared.c | 3 | ||||
-rw-r--r-- | src/libcommon/handover_cfg.c | 83 | ||||
-rw-r--r-- | tests/Makefile.am | 11 | ||||
-rw-r--r-- | tests/handover_cfg.vty | 279 |
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 |