From 0fe106869314de8b983a2e61ee7c878ba4f936bf Mon Sep 17 00:00:00 2001 From: Neels Hofmeyr Date: Mon, 27 Nov 2017 21:29:33 +0100 Subject: HO prep: introduce per-BTS handover config, with defaults on net node It is desirable to allow configuring handover for each individual network cell. At the same time, it is desirable to set global defaults. Treat the 'network' node handover parameters as global defaults, add another set of parameters for each individual BTS. This raises questions on how the 'network' node should affect the individual BTS. The simplistic solution would have been: on creating a BTS in the config, just copy the current defaults; with serious drawbacks: - tweaking any parameter in the telnet VTY on network node will never affect any running BTS. - network node defaults *must* be issued before the bts sections in the config file. - when writing a config back to file, we would copy all net node defaults to each BTS node, making the network node configs pointless. Instead, add a handover_cfg API that tracks whether a given node has a value set or not. A bts node ho_cfg gets a pointer to the network node config and returns those values if locally unset. If no value is set on any node, use the "factory" defaults, which are hardcoded in the API. Only write back exactly those config items that were actually issued in a config file / on the telnet VTY. (ho_cfg API wise, we could trivially add another ho_cfg level per TRX if we so desire in the future.) Implement ho parameters as an opaque config struct with getters and setters to ensure the tracking is always heeded. Opaqueness dictates allocating instead of direct embedding in gsm_network and gsm_bts structs, ctx is gsm_net / bts. This is 100% backwards compatible to old configs. - No VTY command syntax changes (only the online help). - If a 'bts' sets nothing, it will use the 'network' defaults. - The 'show network' output only changes in presence of individual BTS configs. On 'show network', say "Handover: On|Off" as before, iff all BTS reflect identical behavior. Otherwise, output BTS counts of handover being enabled or not. Use the same set of VTY commands (same VTY cmd syntax as before) on network and BTS nodes, i.e. don't duplicate VTY code. From the current vty->node, figure out which ho_cfg to modify. For linking, add handover_cfg.c (the value API) in libcommon, while the handover_vty.c is in libbsc. This is mainly because some utility programs use gsm_network and hence suck in the ho stuff, but don't need the VTY commands. Review the VTY online help strings. Add VTY transcript test for handover options, testing config propagation from network to bts nodes, 'show network' output and VTY online help strings. (Needs recent addition of '... !' wildcard to osmo_interact_common.py.) I considered leaving parts of this more readable, but in the end decided for heavy use of macros to define and declare the API, because more values will be added in upcoming patches and I want to prevent myself from messing them up. Inspired-by: jolly/new_handover branch, which moves the config to 'bts' level Depends: I7c1ebb2e7f059047903a53de26a0ec1ce7fa9b98 (osmo-python-tests) Change-Id: I79d35f6d3c0fbee67904378ad7f216df34fde79a --- include/osmocom/bsc/Makefile.am | 2 + include/osmocom/bsc/gsm_data.h | 17 +-- include/osmocom/bsc/gsm_data_shared.h | 2 + include/osmocom/bsc/handover_cfg.h | 112 ++++++++++++++ include/osmocom/bsc/handover_vty.h | 7 + src/libbsc/Makefile.am | 1 + src/libbsc/bsc_vty.c | 148 ++++-------------- src/libbsc/handover_decision.c | 20 +-- src/libbsc/handover_vty.c | 101 ++++++++++++ src/libbsc/net_init.c | 9 +- src/libcommon/Makefile.am | 1 + src/libcommon/gsm_data.c | 1 + src/libcommon/gsm_data_shared.c | 3 + src/libcommon/handover_cfg.c | 83 ++++++++++ tests/Makefile.am | 11 ++ tests/handover_cfg.vty | 279 ++++++++++++++++++++++++++++++++++ 16 files changed, 649 insertions(+), 148 deletions(-) create mode 100644 include/osmocom/bsc/handover_cfg.h create mode 100644 include/osmocom/bsc/handover_vty.h create mode 100644 src/libbsc/handover_vty.c create mode 100644 src/libcommon/handover_cfg.c create mode 100644 tests/handover_cfg.vty 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 0ff58117e..f07a593cc 100644 --- a/include/osmocom/bsc/gsm_data.h +++ b/include/osmocom/bsc/gsm_data.h @@ -225,22 +225,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..264cb1bc1 --- /dev/null +++ b/include/osmocom/bsc/handover_cfg.h @@ -0,0 +1,112 @@ +#pragma once + +#include + +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 +#include + +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 fc12d05d7..23a527a55 100644 --- a/src/libbsc/Makefile.am +++ b/src/libbsc/Makefile.am @@ -55,5 +55,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 d58280215..a304e864e 100644 --- a/src/libbsc/bsc_vty.c +++ b/src/libbsc/bsc_vty.c @@ -58,6 +58,8 @@ #include #include #include +#include +#include #include #include @@ -174,8 +176,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); @@ -774,6 +795,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); } @@ -803,19 +826,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)", @@ -4386,13 +4305,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); @@ -4407,6 +4319,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); @@ -4512,6 +4425,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); @@ -4550,6 +4464,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 2506946ca..15463b0f5 100644 --- a/src/libbsc/handover_decision.c +++ b/src/libbsc/handover_decision.c @@ -30,9 +30,11 @@ #include #include #include -#include #include +#include +#include + /* 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,7 +250,7 @@ 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; @@ -274,7 +276,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 && @@ -299,14 +301,14 @@ static int process_meas_rep(struct gsm_meas_rep *mr) } /* Distance */ - if (mr->ms_l1.ta > net->handover.max_distance) { + 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) == 0) + if ((mr->nr % ho_get_pwr_interval(bts->ho)) == 0) 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 + * (C) 2009-2010 by Harald Welte + * All Rights Reserved + * + * Author: Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include + +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 #include #include +#include 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 ec997b76a..6b0ac5cb7 100644 --- a/src/libcommon/Makefile.am +++ b/src/libcommon/Makefile.am @@ -25,4 +25,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 #include #include +#include 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 #include +#include 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 + * (C) 2009-2010 by Harald Welte + * All Rights Reserved + * + * Author: Neels Hofmeyr + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +#include +#include + +#include +#include +#include + +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 -- cgit v1.2.3