aboutsummaryrefslogtreecommitdiffstats
path: root/include/osmocom/bsc/handover_cfg.h
blob: ff73fa328da8f4adae543d1dc24cd0179a83d5d4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
#pragma once

#include <stdbool.h>
#include <string.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;

enum handover_cfg_ctx_type {
	HO_CFG_CTX_NET,
	HO_CFG_CTX_BTS,
};

struct handover_cfg *ho_cfg_init(void *ctx, enum handover_cfg_ctx_type ctx_type,
				 struct handover_cfg *higher_level_cfg);

typedef void (*ho_cfg_on_change_cb_t)(void *ctx, enum handover_cfg_ctx_type ctx_type);

/* ho_cfg_* code gets called during initialization of the global gsm_network struct, which is included in
 * various utility programs that don't need most of gsm_data.c, definitely no handover. The on_change
 * callback from the ho_cfg touches internals of the handover decision, which would cause utility
 * programs to require linking of most of the handover code. To break this linking cascade, have the
 * on_change callbacks as function pointers. */
extern ho_cfg_on_change_cb_t ho_cfg_on_change_congestion_check_interval_cb;

#define HO_CFG_STR_HANDOVER "Handover options\n"
#define HO_CFG_STR_WIN HO_CFG_STR_HANDOVER "Measurement averaging settings\n"
#define HO_CFG_STR_WIN_RXLEV HO_CFG_STR_WIN "Received-Level averaging\n"
#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 HO_CFG_STR_2 " (HO algo 2 only)\n"
#define HO_CFG_STR_MIN "Minimum Level/Quality thresholds before triggering HO" HO_CFG_STR_2
#define HO_CFG_STR_AFS_BIAS "Configure bias to prefer AFS (AMR on TCH/F) over other codecs" HO_CFG_STR_2
#define HO_CFG_STR_MIN_TCH "Minimum free TCH timeslots before cell is considered congested" HO_CFG_STR_2
#define HO_CFG_STR_PENALTY_TIME "Set penalty times to wait between repeated handovers" HO_CFG_STR_2

#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;
}

static inline bool a2tdma(const char *arg)
{
	if (!strcmp(arg, "full"))
		return true;
	return false;
}

static inline const char *tdma2a(bool val)
{
	return val? "full" : "subset";
}

static inline const int a2congestion_check_interval(const char *arg)
{
	if (!strcmp(arg, "disabled"))
		return 0;
	return atoi(arg);
}

static inline const char *congestion_check_interval2a(int val)
{
	static char str[9];
	if (val < 1
	    || snprintf(str, sizeof(str), "%d", val) >= sizeof(str))
		return "disabled";
	return str;
}

/* 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,
 *                     ON_CHANGE,
 *                     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.
 * ON_CHANGE: a ho_cfg_on_change_cb_t callback to invoke from ho_set_*(), or NULL.
 * 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, NULL, \
		"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(int, algorithm, 1, NULL, \
		"handover algorithm", "1|2", atoi, "%d", as_is, \
		HO_CFG_STR_HANDOVER \
		"Choose algorithm for handover decision\n" \
		"Algorithm 1: trigger handover based on comparing current cell and neighbor RxLev and RxQual," \
		" only.\n" \
		"Algorithm 2: trigger handover on RxLev/RxQual, and also to balance the load across several" \
		" cells. Consider available codecs. Prevent repeated handover by penalty timers.\n") \
	\
	HO_CFG_ONE_MEMBER(unsigned int, rxlev_avg_win, 10, NULL, \
		"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, NULL, \
		"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, NULL, \
		"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, NULL, \
		"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, NULL, \
		"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, NULL, \
		"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") \
	\
	HO_CFG_ONE_MEMBER(bool, as_active, 0, NULL, \
		"handover assignment", "0|1", a2bool, "%d", bool2i, \
		HO_CFG_STR_HANDOVER \
		"Enable or disable in-call channel re-assignment" HO_CFG_STR_2 \
		"Disable in-call assignment\n" \
		"Enable in-call assignment\n") \
	\
	HO_CFG_ONE_MEMBER(int, congestion_check_interval, 10, \
		ho_cfg_on_change_congestion_check_interval_cb, \
		"handover congestion-check", "disabled|<1-60>", \
		a2congestion_check_interval, "%s", congestion_check_interval2a, \
		HO_CFG_STR_HANDOVER \
		"Configure congestion check interval" HO_CFG_STR_2 \
		"Disable congestion checking, do not handover based on cell overload\n" \
		"Congestion check interval in seconds\n") \
	\
	HO_CFG_ONE_MEMBER(bool, full_tdma, subset, NULL, \
		"handover tdma-measurement", "full|subset", a2tdma, "%s", tdma2a, \
		HO_CFG_STR_HANDOVER \
		"Define measurement set of TDMA frames" HO_CFG_STR_2 \
		"Full set of 102/104 TDMA frames\n" \
		"Sub set of 4 TDMA frames (SACCH)\n") \
	\
	HO_CFG_ONE_MEMBER(int, min_rxlev, -100, NULL, \
		"handover min rxlev", "<-110--50>", atoi, "%d", as_is, \
		HO_CFG_STR_HANDOVER \
		HO_CFG_STR_MIN \
		"How weak may RxLev of an MS become before triggering HO\n" \
		"minimum RxLev (dBm)\n") \
	\
	HO_CFG_ONE_MEMBER(int, min_rxqual, 5, NULL, \
		"handover min rxqual", "<0-7>", atoi, "%d", as_is, \
		HO_CFG_STR_HANDOVER \
		HO_CFG_STR_MIN \
		"How bad may RxQual of an MS become before triggering HO\n" \
		"minimum RxQual (dBm)\n") \
	\
	HO_CFG_ONE_MEMBER(int, afs_bias_rxlev, 0, NULL, \
		"handover afs-bias rxlev", "<0-20>", atoi, "%d", as_is, \
		HO_CFG_STR_HANDOVER \
		HO_CFG_STR_AFS_BIAS \
		"RxLev improvement bias for AFS over other codecs\n" \
		"Virtual RxLev improvement (dBm)\n") \
	\
	HO_CFG_ONE_MEMBER(int, afs_bias_rxqual, 0, NULL, \
		"handover afs-bias rxqual", "<0-7>", atoi, "%d", as_is, \
		HO_CFG_STR_HANDOVER \
		HO_CFG_STR_AFS_BIAS \
		"RxQual improvement bias for AFS over other codecs\n" \
		"Virtual RxQual improvement (dBm)\n") \
	\
	HO_CFG_ONE_MEMBER(int, tchf_min_slots, 0, NULL, \
		"handover min-free-slots tch/f", "<0-9999>", atoi, "%d", as_is, \
		HO_CFG_STR_HANDOVER \
		HO_CFG_STR_MIN_TCH \
		"Minimum free TCH/F timeslots before cell is considered congested\n" \
		"Number of TCH/F slots\n") \
	\
	HO_CFG_ONE_MEMBER(int, tchh_min_slots, 0, NULL, \
		"handover min-free-slots tch/h", "<0-9999>", atoi, "%d", as_is, \
		HO_CFG_STR_HANDOVER \
		HO_CFG_STR_MIN_TCH \
		"Minimum free TCH/H timeslots before cell is considered congested\n" \
		"Number of TCH/H slots\n") \
	\
	HO_CFG_ONE_MEMBER(int, ho_max, 9999, NULL, \
		"handover max-handovers", "<1-9999>", atoi, "%d", as_is, \
		HO_CFG_STR_HANDOVER \
		"Maximum number of concurrent handovers allowed per cell" HO_CFG_STR_2 \
		"Number\n") \
	\
	HO_CFG_ONE_MEMBER(int, penalty_max_dist, 300, NULL, \
		"handover penalty-time max-distance", "<0-99999>", atoi, "%d", as_is, \
		HO_CFG_STR_HANDOVER \
		HO_CFG_STR_PENALTY_TIME \
		"Time to suspend handovers after leaving this cell due to exceeding max distance\n" \
		"Seconds\n") \
	\
	HO_CFG_ONE_MEMBER(int, penalty_failed_ho, 60, NULL, \
		"handover penalty-time failed-ho", "<0-99999>", atoi, "%d", as_is, \
		HO_CFG_STR_HANDOVER \
		HO_CFG_STR_PENALTY_TIME \
		"Time to suspend handovers after handover failure to this cell\n" \
		"Seconds\n") \
	\
	HO_CFG_ONE_MEMBER(int, penalty_failed_as, 60, NULL, \
		"handover penalty-time failed-assignment", "<0-99999>", atoi, "%d", as_is, \
		HO_CFG_STR_HANDOVER \
		HO_CFG_STR_PENALTY_TIME \
		"Time to suspend handovers after assignment failure in this cell\n" \
		"Seconds\n") \
	\
	HO_CFG_ONE_MEMBER(int, retries, 0, NULL, \
		"handover retries", "<0-9>", atoi, "%d", as_is, \
		HO_CFG_STR_HANDOVER \
		"Immediately retry on handover/assignment failure" HO_CFG_STR_2 \
		"Number of retries\n") \
	

/* Declare public API for handover cfg parameters... */
 
#define HO_CFG_ONE_MEMBER(TYPE, NAME, DEFAULT_VAL, ON_CHANGE, 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